gemiterm 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -9
- package/package.json +13 -4
- package/src/cli/command-registry.ts +66 -62
- package/src/cli/commands/continue-command.ts +75 -1
- package/src/cli/commands/install-skills-command.ts +35 -0
- package/src/cli/commands/new-command.ts +62 -0
- package/src/cli/index.ts +19 -44
- package/src/cli/utils/long-arg-guard.ts +28 -0
- package/src/cli/utils/prompt-file.ts +26 -0
- package/src/infrastructure/cli-parser.ts +77 -0
- package/src/infrastructure/io.ts +9 -0
- package/src/services/skills-cli-driver.ts +58 -0
- package/src/services/skills-service.ts +52 -0
- package/.env +0 -2
- package/.github/workflows/manual-release.yml +0 -34
- package/.github/workflows/release.yml +0 -198
- package/.github/workflows/test.yml +0 -29
- package/.opencode/commands/opsx-apply.md +0 -152
- package/.opencode/commands/opsx-archive.md +0 -157
- package/.opencode/commands/opsx-explore.md +0 -169
- package/.opencode/commands/opsx-propose.md +0 -104
- package/.opencode/commands/opsx-sync.md +0 -140
- package/.opencode/commands/search-intelligently.md +0 -9
- package/.opencode/opencode.json +0 -20
- package/.opencode/skills/intellisearch/.version +0 -1
- package/.opencode/skills/intellisearch/SKILL.md +0 -84
- package/.opencode/skills/intellisearch/references/brave-search.md +0 -67
- package/.opencode/skills/intellisearch/references/ddg-search.md +0 -79
- package/.opencode/skills/intellisearch/references/deepwiki-tools.md +0 -246
- package/.opencode/skills/intellisearch/references/examples.md +0 -97
- package/.opencode/skills/intellisearch/references/gh-cli.md +0 -143
- package/.opencode/skills/intellisearch/references/google-search.md +0 -40
- package/.opencode/skills/intellisearch/references/search-workflow.md +0 -143
- package/.opencode/skills/intellisearch/references/workflow.md +0 -115
- package/.opencode/skills/openspec-apply-change/SKILL.md +0 -159
- package/.opencode/skills/openspec-archive-change/SKILL.md +0 -117
- package/.opencode/skills/openspec-explore/SKILL.md +0 -287
- package/.opencode/skills/openspec-propose/SKILL.md +0 -111
- package/.opencode/skills/openspec-sync-specs/SKILL.md +0 -147
- package/.serena/project.yml +0 -133
- package/AGENTS.md +0 -136
- package/CHANGELOG.md +0 -3
- package/bun.lock +0 -105
- package/docs/INSTALL.md +0 -185
- package/docs/PLAYWRIGHT_CLI_API.md +0 -306
- package/docs/python-release-for-reference.md +0 -175
- package/examples/sample_json_export.json +0 -32
- package/examples/sample_markdown_export.md +0 -221
- package/examples/usage_examples.sh +0 -222
- package/install.ps1 +0 -264
- package/install.sh +0 -275
- package/openspec/MAESTRO_MIGRATION.md +0 -180
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/design.md +0 -194
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/proposal.md +0 -52
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/specs/multi-profile-conversations/spec.md +0 -133
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/tasks.md +0 -95
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/design.md +0 -28
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/proposal.md +0 -22
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/specs/interactive-prompt-loop/spec.md +0 -35
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/tasks.md +0 -28
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/design.md +0 -323
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/proposal.md +0 -122
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/specs/no-capability-changes/spec.md +0 -29
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/tasks.md +0 -172
- package/openspec/changes/archive/2026-06-09-v2-install-migration/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-06-09-v2-install-migration/design.md +0 -241
- package/openspec/changes/archive/2026-06-09-v2-install-migration/proposal.md +0 -41
- package/openspec/changes/archive/2026-06-09-v2-install-migration/specs/v2-installer/spec.md +0 -205
- package/openspec/changes/archive/2026-06-09-v2-install-migration/tasks.md +0 -97
- package/openspec/changes/commander-cli-parser/.openspec.yaml +0 -2
- package/openspec/changes/commander-cli-parser/design.md +0 -107
- package/openspec/changes/commander-cli-parser/proposal.md +0 -31
- package/openspec/changes/commander-cli-parser/specs/cli/spec.md +0 -48
- package/openspec/changes/commander-cli-parser/tasks.md +0 -33
- package/openspec/changes/cross-platform-build-and-ci/.openspec.yaml +0 -2
- package/openspec/changes/cross-platform-build-and-ci/design.md +0 -213
- package/openspec/changes/cross-platform-build-and-ci/proposal.md +0 -50
- package/openspec/changes/cross-platform-build-and-ci/specs/path-and-file-mediation/spec.md +0 -335
- package/openspec/changes/cross-platform-build-and-ci/specs/release-pipeline/spec.md +0 -95
- package/openspec/changes/cross-platform-build-and-ci/tasks.md +0 -164
- package/openspec/config.yaml +0 -20
- package/openspec/specs/auth/spec.md +0 -394
- package/openspec/specs/cli/spec.md +0 -108
- package/openspec/specs/commands/spec.md +0 -409
- package/openspec/specs/configuration/spec.md +0 -130
- package/openspec/specs/conversations/spec.md +0 -139
- package/openspec/specs/domain-model/spec.md +0 -128
- package/openspec/specs/formatters/spec.md +0 -158
- package/openspec/specs/install-browser/spec.md +0 -146
- package/openspec/specs/interactive-prompt-loop/spec.md +0 -41
- package/openspec/specs/logger/spec.md +0 -71
- package/openspec/specs/mediator/spec.md +0 -72
- package/openspec/specs/multi-profile-conversations/spec.md +0 -145
- package/openspec/specs/path-utils/spec.md +0 -101
- package/openspec/specs/profiles/spec.md +0 -94
- package/openspec/specs/storage/spec.md +0 -198
- package/openspec/specs/testing/spec.md +0 -237
- package/openspec/specs/v2-installer/spec.md +0 -205
- package/openspec/specs/validators/spec.md +0 -65
- package/scripts/build.ts +0 -88
- package/scripts/clean-build.ps1 +0 -4
- package/scripts/clean-build.sh +0 -5
- package/scripts/install-browser.ps1 +0 -5
- package/scripts/install-browser.sh +0 -6
- package/scripts/lint-path-mediation.ps1 +0 -65
- package/scripts/lint-path-mediation.sh +0 -40
- package/tests/cli/auth-command.test.ts +0 -220
- package/tests/cli/command-registry.test.ts +0 -67
- package/tests/cli/continue-command.test.ts +0 -100
- package/tests/cli/delete-command.test.ts +0 -128
- package/tests/cli/export-all-command.test.ts +0 -375
- package/tests/cli/install-browser-command.test.ts +0 -48
- package/tests/cli/list-command.test.ts +0 -179
- package/tests/cli/new-command.test.ts +0 -139
- package/tests/cli/status-command.test.ts +0 -106
- package/tests/core/query-handlers.test.ts +0 -187
- package/tests/fixtures/auth-fixtures.ts +0 -98
- package/tests/fixtures/chat-fixtures.ts +0 -87
- package/tests/infrastructure/cli-table.test.ts +0 -107
- package/tests/infrastructure/formatters.test.ts +0 -202
- package/tests/infrastructure/io.test.ts +0 -197
- package/tests/infrastructure/logger.test.ts +0 -74
- package/tests/infrastructure/storage.test.ts +0 -335
- package/tests/infrastructure/validators.test.ts +0 -67
- package/tests/integration/commands/auth.test.ts +0 -186
- package/tests/integration/commands/continue.test.ts +0 -130
- package/tests/integration/commands/delete.test.ts +0 -140
- package/tests/integration/commands/export.test.ts +0 -266
- package/tests/integration/commands/fetch.test.ts +0 -238
- package/tests/integration/commands/list.test.ts +0 -281
- package/tests/integration/commands/profile.test.ts +0 -258
- package/tests/integration/commands/status.test.ts +0 -223
- package/tests/integration/installer-script-shape.test.ts +0 -67
- package/tests/parity/compare-outputs.ts +0 -359
- package/tests/parity/test-commands-parity.ps1 +0 -82
- package/tests/parity/test-commands-parity.sh +0 -61
- package/tests/services/auth-service.test.ts +0 -315
- package/tests/services/cookie-monitor.test.ts +0 -229
- package/tests/services/cookie-storage-service.test.ts +0 -230
- package/tests/services/gemini-client-wrapper.test.ts +0 -688
- package/tests/services/install-browser-service.test.ts +0 -83
- package/tests/services/playwright-cli-driver.test.ts +0 -468
- package/tests/services/profile-auth-manager.test.ts +0 -334
- package/tests/services/profile-service.test.ts +0 -224
- package/tests/setup.ts +0 -95
- package/tests/smoke/smoke.test.ts +0 -52
- package/tests/unit/config.test.ts +0 -159
- package/tests/unit/errors.test.ts +0 -119
- package/tests/unit/formatters.test.ts +0 -316
- package/tests/unit/mediator.test.ts +0 -152
- package/tests/unit/path-utils.test.ts +0 -289
- package/tsconfig.json +0 -16
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
## Context
|
|
2
|
-
|
|
3
|
-
The Bun rewrite of GemiTerm is functionally complete (432/432 unit/integration/parity/smoke tests pass on Bun 1.3.13, all 11 CLI commands registered, auth flow working) but two structural pieces are missing: the **delivery pipeline** (build fix, CI, release) and the **path-and-file mediation layer** (a single source of truth for `node:fs` and `node:path` access in `src/`).
|
|
4
|
-
|
|
5
|
-
The current `package.json:20-22` build scripts:
|
|
6
|
-
|
|
7
|
-
```json
|
|
8
|
-
"build": "bun build --compile --target=bun --outdir=dist src/cli/index.ts",
|
|
9
|
-
"build:linux": "bun build --compile --target=bun-linux-x64 --outdir=dist src/cli/index.ts",
|
|
10
|
-
"build:windows": "bun build --compile --target=bun-windows-x64 --outdir=dist src/cli/index.ts"
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Verified failure mode on Bun 1.3.13 (current dev environment): `bun run build` exits 1 with `error: cannot use --compile with --outdir`. Bun 1.3.x removed support for combining `--compile` with `--outdir` because the resulting tree semantics are undefined for a single-file executable.
|
|
14
|
-
|
|
15
|
-
The Python release workflow at `docs/python-release-for-reference.md` (used by v1.4.1) is the target shape for the new Bun release workflow, but with PyInstaller-specific steps replaced by `bun build --compile`. The Python release tagged artifacts as `GemiTerm` (Linux) and `GemiTerm.exe` (Windows); the Bun release MUST preserve those names so the v1.4.1 `install.ps1` referenced by the related `v2-install-migration` change keeps working.
|
|
16
|
-
|
|
17
|
-
A read-only audit of `src/` shows the rewrite does **node:fs** and **node:path** access in 9 files, with at least 4 copy-pasted write-to-disk blocks and ~20 open-coded `mkdirSync` / `existsSync` / `renameSync` calls. The original OpenSpec change proposed a `platform-detect.ts` module to share one private `isWsl()` helper; that helper does not need a new file, and the broader duplication is the actual cross-platform work.
|
|
18
|
-
|
|
19
|
-
The audit found:
|
|
20
|
-
|
|
21
|
-
- **`src/cli/commands/export-command.ts:72-82`**, **`export-all-command.ts:79-80` + 96-104`**, **`list-command.ts:138-143`**, **`fetch-command.ts:121-127`** — the same "resolve path + ensure parent dir + writeFileSync UTF-8" block, copied 4 times. Two of those (`list` and `fetch`) are byte-identical 6-line `writeOutput` methods.
|
|
22
|
-
- **`src/infrastructure/storage.ts`** — 11 inline `mkdirSync` / `writeFileSync` / `readFileSync` / `rmSync` / `renameSync` / `existsSync` calls in 6 methods.
|
|
23
|
-
- **`src/infrastructure/config.ts`** — 64 lines, of which 30 are open-coded `node:fs` operations wrapping the `path-utils.ts` getters.
|
|
24
|
-
- **`src/services/install-browser-service.ts`** — 11 inline `existsSync` / `readFileSync` calls; 2 near-identical `getEdgePaths` / `getChromePaths` methods; a private `isWsl()` that is the only consumer of `/proc/version` detection.
|
|
25
|
-
- **`src/cli/index.ts:33-35`** — the only place in the rewrite that needs `__filename` / `__dirname` and is the only consumer of `fileURLToPath`; a one-liner helper would clean it up.
|
|
26
|
-
|
|
27
|
-
The previous `isWsl()` is duplicated logic that will be needed by future command code, so it makes sense to lift it into `path-utils.ts`. The lift MUST be behavior-preserving; the 5 existing unit tests in `tests/services/install-browser-service.test.ts` are the regression gate. The `src/services/playwright-cli-driver.ts`, `src/services/cookie-monitor.ts`, and `src/services/auth-service.ts` files are off-limits and will not be touched by this change.
|
|
28
|
-
|
|
29
|
-
Constraints:
|
|
30
|
-
|
|
31
|
-
- **Bun 1.3.13** is the pinned toolchain (the env has it; `devDependencies.@types/bun: ^1.3.14`).
|
|
32
|
-
- **GitHub Actions** is the CI/CD host (the repo is on GitHub per `docs/python-release-for-reference.md`).
|
|
33
|
-
- **No breaking changes** to the CLI user surface; all changes are internal build/CI plus internal refactoring.
|
|
34
|
-
- **No new runtime dependencies** are added; only removal of the unused `commander`.
|
|
35
|
-
- **WSL** must be supported as a first-class target because GemiTerm's primary auth flow is browser-driven and WSL users invoke the Linux binary while their Chromium lives in the Windows host filesystem (handled today by `findWslBrowser` in `src/services/install-browser-service.ts:124-141`).
|
|
36
|
-
- **The "no `node:fs` outside path-utils and io" rule is enforced by a CI lint check** in `.github/workflows/test.yml`, not by human review. The check excludes `tests/` and `tests/parity/` (a test-helper script) and the two allowed `src/` consumers (`path-utils.ts` and `install-browser-service.ts`).
|
|
37
|
-
|
|
38
|
-
Stakeholders: maintainer (who ships the release), contributors (who need fast PR feedback), end users on Linux/Windows/WSL (who download the binary from GitHub Releases).
|
|
39
|
-
|
|
40
|
-
## Goals / Non-Goals
|
|
41
|
-
|
|
42
|
-
**Goals:**
|
|
43
|
-
|
|
44
|
-
- Fix `bun run build` / `build:linux` / `build:windows` so they produce a working single-file executable on Bun 1.3.13.
|
|
45
|
-
- Add a `release-pipeline` capability: three GitHub Actions workflows (`test.yml`, `build.yml`, `release.yml`) plus the build/clean/install-browser shell scripts that produce v2.0.0 cross-platform artifacts.
|
|
46
|
-
- Add a `path-and-file-mediation` capability: extend `src/infrastructure/path-utils.ts` with `isWSL()`, `getProjectRoot()`, and `getPackageJson()`; create `src/infrastructure/io.ts` with the file-ops helpers; refactor the 7 call sites that currently use `node:fs` / `node:path` directly; add a CI lint check that enforces the rule going forward.
|
|
47
|
-
- Keep the artifact naming convention `GemiTerm` (Linux/WSL) and `GemiTerm.exe` (Windows) so the v1.4.1 `install.ps1` keeps working.
|
|
48
|
-
- Cleanup: remove the orphan `{` file at the repo root, remove the stray `{` line in `.gitignore:23`, remove the unused `commander` dependency, decide the fate of the empty `src/commands/` placeholder.
|
|
49
|
-
- Update README with a "Building from source" section and a "Release artifacts" section.
|
|
50
|
-
|
|
51
|
-
**Non-Goals:**
|
|
52
|
-
|
|
53
|
-
- Touching `src/services/playwright-cli-driver.ts`, `src/services/cookie-monitor.ts`, or `src/services/auth-service.ts` (sensitive area per the change's explicit warning).
|
|
54
|
-
- Publishing to npm, PyPI, Homebrew, or any package registry — distribution is GitHub Releases + raw `install.sh` / `install.ps1` only.
|
|
55
|
-
- Adding a code-signing step for Windows binaries (would require a cert and is deferred).
|
|
56
|
-
- Adding macOS / Darwin builds to the matrix (no users on that platform have requested it; `isWSL()` is not relevant on macOS and the rewrite does not need a `isDarwin()` helper).
|
|
57
|
-
- Migrating the existing Python `v1.4.1 install.ps1` (that lives in the related `v2-install-migration` change).
|
|
58
|
-
- Adding new test cases for `install-browser-service` — the 5 existing tests are the regression gate and that is sufficient.
|
|
59
|
-
- Switching the CI provider away from GitHub Actions.
|
|
60
|
-
|
|
61
|
-
## Decisions
|
|
62
|
-
|
|
63
|
-
### D1. `src/infrastructure/io.ts` is a separate file from `path-utils.ts`, not a re-export
|
|
64
|
-
|
|
65
|
-
`path-utils.ts` is about **storage paths and config directories** (functions: `resolvePath`, `getConfigDir`, `getProfilesDir`, `getProfilePath`, `getProfileDir`, `getDefaultProfileMarkerPath`, plus three constants, plus the new `isWSL`, `getProjectRoot`, `getPackageJson`). `io.ts` is about **file-system operations** (`ensureDir`, `existsFile`, `readTextFile`, `writeTextFile`, `readJsonFile`, `writeJsonFile`, `removeDir`, `renameDir`, `isDirectory`, `listSubdirectories`). The two concerns are distinct: paths are values; file ops are side effects. Conflating them would create a God-module.
|
|
66
|
-
|
|
67
|
-
The dependency direction is one-way: `io.ts` may import from `path-utils.ts` (for path joining and the WSL detection), but `path-utils.ts` does not import from `io.ts`. This is verifiable by `grep` and is part of the lint rule.
|
|
68
|
-
|
|
69
|
-
**Alternative considered:** Put everything in `path-utils.ts`. Rejected: mixes concerns, makes the file longer, and obscures the dependency direction.
|
|
70
|
-
|
|
71
|
-
**Alternative considered:** Put everything in `io.ts` and re-export from `path-utils.ts` for backward compatibility. Rejected: forces every call site to know which is the "real" home; the dependency direction is the wrong way around.
|
|
72
|
-
|
|
73
|
-
### D2. Drop `--outdir` from `bun build --compile`; use `--outfile` for deterministic artifact paths
|
|
74
|
-
|
|
75
|
-
Verified failure mode on Bun 1.3.13: `bun build --compile --target=bun --outdir=dist src/cli/index.ts` exits 1 with `error: cannot use --compile with --outdir`. Bun 1.3.x removed this combination because a single-file executable has no meaningful relationship to an output directory tree.
|
|
76
|
-
|
|
77
|
-
Two viable fixes in Bun 1.3.x:
|
|
78
|
-
|
|
79
|
-
1. **Drop `--outdir` entirely** → Bun places the binary in `cwd` named after the **parent directory** of the entrypoint. Fragile and surprising.
|
|
80
|
-
2. **Use `--outfile <path>`** → Bun places the binary at the specified path. Matches the v1.4.1 PyInstaller convention and gives CI a deterministic upload path.
|
|
81
|
-
|
|
82
|
-
**Chosen: option 2** — `--outfile dist/gemiterm` for the platform-specific scripts. The resulting build scripts:
|
|
83
|
-
|
|
84
|
-
```json
|
|
85
|
-
"build": "bun build --compile --target=bun --outfile dist/gemiterm src/cli/index.ts",
|
|
86
|
-
"build:linux": "bun build --compile --target=bun-linux-x64 --outfile dist/gemiterm src/cli/index.ts",
|
|
87
|
-
"build:windows": "bun build --compile --target=bun-windows-x64 --outfile dist/gemiterm src/cli/index.ts",
|
|
88
|
-
"build:release": "bun build --compile --target=bun --outfile dist/gemiterm --minify src/cli/index.ts"
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### D3. The `isWSL()` helper is in `path-utils.ts`, not in a separate `platform-detect.ts`
|
|
92
|
-
|
|
93
|
-
The original spec proposed a 7-helper `platform-detect.ts` module (`isWindows`, `isLinux`, `isWSL`, `isDarwin`, `detectPlatform`, `getPlatformName`, `normalizePath`). A read-only audit found **zero callers** for six of those helpers; the only non-trivial check (`isWSL()`) is the one with a real consumer. Putting `isWSL()` in its own file is over-engineering; lifting it into `path-utils.ts` keeps the cross-platform concern in the same module as the other path-related code.
|
|
94
|
-
|
|
95
|
-
The 6 unnecessary helpers (`isWindows`, `isLinux`, `isDarwin`, `detectPlatform`, `getPlatformName`, `normalizePath`) are **dropped**. The only `process.platform` checks in the rewrite are 3 inline in `install-browser-service.ts` (lines 58, 61, 185), all of which are clearer as inline `=== 'win32'` / `=== 'linux'` checks than as named boolean predicates.
|
|
96
|
-
|
|
97
|
-
The `isWSL()` implementation is a strict superset of the old private `isWsl()`: it checks both `/proc/version` (for "microsoft" or "WSL" case-insensitive) AND the `WSL_DISTRO_NAME` env var. This is a minor improvement in detection coverage; the 5 unit tests in `tests/services/install-browser-service.test.ts` are not affected because they do not exercise WSL detection directly.
|
|
98
|
-
|
|
99
|
-
### D4. The 4-copy "write to disk" block becomes a single `writeTextFile()` helper
|
|
100
|
-
|
|
101
|
-
The same 3-line pattern (`resolve path + ensure parent dir + writeFileSync UTF-8`) appears in `export-command.ts`, `export-all-command.ts`, `list-command.ts`, and `fetch-command.ts`. Two of those (`list` and `fetch`) are byte-identical 6-line `writeOutput` private methods. A single `writeTextFile(path, content)` in `io.ts` consolidates the pattern; the call sites collapse to one line each.
|
|
102
|
-
|
|
103
|
-
### D5. The CI lint check is a hard failure on PRs
|
|
104
|
-
|
|
105
|
-
The rule "no `node:fs` / `node:path` / `node:os` imports in `src/` outside `path-utils.ts` and `install-browser-service.ts`" is enforced by a grep-based check in `.github/workflows/test.yml`:
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
! grep -rn --include='*.ts' --exclude-dir=infrastructure --exclude-dir=install-browser-service \
|
|
109
|
-
"from \"node:\\(fs\\|path\\|os\\)\"" src/
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Wait, `--exclude-dir` in grep is for the directory the match is in, not the filename. The right invocation is:
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
grep -rn --include='*.ts' "from \"node:\\(fs\\|path\\|os\\)\"" src/ \
|
|
116
|
-
| grep -v "src/infrastructure/path-utils.ts" \
|
|
117
|
-
| grep -v "src/services/install-browser-service.ts" \
|
|
118
|
-
| (! grep .)
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
The check is a hard failure: if any forbidden import is found, the test job exits non-zero. The exclusion list is `path-utils.ts` (the canonical home) and `install-browser-service.ts` (the only legitimate cross-platform `node:path` consumer, for the WSL mount parser). `tests/` is not in scope of the check (the rule is about `src/` only).
|
|
122
|
-
|
|
123
|
-
**Alternative considered:** ESLint plugin with `no-restricted-imports`. Rejected: adds a new dev dependency and a config file; the grep check is one line and requires no additional tooling.
|
|
124
|
-
|
|
125
|
-
### D6. Three separate workflow files (test.yml, build.yml, release.yml) instead of one combined workflow
|
|
126
|
-
|
|
127
|
-
The Python reference (`docs/python-release-for-reference.md:1-175`) uses a single `Release` workflow with all jobs inside. We will split into three because:
|
|
128
|
-
|
|
129
|
-
1. **`test.yml` runs on every PR** — it must be fast and not blocked by the slower build matrix.
|
|
130
|
-
2. **`build.yml` runs on tag push + manual dispatch** — it has a matrix that takes 5-10 minutes.
|
|
131
|
-
3. **`release.yml` is the only one that creates the GitHub Release** — splitting it means a re-publish can be done by re-running just `release.yml` after fixing a release-note mistake.
|
|
132
|
-
|
|
133
|
-
This is the standard GitHub Actions pattern for "test on PR, build on tag, release on tag" and is well-supported by `actions/upload-artifact@v4` (artifacts persist 90 days by default, which is enough for `release.yml` to download them).
|
|
134
|
-
|
|
135
|
-
### D7. WSL is a separate build job, not a runtime check
|
|
136
|
-
|
|
137
|
-
WSL needs its own `build-wsl` job that runs on `ubuntu-latest` and produces a Linux x64 binary. WSL itself can run any Linux ELF binary, so a regular Linux build is sufficient — but having a dedicated `build-wsl` job in the matrix makes the artifact explicit and named consistently in the GitHub Actions UI.
|
|
138
|
-
|
|
139
|
-
### D8. Artifact naming preserves v1.4.1 convention
|
|
140
|
-
|
|
141
|
-
| Platform | Local build path | CI artifact name | Release asset name |
|
|
142
|
-
|----------|------------------|------------------|--------------------|
|
|
143
|
-
| Linux x64 | `dist/gemiterm` | `GemiTerm` | `GemiTerm` |
|
|
144
|
-
| Windows x64 | `dist/gemiterm.exe` | `GemiTerm.exe` | `GemiTerm.exe` |
|
|
145
|
-
| WSL (Linux x64) | `dist/gemiterm` | `GemiTerm` | (not released — only Linux) |
|
|
146
|
-
|
|
147
|
-
The v1.4.1 `install.ps1` looks for `GemiTerm.exe` in the release assets.
|
|
148
|
-
|
|
149
|
-
### D9. `bunx @playwright/cli install chromium` is the canonical install command
|
|
150
|
-
|
|
151
|
-
The existing `src/services/install-browser-service.ts:183` already spawns `bunx @playwright/cli install chromium`. The new user-facing `scripts/install-browser.sh` and `scripts/install-browser.ps1` will use the exact same command.
|
|
152
|
-
|
|
153
|
-
### D10. The `src/commands/` placeholder is removed
|
|
154
|
-
|
|
155
|
-
The directory contains only a `.gitkeep`. Decision: remove both the directory and the `.gitkeep`. No code references it. The CLI commands live in `src/cli/commands/`.
|
|
156
|
-
|
|
157
|
-
## Risks / Trade-offs
|
|
158
|
-
|
|
159
|
-
- **Risk:** `--outfile` was added in Bun 1.3.x; older Bun versions (1.2.x) don't have it. → **Mitigation:** `devDependencies.@types/bun: ^1.3.14` already pins us to 1.3.x; CI uses `oven-sh/setup-bun@v2` with `bun-version: 1.3.13` explicitly.
|
|
160
|
-
|
|
161
|
-
- **Risk:** Cross-compile from Linux to Windows requires a Bun-built-for-Windows toolchain at compile time. → **Mitigation:** the `build-windows` job runs on `windows-latest`. We never cross-compile Windows FROM Linux.
|
|
162
|
-
|
|
163
|
-
- **Risk:** The `isWSL()` is broader than the old private `isWsl()`. If any test or code path assumed the old narrower check, behavior could change. → **Mitigation:** the WSL branch in `findLinuxBrowser` only adds extra candidate paths; it never removes any. Worst case, more browsers are detected. The 5 unit tests in `tests/services/install-browser-service.test.ts` all pass against the new code.
|
|
164
|
-
|
|
165
|
-
- **Risk:** The CI lint check uses `grep` rather than a proper linter, so it can be fooled by multi-line imports or import-string concatenation. → **Mitigation:** the rule is enforced on `src/`; all current `src/` files use single-line `import … from "node:fs"` statements. Future authors who try to obfuscate the import will fail code review.
|
|
166
|
-
|
|
167
|
-
- **Risk:** A new contributor legitimately needs `node:fs` for a new feature in `src/`. → **Mitigation:** the right answer is to add a new helper to `io.ts` and use that. If the helper is genuinely impossible to write in `io.ts` (e.g. it requires direct `fs.watch` access), the contributor edits the `.github/workflows/test.yml` exclusion list with a comment explaining why.
|
|
168
|
-
|
|
169
|
-
- **Risk:** `safeReadTextFile` returns `""` on any error (preserves the existing `readFileSafe` behavior in `install-browser-service.ts:173-179`). An empty return confuses "file does not exist" with "file exists but is empty". → **Mitigation:** the only current caller (`isWsl()` check on `/proc/version`) is robust to both `""` and `null`; the result is used in a `.toLowerCase().includes("microsoft")` check that returns false on `""`. A code comment in `io.ts` notes the `""` semantic and its implication for future callers.
|
|
170
|
-
|
|
171
|
-
- **Risk:** The `io.ts` module grows over time and becomes its own God-module. → **Mitigation:** the module is already 10 functions focused on a single concern (file-system access). New functions should be added only if they are used in at least 2 call sites; ad-hoc single-use helpers should stay in the call site.
|
|
172
|
-
|
|
173
|
-
- **Risk:** `softprops/action-gh-release@v2` is a third-party action. If it breaks, the release pipeline fails. → **Mitigation:** pin to a specific minor version (`@v2.3.x`); the Python reference already uses this action.
|
|
174
|
-
|
|
175
|
-
- **Risk:** GitHub Actions matrix can fail intermittently. → **Mitigation:** no retry for v1.4.1; we match that. The maintainer re-runs on flake.
|
|
176
|
-
|
|
177
|
-
- **Risk:** Removing `commander` from `package.json` and `bun.lock` requires `bun install` to regenerate the lockfile. → **Mitigation:** the verification task includes running `bun install --frozen-lockfile` to detect drift.
|
|
178
|
-
|
|
179
|
-
- **Risk:** The orphan `{` file has been there long enough to be a known-good. Removing it could break a downstream tool that `cat`s the repo. → **Mitigation:** search the repo for any reference to a file named `{` (none found) before deletion. The file is untracked, so deletion is a no-op for git history.
|
|
180
|
-
|
|
181
|
-
- **Trade-off:** Splitting CI into three workflows means the `release.yml` job has to re-run `test` and `build` jobs because of the `needs:` chain. → **Accepted:** with three workflows, `test.yml` and `build.yml` cache their results via `actions/upload-artifact@v4`; `release.yml` only re-runs the release step on manual re-run.
|
|
182
|
-
|
|
183
|
-
- **Trade-off:** A separate `build-wsl` job doubles the matrix time vs. reusing `build-linux`. → **Accepted:** matrix runs in parallel; total wall-clock time is `max(linux, windows, wsl) ≈ 5 minutes` either way.
|
|
184
|
-
|
|
185
|
-
## Migration Plan
|
|
186
|
-
|
|
187
|
-
The change is purely additive plus one CLI build fix and a refactor of 7 internal call sites. There is no user-visible migration.
|
|
188
|
-
|
|
189
|
-
**Deploy steps** (all done in this PR):
|
|
190
|
-
|
|
191
|
-
1. Land the `path-utils.ts` extensions + `io.ts` module + refactor of the 7 call sites. Run `bun test` — must stay at 432/432 plus the new tests.
|
|
192
|
-
2. Fix `package.json:20-22` build scripts. Run `bun run build` and `bun run build:linux` locally — both must produce an executable.
|
|
193
|
-
3. Add the shell scripts (`scripts/clean-build.{sh,ps1}`, `scripts/install-browser.{sh,ps1}`).
|
|
194
|
-
4. Add the three GitHub Actions workflows, including the lint check in `test.yml`.
|
|
195
|
-
5. Cleanup: `.gitignore:23` (remove stray `{`), repo root (remove orphan `{`), `package.json:29` and `bun.lock` (remove `commander`), `src/commands/` (remove placeholder).
|
|
196
|
-
6. Update README with "Building from source" + "Release artifacts" sections.
|
|
197
|
-
7. After merge, push a `v2.0.0` tag. The `build.yml` matrix produces Linux + Windows + WSL binaries. The `release.yml` job attaches them to a GitHub Release with auto-generated notes.
|
|
198
|
-
|
|
199
|
-
**Rollback strategy:**
|
|
200
|
-
|
|
201
|
-
- The CLI build fix is not yet shipped, so there's nothing to roll back for end users.
|
|
202
|
-
- The workflows only fire on tag push; if a `v2.0.0` tag is pushed and the release breaks, the maintainer can delete the GitHub Release or re-tag with a patch version.
|
|
203
|
-
- The `install-browser-service` refactor has the 5 unit tests as a safety net; if any test fails post-refactor, revert the change to that file only.
|
|
204
|
-
- The `io.ts` additions are additive; if they cause a circular import (verified not the case by inspection), revert by removing the import from the affected call site.
|
|
205
|
-
|
|
206
|
-
## Open Questions
|
|
207
|
-
|
|
208
|
-
- **Q1:** Should `safeReadTextFile` return `""` (preserves the old `readFileSafe` behavior) or `null` (more honest API)? **Decision:** keep `""` for minimum diff and add a code comment noting the implication. Future contributors can add a `safeReadTextFileOrNull` if needed.
|
|
209
|
-
- **Q2:** Should the WSL build be a separate artifact in the GitHub Release, or is it identical to the Linux artifact? **Decision:** WSL is a CI-only check; the artifact is not released. If users complain, add it later.
|
|
210
|
-
- **Q3:** Should the `install-browser.sh` and `install-browser.ps1` scripts also be uploaded to the GitHub Release? **Decision:** yes, attach both, but the v1.4.1 `install.ps1` will be the one referenced by `v2-install-migration`.
|
|
211
|
-
- **Q4:** Should the `test.yml` workflow also run on `windows-latest`? **Decision:** start with `ubuntu-latest` only; expand later if a Windows-only bug appears.
|
|
212
|
-
- **Q5:** What is the minimum Bun version for the `build` scripts to work? **Decision:** Bun 1.3.13. Document in README.
|
|
213
|
-
- **Q6:** Should the shared `isWSL()` also read `/proc/sys/kernel/osrelease` as a fallback? **Decision:** stick with `/proc/version` + `WSL_DISTRO_NAME`.
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
## Why
|
|
2
|
-
|
|
3
|
-
The Bun rewrite is functionally complete (432/432 tests pass, all 11 commands registered, auth flow working), but the **delivery pipeline is missing**: `bun run build` is broken in Bun 1.3.x (`--compile` no longer accepts `--outdir`), there is no CI to catch regressions on PRs, no cross-platform build matrix, no GitHub release automation, and the rewrite's `src/` is full of direct `node:fs` and `node:path` imports that duplicate the same patterns in 4–8 places each (the "resolve + dirname + mkdirSync + writeFileSync" block alone appears in 4 export commands; `mkdirSync`/`existsSync`/`renameSync` are open-coded in `storage.ts` and `config.ts`).
|
|
4
|
-
|
|
5
|
-
Two findings drive the change:
|
|
6
|
-
|
|
7
|
-
1. **The build/CI/release work** — fixing `bun run build`, adding `test.yml` / `build.yml` / `release.yml` workflows, and the repo-hygiene cleanup — is the original scope and stands as-is.
|
|
8
|
-
2. **Path and file-system access is duplicated across the rewrite** and is the actual cross-platform work. A read-only audit of `src/` finds **9 files** importing `node:fs` and `node:path` directly, with at least 4 copy-pasted "write to disk" blocks and ~20 open-coded `mkdirSync` / `existsSync` / `renameSync` calls. The original spec proposed a `platform-detect.ts` module to share one private `isWsl()` helper, but the duplication is much wider: the rewrite does not need a platform-detection module — it needs **mandatory mediation** of all file-system and path operations through `src/infrastructure/path-utils.ts` and a new `src/infrastructure/io.ts`.
|
|
9
|
-
|
|
10
|
-
This change makes that mediation the rule, with a CI lint check that fails the build if any new `node:fs`/`node:path`/`node:os` import is added to `src/` outside the two allowed modules.
|
|
11
|
-
|
|
12
|
-
## What Changes
|
|
13
|
-
|
|
14
|
-
- **Fix `package.json` build scripts** so `bun run build`, `bun run build:linux`, and `bun run build:windows` produce a working single-file executable on Bun 1.3.x (drop the `--outdir` flag from `--compile` invocations; use `--outfile`).
|
|
15
|
-
- **Add `src/infrastructure/path-utils.ts` extensions**: `isWSL()`, `getProjectRoot()`, and `getPackageJson()`. The single private `isWsl()` in `install-browser-service.ts` is lifted into `path-utils.ts` (the only platform-detection helper we actually need; the rest of the proposed `platform-detect.ts` module is dropped).
|
|
16
|
-
- **Add `src/infrastructure/io.ts`**: a new sibling module that consolidates every `node:fs` and `node:path` operation the rewrite actually uses. Exports: `ensureDir`, `existsFile`, `readTextFile`, `safeReadTextFile`, `writeTextFile`, `readJsonFile`, `writeJsonFile`, `removeDir`, `renameDir`, `isDirectory`, `listSubdirectories`. This is the **canonical home for all file-system access**; the rule is "no other file in `src/` imports from `node:fs` or `node:path` directly" (the only allowed exception is `install-browser-service.ts`, which keeps `node:path` for the WSL mount parser).
|
|
17
|
-
- **Refactor `src/services/install-browser-service.ts`** to consume `isWSL()`, `safeReadTextFile`, `existsFile` from the new modules. The two near-identical `getEdgePaths` / `getChromePaths` methods are unified behind a `getWindowsKnownDirs()` local helper. The 5 existing unit tests in `tests/services/install-browser-service.test.ts` are the regression gate; the 432/432 baseline MUST be preserved.
|
|
18
|
-
- **Refactor `src/infrastructure/storage.ts` and `src/infrastructure/config.ts`** to route all file-system access through `io.ts`. `config.ts` shrinks from 64 lines to ~40; `storage.ts` loses all `node:fs` imports.
|
|
19
|
-
- **Refactor the 4 export commands** (`src/cli/commands/export-command.ts`, `export-all-command.ts`, `list-command.ts`, `fetch-command.ts`) to use `writeTextFile` and `ensureDir`. The 3-line "resolve + mkdir + write" block that appears in 4 places collapses to a single call. The two byte-identical `writeOutput` private methods in `list-command.ts` and `fetch-command.ts` each collapse to 2 lines.
|
|
20
|
-
- **Refactor `src/cli/index.ts`** to use `getPackageJson()` from `path-utils.ts` instead of the inline `fileURLToPath` / `__dirname` / `readFileSync` / `JSON.parse` dance at lines 33–35.
|
|
21
|
-
- **Add `.github/workflows/test.yml`**: trigger on `push` to `main` and on `pull_request`; single `test` job on `ubuntu-latest`; steps: `actions/checkout@v4`, `oven-sh/setup-bun@v2`, `bun install --frozen-lockfile`, `bun test`, `bun run typecheck`, and a final lint check that fails the build if any new `node:fs`/`node:path`/`node:os` import is added to `src/` outside the allowed files.
|
|
22
|
-
- **Add `.github/workflows/build.yml`**: trigger on `push` of tags matching `v*` and `workflow_dispatch`; matrix jobs `build-linux`, `build-windows`, and `build-wsl`; each verifies its artifact exists, reports size, and uploads via `actions/upload-artifact@v4`.
|
|
23
|
-
- **Add `.github/workflows/release.yml`**: trigger on `push` of tags matching `v*`; chains `test → build → release`; release job uses `softprops/action-gh-release@v2` with auto-generated notes and attaches `GemiTerm`, `GemiTerm.exe`, `install.sh`, and `install.ps1`.
|
|
24
|
-
- **Add `scripts/clean-build.sh`** (POSIX) and **`scripts/clean-build.ps1`** (PowerShell) that remove `dist/` and any temp build artifacts.
|
|
25
|
-
- **Add `scripts/install-browser.sh`** and **`scripts/install-browser.ps1`** as user-facing shell wrappers that run `bunx @playwright/cli install chromium` and verify the install.
|
|
26
|
-
- **Cleanup**: remove the unused `commander` dependency from `package.json` and `bun.lock` (it is installed but never imported). Remove the stray bare `{` line from `.gitignore` and the orphan file literally named `{` at the repo root. Remove the empty `src/commands/` placeholder (contains only `.gitkeep`).
|
|
27
|
-
- **README updates**: add a "Building from source" section and a "Release artifacts" section; update the "Development" section to mention the new `bun run typecheck` and the `scripts/install-browser.{sh,ps1}` wrappers.
|
|
28
|
-
|
|
29
|
-
**No breaking changes** to the CLI user surface. The 4 export commands keep their existing flags and behavior; the rewrite is purely internal refactoring plus build/CI.
|
|
30
|
-
|
|
31
|
-
## Capabilities
|
|
32
|
-
|
|
33
|
-
### New Capabilities
|
|
34
|
-
|
|
35
|
-
- `release-pipeline`: GitHub Actions workflows (test, build, release), the build/clean scripts, the install-browser shell wrappers, and the repo-hygiene cleanup. Creates `openspec/changes/cross-platform-build-and-ci/specs/release-pipeline/spec.md`.
|
|
36
|
-
- `path-and-file-mediation`: A mandatory mediation layer that makes `src/infrastructure/path-utils.ts` and the new `src/infrastructure/io.ts` the only consumers of `node:fs`, `node:path`, and `node:os` in `src/`. Adds `isWSL()`, `getProjectRoot()`, and `getPackageJson()` to `path-utils.ts`; adds the file-ops helpers to the new `io.ts`. Refactors all 7 existing call sites to use the new helpers. Adds a CI lint check that enforces the rule on every PR. Creates `openspec/changes/cross-platform-build-and-ci/specs/path-and-file-mediation/spec.md`.
|
|
37
|
-
|
|
38
|
-
### Modified Capabilities
|
|
39
|
-
|
|
40
|
-
- (none) — no existing capability has a spec-level requirement change. The `install-browser-service` refactor is an implementation detail covered by the regression test gate.
|
|
41
|
-
|
|
42
|
-
## Impact
|
|
43
|
-
|
|
44
|
-
- **Code:** `src/infrastructure/path-utils.ts` (add 3 helpers), new `src/infrastructure/io.ts` (~120 lines), `src/services/install-browser-service.ts` (delegate to shared module — no behavior change), `src/infrastructure/storage.ts` (lose 6 `node:fs` imports, shrink to use `io.ts` helpers), `src/infrastructure/config.ts` (shrink from 64 to ~40 lines), `src/cli/commands/{export,export-all,list,fetch}-command.ts` (lose `node:fs`/`node:path` imports, replace 3-line write block with `writeTextFile`), `src/cli/index.ts` (lose `node:url`/`node:path`/`node:fs` imports).
|
|
45
|
-
- **Build:** `package.json:20-22` (3 `build*` scripts) and `bun.lock` (drop `commander`).
|
|
46
|
-
- **Tests:** Add ~15–20 tests to `tests/unit/path-utils.test.ts` for the new path-utils helpers. Add `tests/infrastructure/io.test.ts` with ~15 tests for the new `io.ts` helpers. The existing 5 `install-browser-service.test.ts` cases are the regression gate for the `install-browser-service` refactor. CI must pass with `bun test` (currently 432/432) and `bun run typecheck` (currently 0 errors).
|
|
47
|
-
- **Repo hygiene:** `.gitignore:23` (remove stray `{`) and the untracked file named `{` at the repo root. Delete empty `src/commands/` (only `.gitkeep` inside).
|
|
48
|
-
- **README:** new "Building from source" and "Release artifacts" sections; update "Development" section.
|
|
49
|
-
- **Docs dependency:** the related `v2-install-migration` change will reference the release artifacts and the v2.0.0 install scripts.
|
|
50
|
-
- **SENSITIVE AREA:** the `install-browser-service` refactor MUST be behavior-preserving. The `playwright-cli-driver` and auth flow files are NOT touched by this change. The CI lint check is the enforcement mechanism for the "no direct `node:fs`" rule; existing code is migrated by hand as part of this change.
|
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
## Purpose
|
|
2
|
-
|
|
3
|
-
This spec defines the **path-and-file mediation layer** for the GemiTerm Bun rewrite. Its success criterion is:
|
|
4
|
-
|
|
5
|
-
> **No file in `src/` (other than the allowed exemptions) imports from `node:fs`, `node:path`, or `node:os` directly. All file-system and path operations route through `src/infrastructure/path-utils.ts` (path values) and `src/infrastructure/io.ts` (file-system side effects).**
|
|
6
|
-
|
|
7
|
-
A CI lint check (added in `release-pipeline/spec.md` §CI Test Job) enforces this rule on every PR.
|
|
8
|
-
|
|
9
|
-
The only allowed `node:fs` / `node:path` / `node:os` consumers in `src/` are:
|
|
10
|
-
|
|
11
|
-
- `src/infrastructure/path-utils.ts` — the canonical home for all path-related code.
|
|
12
|
-
- `src/services/install-browser-service.ts` — keeps a `node:path` import for the WSL `9p`/`drvfs` mount parser, which is inherently Unix-specific and not a general-purpose cross-platform concern.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## ADDED Requirements
|
|
17
|
-
|
|
18
|
-
### Requirement: `path-utils.ts` is the only path value source in `src/`
|
|
19
|
-
|
|
20
|
-
The system MUST provide `src/infrastructure/path-utils.ts` as the canonical home for all path-related functions and constants. No other source file in `src/` MAY import from `node:path` or `node:os` directly. The only allowed `node:path` consumer in `src/` is `src/services/install-browser-service.ts`, which uses `join` and `sep` for the WSL mount parser.
|
|
21
|
-
|
|
22
|
-
The `path-utils.ts` module MUST export the existing 9 symbols (the 6 path functions: `resolvePath`, `getConfigDir`, `getProfilesDir`, `getProfilePath`, `getProfileDir`, `getDefaultProfileMarkerPath`; plus 3 constants: `STORAGE_STATE_FILE`, `PROFILES_DIR`, `DEFAULT_PROFILE_MARKER`).
|
|
23
|
-
|
|
24
|
-
#### Scenario: A new src/ file wants to build a path
|
|
25
|
-
|
|
26
|
-
- **WHEN** a developer needs to build a path inside `src/`
|
|
27
|
-
- **THEN** they MUST use one of the exported functions from `path-utils.ts` (e.g. `join(getProfileDir(name), "subdir")` is fine, but `join` from `node:path` is not)
|
|
28
|
-
|
|
29
|
-
#### Scenario: An existing src/ file is audited
|
|
30
|
-
|
|
31
|
-
- **WHEN** the CI lint check runs over `src/`
|
|
32
|
-
- **THEN** the only matches for `from "node:path"` or `from "node:os"` are `src/infrastructure/path-utils.ts` and `src/services/install-browser-service.ts`
|
|
33
|
-
|
|
34
|
-
### Requirement: `io.ts` is the only file-system operation source in `src/`
|
|
35
|
-
|
|
36
|
-
The system MUST provide `src/infrastructure/io.ts` as the canonical home for all file-system operations. No other source file in `src/` MAY import from `node:fs` directly. The CI lint check enforces this rule.
|
|
37
|
-
|
|
38
|
-
The `io.ts` module MUST export the following 11 functions plus one error class: `ensureDir`, `existsFile`, `readTextFile`, `safeReadTextFile`, `writeTextFile`, `readJsonFile`, `writeJsonFile`, `removeDir`, `renameDir`, `isDirectory`, `listSubdirectories`, and `IOError`.
|
|
39
|
-
|
|
40
|
-
#### Scenario: A new src/ file wants to read a file
|
|
41
|
-
|
|
42
|
-
- **WHEN** a developer needs to read a file inside `src/`
|
|
43
|
-
- **THEN** they MUST use `readTextFile` or `readJsonFile` from `io.ts`, never `readFileSync` from `node:fs`
|
|
44
|
-
|
|
45
|
-
#### Scenario: A new src/ file wants to write a file
|
|
46
|
-
|
|
47
|
-
- **WHEN** a developer needs to write a file inside `src/`
|
|
48
|
-
- **THEN** they MUST use `writeTextFile` or `writeJsonFile` from `io.ts`, which creates the parent directory if it does not exist, never `writeFileSync` from `node:fs`
|
|
49
|
-
|
|
50
|
-
#### Scenario: A new src/ file wants to check file existence
|
|
51
|
-
|
|
52
|
-
- **WHEN** a developer needs to check whether a file or directory exists
|
|
53
|
-
- **THEN** they MUST use `existsFile` from `io.ts`, never `existsSync` from `node:fs`
|
|
54
|
-
|
|
55
|
-
### Requirement: `isWSL()` detects WSL via `/proc/version` or `WSL_DISTRO_NAME`
|
|
56
|
-
|
|
57
|
-
The system MUST provide `isWSL(): boolean` in `src/infrastructure/path-utils.ts`. The function MUST return `false` on any platform other than Linux. On Linux, it MUST return `true` if either:
|
|
58
|
-
|
|
59
|
-
- `/proc/version` exists and contains the substring `microsoft` or `WSL` (case-insensitive), OR
|
|
60
|
-
- the `WSL_DISTRO_NAME` environment variable is set and non-empty.
|
|
61
|
-
|
|
62
|
-
If neither condition holds, the function MUST return `false`.
|
|
63
|
-
|
|
64
|
-
#### Scenario: WSL detected via `/proc/version`
|
|
65
|
-
|
|
66
|
-
- **WHEN** `process.platform === "linux"` and `/proc/version` contains the substring `microsoft`
|
|
67
|
-
- **THEN** `isWSL()` returns `true`
|
|
68
|
-
|
|
69
|
-
#### Scenario: WSL detected via `WSL_DISTRO_NAME`
|
|
70
|
-
|
|
71
|
-
- **WHEN** `process.platform === "linux"` and `WSL_DISTRO_NAME` is set to any non-empty string
|
|
72
|
-
- **THEN** `isWSL()` returns `true` even if `/proc/version` does not contain the `microsoft` substring
|
|
73
|
-
|
|
74
|
-
#### Scenario: Native Linux returns false
|
|
75
|
-
|
|
76
|
-
- **WHEN** `process.platform === "linux"` and neither `/proc/version` contains `microsoft` or `WSL` nor `WSL_DISTRO_NAME` is set
|
|
77
|
-
- **THEN** `isWSL()` returns `false`
|
|
78
|
-
|
|
79
|
-
#### Scenario: macOS or Windows returns false
|
|
80
|
-
|
|
81
|
-
- **WHEN** `process.platform` is `"darwin"` or `"win32"`
|
|
82
|
-
- **THEN** `isWSL()` returns `false` regardless of `/proc/version` or environment variable contents
|
|
83
|
-
|
|
84
|
-
### Requirement: `getProjectRoot()` finds the repository root from `import.meta.url`
|
|
85
|
-
|
|
86
|
-
The system MUST provide `getProjectRoot(importMetaUrl?: string): string` in `src/infrastructure/path-utils.ts`. The function MUST walk up from the caller (or the provided `import.meta.url`) until it finds a directory containing `package.json`, then return the absolute path. The function MUST throw `IOError` if no `package.json` is found in any parent directory.
|
|
87
|
-
|
|
88
|
-
#### Scenario: Called from a file in the repo
|
|
89
|
-
|
|
90
|
-
- **WHEN** `getProjectRoot()` is called from any file under the repository root
|
|
91
|
-
- **THEN** it returns the absolute path of the repository root (the directory containing `package.json`)
|
|
92
|
-
|
|
93
|
-
#### Scenario: Called with no `package.json` ancestors
|
|
94
|
-
|
|
95
|
-
- **WHEN** `getProjectRoot()` is called from a file that has no `package.json` in any parent directory
|
|
96
|
-
- **THEN** it throws an `IOError` with a message indicating the walk completed without finding `package.json`
|
|
97
|
-
|
|
98
|
-
### Requirement: `getPackageJson()` reads and parses `package.json`
|
|
99
|
-
|
|
100
|
-
The system MUST provide `getPackageJson(): { name: string; version: string; [key: string]: unknown }` in `src/infrastructure/path-utils.ts`. The function MUST call `getProjectRoot(import.meta.url)`, read `<root>/package.json` as UTF-8, parse it as JSON, and return the parsed object. On any error (file missing, file unparseable, no `package.json` ancestor), the function MUST return `{ name: "gemiterm", version: "unknown" }` as a fallback.
|
|
101
|
-
|
|
102
|
-
#### Scenario: Normal call
|
|
103
|
-
|
|
104
|
-
- **WHEN** `getPackageJson()` is called from a file under the repository root
|
|
105
|
-
- **THEN** it returns the parsed `package.json` object, with `name === "gemiterm"` and `version === "2.0.0"`
|
|
106
|
-
|
|
107
|
-
#### Scenario: `package.json` missing or unparseable
|
|
108
|
-
|
|
109
|
-
- **WHEN** the resolved `package.json` does not exist or is unparseable JSON
|
|
110
|
-
- **THEN** `getPackageJson()` returns the fallback object `{ name: "gemiterm", version: "unknown" }`
|
|
111
|
-
|
|
112
|
-
### Requirement: `writeTextFile` consolidates the 4-copy "resolve + mkdir + write" block
|
|
113
|
-
|
|
114
|
-
The system MUST provide `writeTextFile(path: string, content: string): void` in `src/infrastructure/io.ts`. The function MUST resolve `path` to an absolute path, ensure the parent directory exists (recursively), and write `content` as UTF-8. The function MUST throw `IOError` on EACCES, ENOSPC, or any other write error.
|
|
115
|
-
|
|
116
|
-
The 4 CLI commands `src/cli/commands/export-command.ts`, `export-all-command.ts`, `list-command.ts`, and `fetch-command.ts` MUST use `writeTextFile` in place of their existing `resolve + dirname + mkdirSync + writeFileSync` sequences.
|
|
117
|
-
|
|
118
|
-
#### Scenario: Writing a new file to an existing directory
|
|
119
|
-
|
|
120
|
-
- **WHEN** `writeTextFile("/tmp/gemiterm-test/output.md", "# Hello")` is called and `/tmp/gemiterm-test` exists
|
|
121
|
-
- **THEN** the file `/tmp/gemiterm-test/output.md` is created with the content `# Hello`
|
|
122
|
-
|
|
123
|
-
#### Scenario: Writing a new file to a nested non-existent directory
|
|
124
|
-
|
|
125
|
-
- **WHEN** `writeTextFile("/tmp/gemiterm-test/nested/dir/output.md", "# Hello")` is called and neither `/tmp/gemiterm-test/nested` nor `/tmp/gemiterm-test/nested/dir` exist
|
|
126
|
-
- **THEN** both directories are created recursively, and the file is written
|
|
127
|
-
|
|
128
|
-
### Requirement: `readJsonFile` and `writeJsonFile` provide typed JSON I/O
|
|
129
|
-
|
|
130
|
-
The system MUST provide `readJsonFile<T>(path: string): T` and `writeJsonFile(path: string, data: unknown): void` in `src/infrastructure/io.ts`. `readJsonFile` MUST read the file as UTF-8, parse it as JSON, and cast the result to `T`. `writeJsonFile` MUST serialize `data` with `JSON.stringify(data, null, 2)` and write it via `writeTextFile`. Both functions MUST throw `IOError` (with a `cause` field for the original error) on any failure.
|
|
131
|
-
|
|
132
|
-
#### Scenario: Round-trip a JSON object
|
|
133
|
-
|
|
134
|
-
- **WHEN** a developer writes an object via `writeJsonFile(path, obj)` and reads it back via `readJsonFile<typeof obj>(path)`
|
|
135
|
-
- **THEN** the deep-equal comparison of the two values is true
|
|
136
|
-
|
|
137
|
-
#### Scenario: `readJsonFile` on a missing file
|
|
138
|
-
|
|
139
|
-
- **WHEN** `readJsonFile` is called with a path that does not exist
|
|
140
|
-
- **THEN** it throws `IOError` whose `cause` is the underlying `ENOENT` error
|
|
141
|
-
|
|
142
|
-
#### Scenario: `readJsonFile` on unparseable content
|
|
143
|
-
|
|
144
|
-
- **WHEN** `readJsonFile` is called with a path whose content is not valid JSON
|
|
145
|
-
- **THEN** it throws `IOError` whose `cause` is the underlying `SyntaxError`
|
|
146
|
-
|
|
147
|
-
### Requirement: `safeReadTextFile` returns `""` on any error
|
|
148
|
-
|
|
149
|
-
The system MUST provide `safeReadTextFile(path: string): string` in `src/infrastructure/io.ts`. The function MUST read the file as UTF-8 and return the content; on any error (ENOENT, EACCES, EISDIR), it MUST return the empty string `""`. The function MUST include a JSDoc comment noting that the `""` return conflates "file does not exist" with "file exists but is empty" and is appropriate for callers that only need a string for `.includes()` / `.trim()` checks.
|
|
150
|
-
|
|
151
|
-
#### Scenario: Reading an existing file
|
|
152
|
-
|
|
153
|
-
- **WHEN** `safeReadTextFile` is called with a path that exists
|
|
154
|
-
- **THEN** it returns the file's content
|
|
155
|
-
|
|
156
|
-
#### Scenario: Reading a missing file
|
|
157
|
-
|
|
158
|
-
- **WHEN** `safeReadTextFile` is called with a path that does not exist
|
|
159
|
-
- **THEN** it returns `""` and does not throw
|
|
160
|
-
|
|
161
|
-
#### Scenario: Code comment is present
|
|
162
|
-
|
|
163
|
-
- **WHEN** the source of `safeReadTextFile` is inspected
|
|
164
|
-
- **THEN** the JSDoc comment is present and explicitly notes the `""` return and its implication
|
|
165
|
-
|
|
166
|
-
### Requirement: `ensureDir`, `removeDir`, `renameDir`, `isDirectory`, `listSubdirectories` provide safe file-system access
|
|
167
|
-
|
|
168
|
-
The system MUST provide the following 5 functions in `src/infrastructure/io.ts`:
|
|
169
|
-
|
|
170
|
-
- `ensureDir(path: string): void` — calls `mkdirSync(path, { recursive: true })`. Throws `IOError` on EACCES, EPERM, EROFS, or any other error.
|
|
171
|
-
- `removeDir(path: string): void` — calls `rmSync(path, { recursive: true, force: true })`. The `force: true` flag means the function is a no-op if the path does not exist. Throws `IOError` on EACCES, EBUSY, or any other error.
|
|
172
|
-
- `renameDir(src: string, dest: string): void` — calls `renameSync`. Throws `IOError` with a clear message on ENOENT (source missing) or EEXIST (destination present).
|
|
173
|
-
- `isDirectory(path: string): boolean` — returns `true` if `statSync(path).isDirectory()` succeeds and the result is `true`; returns `false` on any error (including ENOENT).
|
|
174
|
-
- `listSubdirectories(path: string): string[]` — reads the directory and returns the names of immediate subdirectories only. Returns `[]` if the path does not exist. The `.default` marker file (or any other non-directory entry) is excluded.
|
|
175
|
-
|
|
176
|
-
#### Scenario: `ensureDir` creates nested directories
|
|
177
|
-
|
|
178
|
-
- **WHEN** `ensureDir("/tmp/a/b/c")` is called and `/tmp/a` does not exist
|
|
179
|
-
- **THEN** `/tmp/a`, `/tmp/a/b`, and `/tmp/a/b/c` are all created
|
|
180
|
-
|
|
181
|
-
#### Scenario: `removeDir` is a no-op on missing path
|
|
182
|
-
|
|
183
|
-
- **WHEN** `removeDir("/tmp/nonexistent")` is called
|
|
184
|
-
- **THEN** no error is thrown and the function returns
|
|
185
|
-
|
|
186
|
-
#### Scenario: `renameDir` throws on missing source
|
|
187
|
-
|
|
188
|
-
- **WHEN** `renameDir("/tmp/nope", "/tmp/dest")` is called and `/tmp/nope` does not exist
|
|
189
|
-
- **THEN** `IOError` is thrown with a message that includes the source path
|
|
190
|
-
|
|
191
|
-
#### Scenario: `listSubdirectories` excludes the marker file
|
|
192
|
-
|
|
193
|
-
- **WHEN** `listSubdirectories` is called on a profile directory containing both subdirectories and a `.default` marker file
|
|
194
|
-
- **THEN** the returned array contains only the subdirectory names; `.default` is excluded
|
|
195
|
-
|
|
196
|
-
### Requirement: `IOError` provides structured error context
|
|
197
|
-
|
|
198
|
-
The system MUST export an `IOError` class from `src/infrastructure/io.ts` that extends `Error` and includes a `cause` field (of type `Error | undefined`) for the original error. All other functions in `io.ts` MUST throw `IOError` (with a populated `cause` when the original error is available) rather than raw `Error` or re-throwing the original error.
|
|
199
|
-
|
|
200
|
-
#### Scenario: `IOError` carries a cause
|
|
201
|
-
|
|
202
|
-
- **WHEN** a function in `io.ts` catches an `ENOENT` error from `node:fs` and re-throws as `IOError`
|
|
203
|
-
- **THEN** the `IOError.cause` field is the original `ENOENT` error
|
|
204
|
-
|
|
205
|
-
#### Scenario: Error name is set
|
|
206
|
-
|
|
207
|
-
- **WHEN** an `IOError` is constructed
|
|
208
|
-
- **THEN** its `name` field is `"IOError"` (so `error.name === "IOError"` is true)
|
|
209
|
-
|
|
210
|
-
### Requirement: `install-browser-service` is the only WSL-detection consumer
|
|
211
|
-
|
|
212
|
-
The system MUST refactor `src/services/install-browser-service.ts` to:
|
|
213
|
-
|
|
214
|
-
- import `isWSL` from `path-utils.ts` and delete the private `isWsl()` method.
|
|
215
|
-
- import `existsFile` and `safeReadTextFile` from `io.ts` and delete the private `readFileSafe` helper.
|
|
216
|
-
- unify `getEdgePaths()` and `getChromePaths()` behind a single local `getWindowsKnownDirs()` helper.
|
|
217
|
-
|
|
218
|
-
The 5 unit tests in `tests/services/install-browser-service.test.ts` MUST continue to pass without modification; the 432/432 baseline MUST be maintained.
|
|
219
|
-
|
|
220
|
-
#### Scenario: `isWsl()` private method is gone
|
|
221
|
-
|
|
222
|
-
- **WHEN** `src/services/install-browser-service.ts` is inspected
|
|
223
|
-
- **THEN** the file does not define a private `isWsl()` method
|
|
224
|
-
|
|
225
|
-
#### Scenario: `isWSL()` is the only WSL detection source
|
|
226
|
-
|
|
227
|
-
- **WHEN** the rewrite needs to check whether it is running under WSL
|
|
228
|
-
- **THEN** the check is made via the shared `isWSL()` from `path-utils.ts`, not via inline `/proc/version` reads
|
|
229
|
-
|
|
230
|
-
#### Scenario: `getEdgePaths` and `getChromePaths` are unified
|
|
231
|
-
|
|
232
|
-
- **WHEN** `src/services/install-browser-service.ts` is inspected
|
|
233
|
-
- **THEN** both methods are short (≤5 lines each) and delegate path construction to a shared `getWindowsKnownDirs()` helper
|
|
234
|
-
|
|
235
|
-
### Requirement: `storage.ts` and `config.ts` route all file access through `io.ts`
|
|
236
|
-
|
|
237
|
-
The system MUST refactor `src/infrastructure/storage.ts` and `src/infrastructure/config.ts` to import from `./io.ts` instead of `node:fs`. After the refactor, neither file MUST import from `node:fs` or `node:path` directly. `config.ts` MUST shrink from 64 lines to approximately 40 lines.
|
|
238
|
-
|
|
239
|
-
The 21 unit tests in `tests/infrastructure/storage.test.ts` and the 13 unit tests in `tests/unit/config.test.ts` MUST continue to pass without modification.
|
|
240
|
-
|
|
241
|
-
#### Scenario: `storage.ts` has no `node:fs` import
|
|
242
|
-
|
|
243
|
-
- **WHEN** `src/infrastructure/storage.ts` is inspected
|
|
244
|
-
- **THEN** the file does not contain `from "node:fs"`
|
|
245
|
-
|
|
246
|
-
#### Scenario: `config.ts` has no `node:fs` or `node:path` import
|
|
247
|
-
|
|
248
|
-
- **WHEN** `src/infrastructure/config.ts` is inspected
|
|
249
|
-
- **THEN** the file does not contain `from "node:fs"` or `from "node:path"`
|
|
250
|
-
|
|
251
|
-
#### Scenario: `storage.ts` tests still pass
|
|
252
|
-
|
|
253
|
-
- **WHEN** `bun test tests/infrastructure/storage.test.ts` is run after the refactor
|
|
254
|
-
- **THEN** all 21 tests pass
|
|
255
|
-
|
|
256
|
-
#### Scenario: `config.ts` tests still pass
|
|
257
|
-
|
|
258
|
-
- **WHEN** `bun test tests/unit/config.test.ts` is run after the refactor
|
|
259
|
-
- **THEN** all 13 tests pass
|
|
260
|
-
|
|
261
|
-
### Requirement: 4 export commands use `writeTextFile`
|
|
262
|
-
|
|
263
|
-
The system MUST refactor `src/cli/commands/export-command.ts`, `export-all-command.ts`, `list-command.ts`, and `fetch-command.ts` so that each file uses `writeTextFile` and `ensureDir` from `io.ts` instead of inline `node:fs` operations. After the refactor, none of the 4 files MUST import from `node:fs` or `node:path` directly. The integration tests in `tests/integration/commands/{export,list,fetch}.test.ts` and `tests/cli/export-all-command.test.ts` MUST continue to pass without modification.
|
|
264
|
-
|
|
265
|
-
#### Scenario: `export-command.ts` uses `writeTextFile`
|
|
266
|
-
|
|
267
|
-
- **WHEN** `src/cli/commands/export-command.ts` is inspected
|
|
268
|
-
- **THEN** the file imports `writeTextFile` from `../../infrastructure/io.ts` and does not import from `node:fs` or `node:path`
|
|
269
|
-
|
|
270
|
-
#### Scenario: `export-all-command.ts` uses `writeTextFile` and `ensureDir`
|
|
271
|
-
|
|
272
|
-
- **WHEN** `src/cli/commands/export-all-command.ts` is inspected
|
|
273
|
-
- **THEN** the file imports `writeTextFile` and `ensureDir` from `../../infrastructure/io.ts` and does not import from `node:fs` or `node:path`
|
|
274
|
-
|
|
275
|
-
#### Scenario: `list-command.ts` and `fetch-command.ts` have a 2-line `writeOutput`
|
|
276
|
-
|
|
277
|
-
- **WHEN** `src/cli/commands/list-command.ts` and `src/cli/commands/fetch-command.ts` are inspected
|
|
278
|
-
- **THEN** the `writeOutput` private method in each is 2 lines (a `writeTextFile` call and a `console.log`)
|
|
279
|
-
|
|
280
|
-
#### Scenario: Integration tests still pass
|
|
281
|
-
|
|
282
|
-
- **WHEN** `bun test tests/integration/commands/{export,list,fetch}.test.ts tests/cli/export-all-command.test.ts` is run after the refactor
|
|
283
|
-
- **THEN** all tests pass
|
|
284
|
-
|
|
285
|
-
### Requirement: `cli/index.ts` uses `getPackageJson()`
|
|
286
|
-
|
|
287
|
-
The system MUST refactor `src/cli/index.ts` to import `getPackageJson` from `src/infrastructure/path-utils.ts` and remove the inline `fileURLToPath` / `__dirname` / `readFileSync` / `JSON.parse` sequence at lines 33-35. After the refactor, `src/cli/index.ts` MUST NOT import from `node:fs`, `node:path`, or `node:url`. The `--version` flag MUST continue to print `2.0.0`; the smoke test in `tests/smoke/smoke.test.ts` is the regression gate.
|
|
288
|
-
|
|
289
|
-
#### Scenario: `cli/index.ts` has no `node:fs` / `node:path` / `node:url` import
|
|
290
|
-
|
|
291
|
-
- **WHEN** `src/cli/index.ts` is inspected
|
|
292
|
-
- **THEN** the file does not contain `from "node:fs"`, `from "node:path"`, or `from "node:url"`
|
|
293
|
-
|
|
294
|
-
#### Scenario: `--version` still prints `2.0.0`
|
|
295
|
-
|
|
296
|
-
- **WHEN** `gemiterm --version` is run
|
|
297
|
-
- **THEN** the output includes the string `2.0.0`
|
|
298
|
-
|
|
299
|
-
### Requirement: CI lint check fails the build on a forbidden import
|
|
300
|
-
|
|
301
|
-
The system MUST add a step to `.github/workflows/test.yml` that runs the following command and fails the workflow if the command exits non-zero:
|
|
302
|
-
|
|
303
|
-
```bash
|
|
304
|
-
! grep -rn --include='*.ts' "from \"node:\\(fs\\|path\\|os\\)\"" src/ \
|
|
305
|
-
| grep -v "src/infrastructure/path-utils.ts" \
|
|
306
|
-
| grep -v "src/services/install-browser-service.ts" \
|
|
307
|
-
| grep -q .
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
The check MUST exclude `src/infrastructure/path-utils.ts` (the canonical home) and `src/services/install-browser-service.ts` (the only legitimate cross-platform `node:path` consumer). The check MUST NOT exclude any other file.
|
|
311
|
-
|
|
312
|
-
#### Scenario: A new src/ file imports from `node:fs`
|
|
313
|
-
|
|
314
|
-
- **WHEN** a contributor adds `import { readFileSync } from "node:fs";` to a new file `src/services/foo.ts` and opens a PR
|
|
315
|
-
- **THEN** the `test.yml` workflow's "Enforce path-utils mediation" step exits non-zero and the PR cannot be merged
|
|
316
|
-
|
|
317
|
-
#### Scenario: A new src/ file imports from `node:path`
|
|
318
|
-
|
|
319
|
-
- **WHEN** a contributor adds `import { join } from "node:path";` to `src/services/foo.ts`
|
|
320
|
-
- **THEN** the workflow fails with a clear error message naming the file
|
|
321
|
-
|
|
322
|
-
#### Scenario: A new src/ file imports from `node:os`
|
|
323
|
-
|
|
324
|
-
- **WHEN** a contributor adds `import { homedir } from "node:os";` to `src/services/foo.ts`
|
|
325
|
-
- **THEN** the workflow fails
|
|
326
|
-
|
|
327
|
-
#### Scenario: The two allowed files are exempt
|
|
328
|
-
|
|
329
|
-
- **WHEN** the check is run and the only matches are in `src/infrastructure/path-utils.ts` and `src/services/install-browser-service.ts`
|
|
330
|
-
- **THEN** the check exits 0 (success)
|
|
331
|
-
|
|
332
|
-
#### Scenario: Tests directory is out of scope
|
|
333
|
-
|
|
334
|
-
- **WHEN** the check is run and a test file in `tests/` imports from `node:fs`
|
|
335
|
-
- **THEN** the check is unaffected (it scans `src/` only)
|