Vite plugin runtime 옵션 구현 계획
Codex용: 구현 단계에서는
executing-plans,test-driven-development,verification-before-completion을 사용해 작업 단위로 진행한다.
목표: Vite plugin에서 기존 core runtime 옵션 전체를 안전하게 전달하고, custom hotkey와 URL 제외 필터를 공개 계약대로 실제 동작하게 만든다.
아키텍처: core는 설치 시 URL matcher와 hotkey matcher를 한 번 구성해 interceptor와 panel에 전달한다. plugin은 core 옵션 타입을 재사용하되, 작은 스키마 기반 serializer로 Node 설정을 virtual browser module에 안전하게 직렬화한다. native transport 호출과 첫 설치 우선 규칙은 유지한다.
기술 스택: TypeScript 6, Vitest 4, happy-dom, Vite 8, Playwright, pnpm workspace, Turborepo, Changesets, VitePress 2.
설계 문서:docs/plans/2026-07-27-vite-plugin-runtime-options-design.md
구현 원칙
- 각 동작은 실패하는 테스트로 시작하고 예상한 이유로 실패하는지 확인한다.
- URL 제외는 native 요청을 막지 않고 DevTools 기록만 생략한다.
- 필터와 단축키 설정 오류가 애플리케이션 실행을 중단하지 않게 한다.
BrowseSentEventOptions를 core와 plugin 설정의 SSOT로 유지한다.- 직렬화 코드에
eval또는Functionconstructor를 사용하지 않는다. - production build 제외와 첫 설치 우선 규칙을 바꾸지 않는다.
- 과거 계획 문서 본문은 수정하지 않는다.
- 각 커밋은 하나의 기능 또는 검증 책임만 포함한다.
커밋 구성
| 순서 | 책임 | 커밋 메시지 |
|---|---|---|
| 1 | 네 interceptor의 URL 제외 | feat(core): URL 제외 필터를 인터셉터에 연결 |
| 2 | custom hotkey parser | feat(ui): 사용자 단축키 조합 지원 |
| 3 | Vite plugin 옵션 직렬화 | feat(plugin-vite): core runtime 옵션 전달 |
| 4 | 실제 browser 통합 검증 | test(e2e): runtime 옵션 전달 검증 추가 |
| 5 | 사용자 문서 계약 갱신 | docs(guide): runtime 설정 계약 반영 |
| 6 | 다음 prerelease 변경 기록 | chore(release): runtime 옵션 기능 변경 기록 |
구현 계획
작업 1: URL 제외 matcher
파일:
- 생성:
packages/core/src/runtime/url-filter.ts - 생성:
packages/core/src/runtime/__tests__/url-filter.test.ts
단계 1: 문자열과 정규식 실패 테스트 작성
packages/core/src/runtime/__tests__/url-filter.test.ts에 다음 핵심 사례를 작성한다.
import { describe, expect, it } from "vitest";
import { createUrlFilter } from "../url-filter.js";
describe("createUrlFilter", () => {
it("matches a case-sensitive URL substring", () => {
const shouldExcludeUrl = createUrlFilter(["/health"]);
expect(shouldExcludeUrl("https://example.test/health?ready=1")).toBe(true);
expect(shouldExcludeUrl("https://example.test/HEALTH")).toBe(false);
});
it("keeps global regular expressions deterministic without mutating the input", () => {
const pattern = /\/internal\/events/g;
pattern.lastIndex = 4;
const shouldExcludeUrl = createUrlFilter([pattern]);
expect(shouldExcludeUrl("https://example.test/internal/events")).toBe(true);
expect(shouldExcludeUrl("https://example.test/internal/events")).toBe(true);
expect(pattern.lastIndex).toBe(4);
});
});빈 pattern 목록, 일치하지 않는 URL, y flag와 정규식 source 접근 실패도 추가한다. 실패하는 pattern은 해당 URL을 포함하는 쪽으로 퇴행해야 한다.
단계 2: 실패 확인
실행:
pnpm --filter @browse-sent-event/core test -- src/runtime/__tests__/url-filter.test.ts기대 결과:
../url-filter.jsmodule을 찾을 수 없어 실패한다.- 기존 test 실패는 없어야 한다.
단계 3: 최소 matcher 구현
packages/core/src/runtime/url-filter.ts에 내부 matcher를 만든다.
export type BrowseSentEventUrlFilter = (url: string) => boolean;
type UrlMatcher = (url: string) => boolean;
function createRegExpMatcher(pattern: RegExp): UrlMatcher | undefined {
try {
const matcher = new RegExp(pattern.source, pattern.flags);
return (url) => {
try {
matcher.lastIndex = 0;
return matcher.test(url);
} catch {
return false;
} finally {
matcher.lastIndex = 0;
}
};
} catch {
return undefined;
}
}
export function createUrlFilter(
patterns: readonly (string | RegExp)[],
): BrowseSentEventUrlFilter {
const matchers = patterns.flatMap<UrlMatcher>((pattern) => {
if (typeof pattern === "string") {
return [(url) => url.includes(pattern)];
}
const matcher = createRegExpMatcher(pattern);
return matcher ? [matcher] : [];
});
return (url) => matchers.some((matcher) => matcher(url));
}정규식 getter 실패와 matcher 실패를 모두 catch하되 문자열은 가공하거나 소문자로 바꾸지 않는다.
단계 4: matcher 테스트 통과 확인
실행:
pnpm --filter @browse-sent-event/core test -- src/runtime/__tests__/url-filter.test.ts기대 결과:
- URL filter test가 모두 통과한다.
- 같은
g또는y정규식을 반복 실행해도 결과가 같다. - 입력 정규식의
lastIndex가 유지된다.
작업 2: URL 필터를 네 interceptor에 연결
파일:
- 수정:
packages/core/src/interceptors/types.ts - 수정:
packages/core/src/interceptors/websocket.ts - 수정:
packages/core/src/interceptors/eventsource.ts - 수정:
packages/core/src/interceptors/fetch-stream.ts - 수정:
packages/core/src/interceptors/xml-http-request.ts - 수정:
packages/core/src/runtime/install.ts - 수정:
packages/core/src/interceptors/__tests__/websocket.test.ts - 수정:
packages/core/src/interceptors/__tests__/eventsource.test.ts - 수정:
packages/core/src/interceptors/__tests__/fetch-stream.test.ts - 수정:
packages/core/src/interceptors/__tests__/xml-http-request.test.ts
단계 1: 프로토콜별 제외 실패 테스트 작성
각 interceptor test에 제외 URL이 native 동작은 실행하지만 engine에는 기록되지 않는 사례를 하나씩 추가한다.
WebSocket은 다음 상태를 확인한다.
const socket = new globalThis.window.WebSocket("wss://example.test/ignored");
socket.send("native message");
expect(Reflect.get(socket, "sent")).toEqual(["native message"]);
expect(Object.hasOwn(socket, "send")).toBe(false);
expect(engine.getConnections()).toEqual([]);
expect(engine.getMessages()).toEqual([]);EventSource는 instance가 반환되고 close가 own property로 덮이지 않았으며 기록이 없는지 확인한다. fetch는 clone() spy가 호출되지 않고 원본 response payload를 읽을 수 있는지 확인한다.
XHR은 다음 흐름을 사용한다.
request.open("POST", "https://example.test/ignored");
request.send("native body");
Reflect.apply(Reflect.get(request, "succeed"), request, ["native response"]);
expect(Reflect.get(request, "sentBodies")).toEqual(["native body"]);
expect(engine.getConnections()).toEqual([]);
expect(engine.getMessages()).toEqual([]);각 설치 context에는 shouldExcludeUrl: (url) => url.includes("/ignored")를 전달한다.
단계 2: interceptor 테스트 실패 확인
실행:
pnpm --filter @browse-sent-event/core test -- \
src/interceptors/__tests__/websocket.test.ts \
src/interceptors/__tests__/eventsource.test.ts \
src/interceptors/__tests__/fetch-stream.test.ts \
src/interceptors/__tests__/xml-http-request.test.ts기대 결과:
- 제외 URL도 현재 engine에 기록되므로 새 assertion이 실패한다.
- native instance와 response를 사용할 수 있다는 assertion은 통과한다.
단계 3: context의 안전한 판정 helper 추가
packages/core/src/interceptors/types.ts에 선택 predicate와 실패 격리 helper를 추가한다. 선택 속성으로 두면 filter를 지정하지 않은 low-level interceptor 테스트와 내부 호출은 기존처럼 모든 URL을 포함한다.
export interface BrowseSentEventInterceptorContext {
readonly engine: BrowseSentEventEngine;
readonly target: BrowseSentEventInterceptorTarget;
readonly shouldExcludeUrl?: (url: string) => boolean;
}
export function shouldExcludeUrl(
context: BrowseSentEventInterceptorContext,
url: string,
): boolean {
try {
return context.shouldExcludeUrl?.(url) === true;
} catch {
return false;
}
}단계 4: 프로토콜별 적용
WebSocket과 EventSource는 native constructor가 성공하고 URL 문자열을 얻은 뒤 다음 guard를 넣는다.
if (shouldExcludeUrl(context, url)) {
return socket;
}EventSource에서는 source를 반환한다. 이 guard는 event listener 등록과 instance method wrapping보다 앞에 둔다.
fetch는 originalFetch()와 기존 body 존재 확인 뒤 URL을 얻어 body clone보다 앞에서 반환한다.
const url = getRequestUrl(input);
if (shouldExcludeUrl(context, url)) {
return response;
}connection 기록에는 같은 url 변수를 사용해 URL 변환을 중복하지 않는다.
XHR은 beginObservation() 첫머리에서 currentDescriptor.url을 판정해 제외 시 active connection을 만들지 않고 반환한다. 이후 wrapper는 원본 send()를 그대로 실행한다.
단계 5: runtime에서 matcher 한 개 공유
packages/core/src/runtime/install.ts에서 resolved filter를 matcher로 바꾸고 공통 context를 만든다.
const resolvedOptions = resolveOptions(options);
const interceptorContext = {
engine: runtime.engine,
shouldExcludeUrl: createUrlFilter(resolvedOptions.filter.excludeUrls),
target,
};네 installer에 같은 interceptorContext를 전달한다. panel은 기존처럼 resolvedOptions.panel을 받는다.
단계 6: core 회귀 테스트
실행:
pnpm --filter @browse-sent-event/core test
pnpm --filter @browse-sent-event/core typecheck기대 결과:
- URL matcher와 네 interceptor test가 통과한다.
- 포함 URL의 기존 connection, message, lifecycle test도 모두 통과한다.
- TypeScript 오류가 없다.
단계 7: 첫 기능 커밋
git add \
packages/core/src/runtime/url-filter.ts \
packages/core/src/runtime/__tests__/url-filter.test.ts \
packages/core/src/interceptors/types.ts \
packages/core/src/interceptors/websocket.ts \
packages/core/src/interceptors/eventsource.ts \
packages/core/src/interceptors/fetch-stream.ts \
packages/core/src/interceptors/xml-http-request.ts \
packages/core/src/runtime/install.ts \
packages/core/src/interceptors/__tests__/websocket.test.ts \
packages/core/src/interceptors/__tests__/eventsource.test.ts \
packages/core/src/interceptors/__tests__/fetch-stream.test.ts \
packages/core/src/interceptors/__tests__/xml-http-request.test.ts
git commit -m "feat(core): URL 제외 필터를 인터셉터에 연결"작업 3: Custom hotkey parser
파일:
- 생성:
packages/core/src/ui/hotkey.ts - 생성:
packages/core/src/ui/__tests__/hotkey.test.ts - 수정:
packages/core/src/ui/mount.ts - 수정:
packages/core/src/ui/__tests__/mount.test.ts
단계 1: parser 실패 테스트 작성
packages/core/src/ui/__tests__/hotkey.test.ts에 다음 계약을 작성한다.
import { describe, expect, it } from "vitest";
import { createHotkeyMatcher } from "../hotkey.js";
function keyboardEvent(
key: string,
init: Omit<KeyboardEventInit, "key"> = {},
): KeyboardEvent {
return new globalThis.KeyboardEvent("keydown", { ...init, key });
}
describe("createHotkeyMatcher", () => {
it("matches portable cmd with either meta or control", () => {
const matches = createHotkeyMatcher("cmd+shift+b");
expect(matches?.(keyboardEvent("b", { metaKey: true, shiftKey: true }))).toBe(true);
expect(matches?.(keyboardEvent("B", { ctrlKey: true, shiftKey: true }))).toBe(true);
});
it("requires an exact modifier set", () => {
const matches = createHotkeyMatcher("ctrl+alt+k");
expect(matches?.(keyboardEvent("k", { altKey: true, ctrlKey: true }))).toBe(true);
expect(
matches?.(
keyboardEvent("k", {
altKey: true,
ctrlKey: true,
shiftKey: true,
}),
),
).toBe(false);
});
it.each(["", "cmd", "cmd++r", "cmd+r+k", "shift+shift+r", "cmd+ctrl+r"])(
"rejects invalid hotkey %s",
(hotkey) => {
expect(createHotkeyMatcher(hotkey)).toBeUndefined();
},
);
});토큰 순서, 공백, meta, ctrl, alt, shift, primary modifier 두 개를 동시에 누른 경우도 추가한다.
단계 2: 실패 확인
실행:
pnpm --filter @browse-sent-event/core test -- src/ui/__tests__/hotkey.test.ts기대 결과:
../hotkey.jsmodule을 찾을 수 없어 실패한다.
단계 3: parser와 matcher 최소 구현
packages/core/src/ui/hotkey.ts에서 modifier token과 key token을 분리한다.
export type BrowseSentEventHotkeyMatcher = (event: KeyboardEvent) => boolean;
const modifiers = new Set(["alt", "cmd", "ctrl", "meta", "shift"]);
export function createHotkeyMatcher(
hotkey: string,
): BrowseSentEventHotkeyMatcher | undefined {
const tokens = hotkey
.toLowerCase()
.split("+")
.map((token) => token.trim());
if (tokens.some((token) => token.length === 0)) {
return undefined;
}
const declaredModifiers = new Set<string>();
const keys: string[] = [];
for (const token of tokens) {
if (!modifiers.has(token)) {
keys.push(token);
continue;
}
if (declaredModifiers.has(token)) {
return undefined;
}
declaredModifiers.add(token);
}
if (
keys.length !== 1 ||
(declaredModifiers.has("cmd") &&
(declaredModifiers.has("meta") || declaredModifiers.has("ctrl")))
) {
return undefined;
}
const key = keys[0];
return (event) => {
const primaryMatches = declaredModifiers.has("cmd")
? event.metaKey !== event.ctrlKey
: event.metaKey === declaredModifiers.has("meta") &&
event.ctrlKey === declaredModifiers.has("ctrl");
return (
primaryMatches &&
event.altKey === declaredModifiers.has("alt") &&
event.shiftKey === declaredModifiers.has("shift") &&
event.key.toLowerCase() === key
);
};
}keys[0]의 undefined narrowing은 noUncheckedIndexedAccess에서 확인하고, 필요하면 length 검사 뒤 지역 변수 guard를 추가한다.
단계 4: mount에 설치 시 한 번 연결
packages/core/src/ui/mount.ts의 고정 matcher를 제거하고 panel을 만들 때 createHotkeyMatcher(options.options.hotkey)를 한 번 호출한다.
유효한 matcher가 있을 때만 keydown listener를 등록한다. unmount()에서도 등록된 경우에만 제거한다. invalid hotkey에서도 element append와 launcher는 유지한다.
mount.test.ts에는 ctrl+alt+k로 toggle되는 사례와 invalid hotkey가 panel mount를 막지 않는 사례를 추가한다.
단계 5: UI 테스트와 typecheck
실행:
pnpm --filter @browse-sent-event/core test -- \
src/ui/__tests__/hotkey.test.ts \
src/ui/__tests__/mount.test.ts
pnpm --filter @browse-sent-event/core typecheck기대 결과:
- 기본
cmd+shift+r과 custom 조합이 통과한다. - 추가 modifier와 invalid 문법은 toggle하지 않는다.
- panel mount와 unmount 회귀가 없다.
단계 6: 두 번째 기능 커밋
git add \
packages/core/src/ui/hotkey.ts \
packages/core/src/ui/__tests__/hotkey.test.ts \
packages/core/src/ui/mount.ts \
packages/core/src/ui/__tests__/mount.test.ts
git commit -m "feat(ui): 사용자 단축키 조합 지원"작업 4: Plugin 옵션 직렬화
파일:
- 수정:
packages/plugin-vite/src/index.ts - 수정:
packages/plugin-vite/src/injection.ts - 수정:
packages/plugin-vite/src/__tests__/injection.test.ts - 수정:
packages/plugin-vite/src/__tests__/plugin.test.ts - 수정:
packages/plugin-vite/src/__tests__/vite-fixture.test.ts
단계 1: 직렬화 실패 테스트 작성
injection.test.ts에 nested option과 정규식 source를 고정한다.
const code = createBootstrapModuleCode({
capacity: 250,
panel: {
autoOpen: true,
hotkey: "ctrl+alt+k",
position: "top-left",
},
filter: {
excludeUrls: ["/health", /\/internal\/events(?:\?|$)/gi],
},
});
expect(code).toContain('"capacity":250');
expect(code).toContain('"hotkey":"ctrl+alt+k"');
expect(code).toContain('new RegExp("\\\\/internal\\\\/events(?:\\\\?|$)", "gi")');
expect(code).not.toContain('"enabled"');실제 expected string은 JSON.stringify(pattern.source)의 결과를 조합해 backslash 개수를 사람이 중복 정의하지 않게 한다. quote, 줄바꿈, </script>와 Unicode 문자열도 JSON 문자열로 보존되는지 확인한다.
plugin.test.ts에는 plugin option을 준 뒤 virtual module load() 결과가 같은 capacity와 filter를 포함하는 사례를 추가한다.
단계 2: 실패 확인
실행:
pnpm --filter @browse-sent-event/plugin-vite test -- \
src/__tests__/injection.test.ts \
src/__tests__/plugin.test.ts기대 결과:
createBootstrapModuleCode()가 인자를 받지 않거나 생성 코드에 옵션이 없어 실패한다.
단계 3: core 타입을 plugin 옵션의 SSOT로 연결
packages/plugin-vite/src/index.ts에서 type-only import를 사용한다.
import type { BrowseSentEventOptions } from "@browse-sent-event/core";
export interface BrowseSentEventVitePluginOptions extends BrowseSentEventOptions {
readonly enabled?: boolean;
}plugin 생성 시 bootstrap source를 한 번 만든다.
const enabled = options.enabled ?? true;
const bootstrapModuleCode = createBootstrapModuleCode(options);load()는 같은 source를 반환한다. serializer가 알려진 core option property만 읽으므로 enabled와 알 수 없는 runtime property는 생성 코드에 들어가지 않는다.
단계 4: 스키마 기반 serializer 구현
packages/plugin-vite/src/injection.ts에 type-only core import와 작은 source builder를 추가한다.
function serializeProperty(name: string, value: string | undefined): string | undefined {
return value === undefined ? undefined : `${JSON.stringify(name)}:${value}`;
}
function serializeObject(properties: readonly (string | undefined)[]): string {
return `{${properties.filter((property) => property !== undefined).join(",")}}`;
}
function serializeExcludeUrl(pattern: string | RegExp): string {
return typeof pattern === "string"
? JSON.stringify(pattern)
: `new RegExp(${JSON.stringify(pattern.source)}, ${JSON.stringify(pattern.flags)})`;
}serializePanel, serializeFilter, serializeBrowseSentEventOptions를 옵션 schema에 맞춰 분리한다. number, boolean과 string은 JSON.stringify() 결과를 사용하고 undefined property는 생략한다.
createBootstrapModuleCode(options = {})는 다음 형태를 생성한다.
return [
`import { installBrowseSentEvent } from "@browse-sent-event/core";`,
`installBrowseSentEvent(${serializeBrowseSentEventOptions(options)});`,
].join("\n");단계 5: Vite fixture에서 production 경계 회귀 확인
vite-fixture.test.ts의 dev server test는 plugin에 capacity와 정규식 filter를 전달하고, virtual bootstrap module load 결과에 복원 코드가 있는지 확인한다. 기존 production build test는 그대로 유지해 emitted file에 browse-sent-event가 없는지 검증한다.
단계 6: plugin 전체 검증
실행:
pnpm --filter @browse-sent-event/plugin-vite test
pnpm --filter @browse-sent-event/plugin-vite typecheck
pnpm --filter @browse-sent-event/plugin-vite build기대 결과:
- 일반 옵션과 정규식 직렬화 test가 통과한다.
enabled: false와 production build 제외 test가 계속 통과한다.- build declaration에서 plugin options가 core options를 확장한다.
- runtime import가 아닌 type-only import이므로 plugin Node bundle 초기화에 불필요한 core 실행이 추가되지 않는다.
단계 7: 세 번째 기능 커밋
git add \
packages/plugin-vite/src/index.ts \
packages/plugin-vite/src/injection.ts \
packages/plugin-vite/src/__tests__/injection.test.ts \
packages/plugin-vite/src/__tests__/plugin.test.ts \
packages/plugin-vite/src/__tests__/vite-fixture.test.ts
git commit -m "feat(plugin-vite): core runtime 옵션 전달"작업 5: 실제 Chromium 통합 검증
파일:
- 수정:
examples/devtools-browser-fixture/vite.config.ts - 수정:
examples/devtools-browser-fixture/src/fixture-probe.ts - 수정:
e2e/devtools-panel.spec.ts
단계 1: fixture plugin 옵션 설정
examples/devtools-browser-fixture/vite.config.ts의 plugin을 다음 설정으로 바꾼다.
browseSentEvent({
capacity: 25,
panel: {
hotkey: "ctrl+alt+b",
},
filter: {
excludeUrls: [/\/__bse-fixture\/ignored-stream(?:\?|$)/],
},
});기존 autoOpen: false와 bottom-right 기본값은 유지해 visual snapshot을 바꾸지 않는다.
fixture endpoint에 /__bse-fixture/ignored-stream을 추가하고 정상 text response를 반환한다. probe에는 요청 전후 snapshot count와 response text를 반환하는 runIgnoredFetchStream()을 추가한다.
단계 2: browser 실패 테스트 작성
e2e/devtools-panel.spec.ts에 다음 시나리오를 추가한다.
- page를 새로 연다.
- runtime capacity가
25인지 읽는다. Control+Alt+B를 눌러 panel이 열리는지 확인한다.runIgnoredFetchStream()을 실행한다.- response payload가 정상인지 확인한다.
- 실행 전후 connection과 message 수가 같은지 확인한다.
이 test는 plugin option 전달, 정규식 복원, custom hotkey와 실제 fetch 제외를 한 browser 흐름에서 검증한다.
단계 3: 실패 확인
실행:
pnpm test:e2e --project chromium-desktop --grep "runtime options"기대 결과:
- 작업 1~4 구현 전에는 capacity, custom hotkey 또는 filter assertion 중 하나가 실패한다.
- 작업 1~4 뒤에는 새 browser test가 통과한다.
계획을 같은 순서로 실행해 이미 기능 구현이 끝난 상태라면, commit 직전 git show HEAD^ 기반 임시 검증 대신 새 E2E가 현재 구현을 검증하는지 test assertion과 fixture 경계를 코드 리뷰로 확인한다.
단계 4: 전체 desktop E2E 회귀 확인
실행:
pnpm test:e2e --project chromium-desktop기대 결과:
- panel mount, seeded UI, WebSocket, EventSource, fetch와 XHR test가 모두 통과한다.
- 기존 visual snapshot에는 변경이 없다.
단계 5: 브라우저 검증 커밋
git add \
examples/devtools-browser-fixture/vite.config.ts \
examples/devtools-browser-fixture/src/fixture-probe.ts \
e2e/devtools-panel.spec.ts
git commit -m "test(e2e): runtime 옵션 전달 검증 추가"작업 6: 사용자 문서 갱신
파일:
- 수정:
README.md - 수정:
packages/core/README.md - 수정:
packages/plugin-vite/README.md - 수정:
docs/guides/configuration-and-limitations.md - 수정:
docs/release/github-release.md - 수정:
docs/plans/index.md
단계 1: 제거할 제한 문구 확인
실행:
rg -n \
"Vite plugin.*설정 전달|plugin option 전달|custom hotkey.*미지원|excludeUrls.*미적용|현재 공개 옵션은.*enabled" \
README.md packages docs기대 결과:
- root README, package README, 설정 가이드와 release 문서에서 실제 구현과 어긋나는 문구 위치를 확인한다.
- 과거
docs/plans/*의 역사 기록은 수정 대상에서 제외한다.
단계 2: Vite plugin 사용자 예제 갱신
root README, plugin README와 설정 가이드에 다음 형태의 예제를 반영한다.
browseSentEvent({
capacity: 5_000,
panel: {
autoOpen: true,
position: "bottom-left",
hotkey: "cmd+shift+b",
},
filter: {
excludeUrls: ["/health", /\/internal\/events(?:\?|$)/],
},
});plugin이 enabled와 모든 core runtime 옵션을 받는다고 설명한다. enabled: false와 apply: "serve"의 production 제외 계약은 유지한다.
단계 3: Core 계약과 제한 갱신
core README와 설정 가이드에 다음 내용을 명시한다.
- string filter는 기록 URL 원문의 대소문자 구분 부분 문자열 일치다.
RegExpfilter는 반복 호출에도 결정적으로 검사된다.- 제외 URL도 native 통신은 실행되고 DevTools 기록만 생략된다.
- URL filter는 payload redaction이나 network 차단이 아니다.
- hotkey modifier는
cmd,meta,ctrl,alt,shift를 지원한다. - 잘못된 hotkey는 단축키만 비활성화하며 launcher는 유지한다.
- 첫 설치가 옵션을 결정하고 설정 변경에는 page reload가 필요하다.
- XHR
URL객체와 Worker 미지원 제한은 그대로다.
지원하게 된 세 항목을 알려진 alpha 제한 표와 root README의 후속 회수 목록에서 제거한다. GitHub release note checklist도 현재 남은 제한만 가리키게 수정한다.
단계 4: 계획 인덱스 상태 변경
docs/plans/index.md에 구현 계획 링크를 추가하고 설계와 구현 상태를 다음처럼 갱신한다.
| 날짜 | 계획 | 상태 |
|---|---|---|
| 2026-07-27 | Vite plugin runtime 옵션 설계 | 완료 |
| 2026-07-27 | Vite plugin runtime 옵션 구현 | 완료 |
단계 5: 문서 검증
실행:
pnpm docs:build
pnpm format:check
git diff --check기대 결과:
- VitePress가 모든 page를 렌더링한다.
- 새 옵션 예제에 존재하지 않는 export가 없다.
- 현재 사용자 문서에 제거된 제한이 남지 않는다.
- 과거 계획에는 당시 제한 기록이 보존된다.
단계 6: 문서 커밋
git add \
README.md \
packages/core/README.md \
packages/plugin-vite/README.md \
docs/guides/configuration-and-limitations.md \
docs/release/github-release.md \
docs/plans/index.md
git commit -m "docs(guide): runtime 설정 계약 반영"작업 7: Changeset 작성
파일:
- 생성:
.changeset/<generated-name>.md
단계 1: package bump 기준 확인
이번 변경은 기존 옵션의 실제 동작과 plugin 공개 옵션을 추가하는 하위 호환 기능이다. 저장소의 0.x 정책에 따라 두 package 모두 minor changeset을 작성한다.
---
"@browse-sent-event/core": minor
"@browse-sent-event/plugin-vite": minor
---
Vite plugin에서 core runtime 옵션 전체를 전달하고, 모든 browser interceptor의
URL 제외 필터와 사용자 정의 panel 단축키를 지원합니다.현재 prerelease mode를 유지하며 이 PR에서 changeset version이나 package version 변경은 실행하지 않는다.
단계 2: Changesets 상태 검증
실행:
pnpm changeset status기대 결과:
- 새 changeset에 core와 plugin-vite가
minor로 표시된다. - private browser fixture는 release 대상이 아니다.
- 기존 prerelease changeset과
pre.json은 임의로 수정하지 않는다.
단계 3: release metadata 커밋
git add .changeset/<generated-name>.md
git commit -m "chore(release): runtime 옵션 기능 변경 기록"검증 계획
검증 1: 커밋별 표적 검사
각 기능 커밋 전 다음 최소 gate를 통과한다.
| 커밋 | 필수 검사 |
|---|---|
| URL 제외 | core URL filter와 네 interceptor test, core typecheck |
| Custom hotkey | hotkey와 mount test, core typecheck |
| Plugin 전달 | plugin 전체 test, typecheck, build |
| Browser 검증 | Chromium desktop E2E 전체 |
| 문서 | docs build, format check, diff check |
| Changeset | pnpm changeset status |
실패하면 다음 커밋으로 넘어가지 않고 원인을 현재 책임 안에서 수정한다.
검증 2: 전체 자동 검사
모든 커밋 후 저장소 root에서 순서대로 실행한다.
pnpm format:check
pnpm lint
pnpm typecheck
pnpm test
pnpm build
pnpm docs:build
pnpm test:e2e
pnpm test:release
pnpm pack:check
git diff --check기대 결과:
- 모든 명령이 exit code
0으로 끝난다. - Playwright desktop과 mobile project가 모두 통과한다.
- package tarball에 source, test 또는
workspace:*dependency가 남지 않는다. - VitePress의 기존 chunk 크기 warning은 허용하지만 새 build error는 없다.
test:release와 pack:check가 같은 검증 경로를 일부 공유하더라도 CI와 수동 release gate를 각각 재현하기 위해 둘 다 실행한다.
검증 3: 직렬화와 공급망 경계
의존성을 추가하지 않으므로 package.json과 pnpm-lock.yaml이 바뀌지 않아야 한다.
git diff main...HEAD -- package.json packages/*/package.json pnpm-lock.yaml
rg -n "eval\\(|new Function|Function\\(" packages/plugin-vite/src
pnpm audit --audit-level high기대 결과:
- manifest와 lockfile diff가 없다.
- production serializer에 동적 코드 실행이 없다.
- audit에서 high 또는 critical advisory가 없다.
audit은 registry network가 필요한 검사다. sandbox 또는 registry 장애로 실행할 수 없으면 그 사유와 마지막 성공 시점을 PR에 기록하고, publish 전 maintainer 환경에서 다시 실행한다.
검증 4: 수동 browser 확인
자동 E2E 통과 뒤 fixture dev server를 실행한다.
pnpm --filter @browse-sent-event/devtools-browser-fixture dev \
--host 127.0.0.1 \
--port 4174브라우저에서 http://127.0.0.1:4174를 열고 다음을 확인한다.
- panel host가 하나만 mount된다.
- 초기 panel은 닫혀 있다.
Ctrl+Alt+B로 panel이 열리고 다시 닫힌다.- seed, WebSocket, EventSource, fetch와 XHR의 기존 기록이 보인다.
- ignored stream의 response는 정상 수신되지만 timeline에는 나타나지 않는다.
- console에 bootstrap 또는 option parsing error가 없다.
- desktop과 mobile viewport에서 panel이 겹치거나 화면 밖으로 밀리지 않는다.
확인이 끝나면 dev server를 정상 종료한다. UI 구조를 바꾸지 않으므로 새 visual snapshot은 만들지 않는다.
검증 5: 최종 diff와 이력
git status --short
git log --oneline main..HEAD
git diff --stat main...HEAD
git diff --check main...HEAD기대 결과:
- 계획한 파일 외 우발적 변경이 없다.
- 설계와 구현 계획 뒤에 여섯 개 책임별 커밋이 순서대로 존재한다.
- build 산출물, Playwright report와 임시 Vite fixture가 추적되지 않는다.
- working tree가 깨끗하다.
완료 보고
최종 보고에는 다음을 포함한다.
- 구현된 plugin 옵션과 hotkey/filter 계약
- native transport 보존 방식
- 기능별 commit hash
- 자동 검사와 browser 검증 결과
- 허용한 기존 warning
- 미지원으로 남은 Worker, XHR URL 객체와 HMR live reconfiguration
- changeset bump와 다음 alpha versioning이 maintainer 승인 단계라는 사실