mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
* fix(sdk): decouple SDK from build-from-source install path, close #2441 and #2453
Ship sdk/dist prebuilt in the tarball and replace the npm-install-g
sub-install with a parent-package bin shim (bin/gsd-sdk.js). npm chmods
bin entries from a packed tarball correctly, eliminating the mode-644
failure (#2453) and the full class of NPM_CONFIG_PREFIX/ignore-scripts/
corepack/air-gapped failure modes that caused #2439 and #2441.
Changes:
- sdk/package.json: prepublishOnly runs `rm -rf dist && tsc && chmod +x
dist/cli.js` (stale-build guard + execute-bit fix at publish time)
- package.json: add "gsd-sdk": "bin/gsd-sdk.js" bin entry; add sdk/dist
to files so the prebuilt CLI ships in the tarball
- bin/gsd-sdk.js: new back-compat shim — resolves sdk/dist/cli.js relative
to the package root and delegates via `node`, so all existing PATH call
sites (slash commands, agents, hooks) continue to work unchanged (S1 shim)
- bin/install.js: replace installSdkIfNeeded() build-from-source + global-
install dance with a dist-verify + chmod-in-place guard; delete
resolveGsdSdk(), detectShellRc(), emitSdkFatal() helpers now unused
- .github/workflows/install-smoke.yml: add smoke-unpacked job that strips
execute bit from sdk/dist/cli.js before install to reproduce the exact
#2453 failure mode
- tests/bug-2441-sdk-decouple.test.cjs: new regression tests asserting all
invariants (no npm install -g from sdk/, shim exists, sdk/dist in files,
prepublishOnly has rm -rf + chmod)
- tests/bugs-1656-1657.test.cjs: update stale assertions that required
build-from-source behavior (now asserts new prebuilt-dist invariants)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(release): bump to 1.38.2, wire release.yml to build SDK dist
- Bump version 1.38.1 -> 1.38.2 for the #2441/#2453 fix shipped in 0f6903d.
- Add `build:sdk` script (`cd sdk && npm ci && npm run build`).
- `prepublishOnly` now runs hooks + SDK builds as a safety net.
- release.yml (rc + finalize): build SDK dist before `npm publish` so the
published tarball always ships fresh `sdk/dist/` (kept gitignored).
- CHANGELOG: document 1.38.2 entry and `--sdk` flag semantics change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: build SDK dist before tests and smoke jobs
sdk/dist/ is gitignored (built fresh at publish time via release.yml),
but both the test suite and install-smoke jobs run `bin/install.js`
or `npm pack` against the checked-out tree where dist doesn't exist yet.
- test.yml: `npm run build:sdk` before `npm run test:coverage`, so tests
that spawn `bin/install.js` don't hit `installSdkIfNeeded()`'s fatal
missing-dist check.
- install-smoke.yml (both smoke and smoke-unpacked): build SDK before
pack/chmod so the published tarball contains dist and the unpacked
install has a file to strip exec-bit from.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sdk): lift SDK runtime deps to parent so tarball install can resolve them
The SDK's runtime deps (ws, @anthropic-ai/claude-agent-sdk) live in
sdk/package.json, but sdk/node_modules is NOT shipped in the parent
tarball — only sdk/dist, sdk/src, sdk/prompts, and sdk/package.json are.
When a user runs `npm install -g get-shit-done-cc`, npm installs the
parent's node_modules but never runs `npm install` inside the nested
sdk/ directory.
Result: `node sdk/dist/cli.js` fails with ERR_MODULE_NOT_FOUND for 'ws'.
The smoke tarball job caught this; the unpacked variant masked it
because `npm install -g <dir>` copies the entire workspace including
sdk/node_modules (left over from `npm run build:sdk`).
Fix: declare the same deps in the parent package.json so they land in
<pkg>/node_modules, which Node's resolution walks up to from
<pkg>/sdk/dist/cli.js. Keep them declared in sdk/package.json too so
the SDK remains a self-contained package for standalone dev.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(lockfile): regenerate package-lock.json cleanly
The previous `npm install` run left the lockfile internally inconsistent
(resolved esbuild@0.27.7 referenced but not fully written), causing
`npm ci` to fail in CI with "Missing from lock file" errors.
Clean regen via rm + npm install fixes all three failed jobs
(test, smoke, smoke-unpacked), which were all hitting the same
`npm ci` sync check.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(deps): remove unused esbuild + vitest from root devDependencies
Both were declared but never imported anywhere in the root package
(confirmed via grep of bin/, scripts/, tests/). They lived in sdk/
already, which is the only place they're actually used.
The transitive tree they pulled in (vitest → vite → esbuild 0.28 →
@esbuild/openharmony-arm64) was the root of the CI npm ci failures:
the openharmony platform package's `optional: true` flag was not being
applied correctly by npm 10 on Linux runners, causing EBADPLATFORM.
After removal: 800+ transitive packages → 155. Lockfile regenerated
cleanly. All 4170 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sdk): pretest:coverage builds sdk; tighten shim test assertions
Add "pretest:coverage": "npm run build:sdk" so npm run test:coverage
works in clean checkouts where sdk/dist/ hasn't been built yet.
Tighten the two loose shim assertions in bug-2441-sdk-decouple.test.cjs:
- forwards-to test now asserts path.resolve() is called with the
'sdk','dist','cli.js' path segments, not just substring presence
- node-invocation test now asserts spawnSync(process.execPath, [...])
pattern, ruling out matches in comments or the shebang line
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address PR review — pretest:coverage + tighten shim tests
Review feedback from trek-e on PR 2457:
1. pretest:coverage + pretest hooks now run `npm run build:sdk` so
`npm run test[:coverage]` in a clean checkout produces the required
sdk/dist/ artifacts before running the installer-dependent tests.
CI already does this explicitly; local contributors benefit.
2. Shim tests in bug-2441-sdk-decouple.test.cjs tightened from loose
substring matches (which would pass on comments/shebangs alone) to
regex assertions on the actual path.resolve call, spawnSync with
process.execPath, process.argv.slice(2), and process.exit pattern.
These now provide real regression protection for #2453-class bugs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: correct CHANGELOG entry and add [1.38.2] reference link
Two issues in the 1.38.2 CHANGELOG entry:
- installSdkIfNeeded() was described as deleted but it still exists in
bin/install.js (repurposed to verify sdk/dist/cli.js and fix execute bit).
Corrected the description to say 'repurposes' rather than 'deletes'.
- The reference-link block at the bottom of the file was missing a [1.38.2]
compare URL and [Unreleased] still pointed to v1.37.1...HEAD. Added the
[1.38.2] link and updated [Unreleased] to compare/v1.38.2...HEAD.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sdk): double-cast WorkflowConfig to Record for strict tsc build
TypeScript error on main (introduced in #2611) blocks `npm run build`
in sdk/, which now runs as part of this PR's tarball build path. Apply
the double-cast via `unknown` as the compiler suggests.
Same fix as #2622; can be dropped if that lands first.
* test: remove bug-2598 test obsoleted by SDK decoupling
The bug-2598 test guards the Windows CVE-2024-27980 fix in the old
build-from-source path (npm spawnSync with shell:true + formatSpawnFailure
diagnostics). This PR removes that entire code path — installSdkIfNeeded
no longer spawns npm, it just verifies the prebuilt sdk/dist/cli.js
shipped in the tarball.
The test asserts `installSdkIfNeeded.toString()` contains a
formatSpawnFailure helper. After decoupling, no such helper exists
(nothing to format — there's no spawn). Keeping the test would assert
invariants of the rejected architecture.
The original #2598 defect (silent failure of npm spawn on Windows) is
structurally impossible in the shim path: bin/gsd-sdk.js invokes
`node sdk/dist/cli.js` directly via child_process.spawn with an
explicit argv array. No .cmd wrapper, no shell delegation.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
107
.github/workflows/install-smoke.yml
vendored
107
.github/workflows/install-smoke.yml
vendored
@@ -1,10 +1,13 @@
|
||||
name: Install Smoke
|
||||
|
||||
# Exercises the real install path: `npm pack` → `npm install -g <tarball>`
|
||||
# → run `bin/install.js` → assert `gsd-sdk` is on PATH.
|
||||
# Exercises the real install paths:
|
||||
# tarball: `npm pack` → `npm install -g <tarball>` → assert gsd-sdk on PATH
|
||||
# unpacked: `npm install -g <dir>` (no pack) → assert gsd-sdk on PATH + executable
|
||||
#
|
||||
# Closes the CI gap that let #2439 ship: the rest of the suite only reads
|
||||
# `bin/install.js` as a string and never executes it.
|
||||
# The tarball path is the canonical ship path. The unpacked path reproduces the
|
||||
# mode-644 failure class (issue #2453): npm does NOT chmod bin targets when
|
||||
# installing from an unpacked local directory, so any stale tsc output lacking
|
||||
# execute bits will be caught by the unpacked job before release.
|
||||
#
|
||||
# - PRs: path-filtered, minimal runner (ubuntu + Node LTS) for fast signal.
|
||||
# - Push to release branches / main: full matrix.
|
||||
@@ -16,6 +19,7 @@ on:
|
||||
- main
|
||||
paths:
|
||||
- 'bin/install.js'
|
||||
- 'bin/gsd-sdk.js'
|
||||
- 'sdk/**'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
@@ -40,6 +44,9 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# ---------------------------------------------------------------------------
|
||||
# Job 1: tarball install (existing canonical path)
|
||||
# ---------------------------------------------------------------------------
|
||||
smoke:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 12
|
||||
@@ -90,6 +97,10 @@ jobs:
|
||||
if: steps.skip.outputs.skip != 'true'
|
||||
run: npm ci
|
||||
|
||||
- name: Build SDK dist (required in tarball — sdk/dist is gitignored)
|
||||
if: steps.skip.outputs.skip != 'true'
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Pack root tarball
|
||||
if: steps.skip.outputs.skip != 'true'
|
||||
id: pack
|
||||
@@ -109,7 +120,7 @@ jobs:
|
||||
echo "$NPM_BIN" >> "$GITHUB_PATH"
|
||||
echo "npm global bin: $NPM_BIN"
|
||||
|
||||
- name: Install tarball globally (runs bin/install.js → installSdkIfNeeded)
|
||||
- name: Install tarball globally
|
||||
if: steps.skip.outputs.skip != 'true'
|
||||
shell: bash
|
||||
env:
|
||||
@@ -121,12 +132,8 @@ jobs:
|
||||
cd "$TMPDIR_ROOT"
|
||||
npm install -g "$WORKSPACE/$TARBALL"
|
||||
command -v get-shit-done-cc
|
||||
# `--claude --local` is the non-interactive code path (see
|
||||
# install.js main block: when both a runtime and location are set,
|
||||
# installAllRuntimes runs with isInteractive=false, no prompts).
|
||||
# We tolerate non-zero here because the authoritative assertion is
|
||||
# the next step: gsd-sdk must land on PATH. Some runtime targets
|
||||
# may exit before the SDK step for unrelated reasons on CI.
|
||||
# `--claude --local` is the non-interactive code path.
|
||||
# Tolerate non-zero: the authoritative assertion is the next step.
|
||||
get-shit-done-cc --claude --local || true
|
||||
|
||||
- name: Assert gsd-sdk resolves on PATH
|
||||
@@ -135,7 +142,7 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v gsd-sdk >/dev/null 2>&1; then
|
||||
echo "::error::gsd-sdk is not on PATH after install — installSdkIfNeeded() regression"
|
||||
echo "::error::gsd-sdk is not on PATH after tarball install — shim regression"
|
||||
NPM_BIN="$(npm config get prefix)/bin"
|
||||
echo "npm global bin: $NPM_BIN"
|
||||
ls -la "$NPM_BIN" | grep -i gsd || true
|
||||
@@ -150,3 +157,79 @@ jobs:
|
||||
set -euo pipefail
|
||||
gsd-sdk --version || gsd-sdk --help
|
||||
echo "✓ gsd-sdk is executable"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Job 2: unpacked-dir install — reproduces the mode-644 failure class (#2453)
|
||||
#
|
||||
# `npm install -g <directory>` does NOT chmod bin targets when the source
|
||||
# file was produced by a build script (tsc emits 0o644). This job catches
|
||||
# regressions where sdk/dist/cli.js loses its execute bit before publish.
|
||||
# ---------------------------------------------------------------------------
|
||||
smoke-unpacked:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
|
||||
- name: Set up Node.js 22
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install root deps
|
||||
run: npm ci
|
||||
|
||||
- name: Build SDK dist (sdk/dist is gitignored — must build for unpacked install)
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Ensure npm global bin is on PATH
|
||||
shell: bash
|
||||
run: |
|
||||
NPM_BIN="$(npm config get prefix)/bin"
|
||||
echo "$NPM_BIN" >> "$GITHUB_PATH"
|
||||
echo "npm global bin: $NPM_BIN"
|
||||
|
||||
- name: Strip execute bit from sdk/dist/cli.js to simulate tsc-fresh output
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Simulate the exact state tsc produces: cli.js at mode 644.
|
||||
chmod 644 sdk/dist/cli.js
|
||||
echo "Stripped execute bit: $(stat -c '%a' sdk/dist/cli.js 2>/dev/null || stat -f '%p' sdk/dist/cli.js)"
|
||||
|
||||
- name: Install from unpacked directory (no npm pack)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TMPDIR_ROOT=$(mktemp -d)
|
||||
cd "$TMPDIR_ROOT"
|
||||
npm install -g "$GITHUB_WORKSPACE"
|
||||
command -v get-shit-done-cc
|
||||
get-shit-done-cc --claude --local || true
|
||||
|
||||
- name: Assert gsd-sdk resolves on PATH after unpacked install
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v gsd-sdk >/dev/null 2>&1; then
|
||||
echo "::error::gsd-sdk is not on PATH after unpacked install — #2453 regression"
|
||||
NPM_BIN="$(npm config get prefix)/bin"
|
||||
ls -la "$NPM_BIN" | grep -i gsd || true
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ gsd-sdk resolves at: $(command -v gsd-sdk)"
|
||||
|
||||
- name: Assert gsd-sdk is executable after unpacked install (#2453)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# This is the exact check that would have caught #2453 before release.
|
||||
# The shim (bin/gsd-sdk.js) invokes sdk/dist/cli.js via `node`, so
|
||||
# the execute bit on cli.js is not needed for the shim path. However
|
||||
# installSdkIfNeeded() also chmods cli.js in-place as a safety net.
|
||||
gsd-sdk --version || gsd-sdk --help
|
||||
echo "✓ gsd-sdk is executable after unpacked install"
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -189,8 +189,8 @@ jobs:
|
||||
git add package.json package-lock.json sdk/package.json
|
||||
git commit -m "chore: bump to ${PRE_VERSION}"
|
||||
|
||||
- name: Build SDK
|
||||
run: cd sdk && npm ci && npm run build
|
||||
- name: Build SDK dist for tarball
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Dry-run publish validation
|
||||
run: |
|
||||
@@ -330,8 +330,8 @@ jobs:
|
||||
npm ci
|
||||
npm run test:coverage
|
||||
|
||||
- name: Build SDK
|
||||
run: cd sdk && npm ci && npm run build
|
||||
- name: Build SDK dist for tarball
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Dry-run publish validation
|
||||
run: |
|
||||
|
||||
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
@@ -45,6 +45,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build SDK dist (required by installer)
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Run tests with coverage
|
||||
shell: bash
|
||||
run: npm run test:coverage
|
||||
|
||||
@@ -30,6 +30,12 @@ If you use GSD **as a workflow**—milestones, phases, `.planning/` artifacts, b
|
||||
|
||||
- **Shell hooks falsely flagged as stale on every session** — `gsd-phase-boundary.sh`, `gsd-session-state.sh`, and `gsd-validate-commit.sh` now ship with a `# gsd-hook-version: {{GSD_VERSION}}` header; the installer substitutes `{{GSD_VERSION}}` in `.sh` hooks the same way it does for `.js` hooks; and the stale-hook detector in `gsd-check-update.js` now matches bash `#` comment syntax in addition to JS `//` syntax. All three changes are required together — neither the regex fix alone nor the install fix alone is sufficient to resolve the false positive (#2136, #2206, #2209, #2210, #2212)
|
||||
|
||||
## [1.38.2] - 2026-04-19
|
||||
|
||||
### Fixed
|
||||
- **SDK decoupled from build-from-source install** — replaces the fragile `tsc` + `npm install -g ./sdk` dance on user machines with a prebuilt `sdk/dist/` shipped inside the parent `get-shit-done-cc` tarball. The `gsd-sdk` CLI is now a `bin/gsd-sdk.js` shim in the parent package that resolves `sdk/dist/cli.js` and invokes it via `node`, so npm chmods the bin entry from the tarball (not from a secondary local install) and PATH/exec-bit issues cannot occur. Repurposes `installSdkIfNeeded()` in `bin/install.js` to only verify `sdk/dist/cli.js` exists and fix its execute bit (non-fatal); deletes `resolveGsdSdk()`, `detectShellRc()`, `emitSdkFatal()` and the source-build/global-install logic (162 lines removed). `release.yml` now runs `npm run build:sdk` before publish in both rc and finalize jobs, so every published tarball contains fresh SDK dist. `sdk/package.json` `prepublishOnly` is the final safety net (`rm -rf dist && tsc && chmod +x dist/cli.js`). `install-smoke.yml` adds an `smoke-unpacked` variant that installs from the unpacked dir with the exec bit stripped, so this class of regression cannot ship again. Closes #2441 and #2453.
|
||||
- **`--sdk` flag semantics changed** — previously forced a rebuild of the SDK from source; now verifies the bundled `sdk/dist/` is resolvable. Users who were invoking `get-shit-done-cc --sdk` as a "force rebuild" no longer need it — the SDK ships prebuilt.
|
||||
|
||||
### Added
|
||||
- **`/gsd-ingest-docs` command** — Scan a repo containing mixed ADRs, PRDs, SPECs, and DOCs and bootstrap or merge the full `.planning/` setup from them in a single pass. Parallel classification (`gsd-doc-classifier`), synthesis with precedence rules and cycle detection (`gsd-doc-synthesizer`), three-bucket conflicts report (`INGEST-CONFLICTS.md`: auto-resolved, competing-variants, unresolved-blockers), and hard-block on LOCKED-vs-LOCKED ADR contradictions in both new and merge modes. Supports directory-convention discovery and `--manifest <file>` YAML override with per-doc precedence. v1 caps at 50 docs per invocation; `--resolve interactive` is reserved. Extracts shared conflict-detection contract into `references/doc-conflict-engine.md` which `/gsd-import` now also consumes (#2387)
|
||||
- **`/gsd-plan-review-convergence` command** — Cross-AI plan convergence loop that automates `plan-phase → review → replan → re-review` cycles. Spawns isolated agents for `gsd-plan-phase` and `gsd-review`; orchestrator only does loop control, HIGH concern counting, stall detection, and escalation. Supports `--codex`, `--gemini`, `--claude`, `--opencode`, `--all` reviewers and `--max-cycles N` (default 3). Loop exits when no HIGH concerns remain; stall detection warns when count isn't decreasing; escalation gate asks user to proceed or review manually when max cycles reached (#2306)
|
||||
@@ -2368,7 +2374,8 @@ Technical implementation details for Phase 2 appear in the **Changed** section b
|
||||
- YOLO mode for autonomous execution
|
||||
- Interactive mode with checkpoints
|
||||
|
||||
[Unreleased]: https://github.com/gsd-build/get-shit-done/compare/v1.37.1...HEAD
|
||||
[Unreleased]: https://github.com/gsd-build/get-shit-done/compare/v1.38.2...HEAD
|
||||
[1.38.2]: https://github.com/gsd-build/get-shit-done/compare/v1.37.1...v1.38.2
|
||||
[1.37.1]: https://github.com/gsd-build/get-shit-done/compare/v1.37.0...v1.37.1
|
||||
[1.37.0]: https://github.com/gsd-build/get-shit-done/compare/v1.36.0...v1.37.0
|
||||
[1.36.0]: https://github.com/gsd-build/get-shit-done/releases/tag/v1.36.0
|
||||
|
||||
32
bin/gsd-sdk.js
Executable file
32
bin/gsd-sdk.js
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* bin/gsd-sdk.js — back-compat shim for external callers of `gsd-sdk`.
|
||||
*
|
||||
* When the parent package is installed globally (`npm install -g get-shit-done-cc`
|
||||
* or `npx get-shit-done-cc`), npm creates a `gsd-sdk` symlink in the global bin
|
||||
* directory pointing at this file. npm correctly chmods bin entries from a tarball,
|
||||
* so the execute-bit problem that afflicted the sub-install approach (issue #2453)
|
||||
* cannot occur here.
|
||||
*
|
||||
* This shim resolves sdk/dist/cli.js relative to its own location and delegates
|
||||
* to it via `node`, so `gsd-sdk <args>` behaves identically to
|
||||
* `node <packageDir>/sdk/dist/cli.js <args>`.
|
||||
*
|
||||
* Call sites (slash commands, agent prompts, hook scripts) continue to work without
|
||||
* changes because `gsd-sdk` still resolves on PATH — it just comes from this shim
|
||||
* in the parent package rather than from a separately installed @gsd-build/sdk.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const cliPath = path.resolve(__dirname, '..', 'sdk', 'dist', 'cli.js');
|
||||
|
||||
const result = spawnSync(process.execPath, [cliPath, ...process.argv.slice(2)], {
|
||||
stdio: 'inherit',
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
process.exit(result.status ?? 1);
|
||||
254
bin/install.js
254
bin/install.js
@@ -6798,228 +6798,63 @@ function promptLocation(runtimes) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build `@gsd-build/sdk` from the in-repo `sdk/` source tree and install the
|
||||
* resulting `gsd-sdk` binary globally so workflow commands that shell out to
|
||||
* `gsd-sdk query …` succeed.
|
||||
* Verify the prebuilt SDK dist is present and the gsd-sdk shim is wired up.
|
||||
*
|
||||
* We build from source rather than `npm install -g @gsd-build/sdk` because the
|
||||
* npm-published package lags the source tree and shipping a stale SDK breaks
|
||||
* every /gsd-* command that depends on newer query handlers.
|
||||
* As of fix/2441-sdk-decouple, sdk/dist/ is shipped prebuilt inside the
|
||||
* get-shit-done-cc npm tarball. The parent package declares a bin entry
|
||||
* "gsd-sdk": "bin/gsd-sdk.js" so npm chmods the shim correctly when
|
||||
* installing from a packed tarball — eliminating the mode-644 failure
|
||||
* (issue #2453) and the build-from-source failure modes (#2439, #2441).
|
||||
*
|
||||
* Skip if --no-sdk. Skip if already on PATH (unless --sdk was explicit).
|
||||
* Failures are FATAL — we exit non-zero so install does not complete with a
|
||||
* silently broken SDK (issue #2439). Set GSD_ALLOW_OFF_PATH=1 to downgrade the
|
||||
* post-install PATH verification to a warning (exit code 2) for users with an
|
||||
* intentionally restricted PATH who will wire things up manually.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolve `gsd-sdk` on PATH. Uses `command -v` via `sh -c` on POSIX (portable
|
||||
* across sh/bash/zsh) and `where` on Windows. Returns trimmed path or null.
|
||||
*/
|
||||
function resolveGsdSdk() {
|
||||
const { spawnSync } = require('child_process');
|
||||
if (process.platform === 'win32') {
|
||||
const r = spawnSync('where', ['gsd-sdk'], { encoding: 'utf-8' });
|
||||
if (r.status === 0 && r.stdout && r.stdout.trim()) {
|
||||
return r.stdout.trim().split('\n')[0].trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const r = spawnSync('sh', ['-c', 'command -v gsd-sdk'], { encoding: 'utf-8' });
|
||||
if (r.status === 0 && r.stdout && r.stdout.trim()) {
|
||||
return r.stdout.trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort detection of the user's shell rc file for PATH remediation hints.
|
||||
*/
|
||||
function detectShellRc() {
|
||||
const path = require('path');
|
||||
const shell = process.env.SHELL || '';
|
||||
const home = process.env.HOME || '~';
|
||||
if (/\/zsh$/.test(shell)) return { shell: 'zsh', rc: path.join(home, '.zshrc') };
|
||||
if (/\/bash$/.test(shell)) return { shell: 'bash', rc: path.join(home, '.bashrc') };
|
||||
if (/\/fish$/.test(shell)) return { shell: 'fish', rc: path.join(home, '.config', 'fish', 'config.fish') };
|
||||
return { shell: 'sh', rc: path.join(home, '.profile') };
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a red fatal banner and exit. Prints actionable PATH remediation when
|
||||
* the global install succeeded but the bin dir is not on PATH.
|
||||
* This function verifies the invariant: sdk/dist/cli.js exists and is
|
||||
* executable. If the execute bit is missing (possible in dev/clone setups
|
||||
* where sdk/dist was committed without +x), we fix it in-place.
|
||||
*
|
||||
* If exitCode is 2, this is the "off-PATH" case and GSD_ALLOW_OFF_PATH respect
|
||||
* is applied by the caller; we only print.
|
||||
* --no-sdk skips the check entirely (back-compat).
|
||||
* --sdk forces the check even if it would otherwise be skipped.
|
||||
*/
|
||||
function emitSdkFatal(reason, { globalBin, exitCode }) {
|
||||
const { shell, rc } = detectShellRc();
|
||||
const bar = '━'.repeat(72);
|
||||
const redBold = `${red}${bold}`;
|
||||
|
||||
console.error('');
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error(`${redBold} ✗ GSD SDK install failed — /gsd-* commands will not work${reset}`);
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error(` ${red}Reason:${reset} ${reason}`);
|
||||
|
||||
if (globalBin) {
|
||||
console.error('');
|
||||
console.error(` ${yellow}gsd-sdk was installed to:${reset}`);
|
||||
console.error(` ${cyan}${globalBin}${reset}`);
|
||||
console.error('');
|
||||
console.error(` ${yellow}Your shell's PATH does not include this directory.${reset}`);
|
||||
console.error(` Add it by running:`);
|
||||
if (shell === 'fish') {
|
||||
console.error(` ${cyan}fish_add_path "${globalBin}"${reset}`);
|
||||
console.error(` (or append to ${rc})`);
|
||||
} else {
|
||||
console.error(` ${cyan}echo 'export PATH="${globalBin}:$PATH"' >> ${rc}${reset}`);
|
||||
console.error(` ${cyan}source ${rc}${reset}`);
|
||||
}
|
||||
console.error('');
|
||||
console.error(` Then verify: ${cyan}command -v gsd-sdk${reset}`);
|
||||
if (exitCode === 2) {
|
||||
console.error('');
|
||||
console.error(` ${dim}(GSD_ALLOW_OFF_PATH=1 set → exit ${exitCode} instead of hard failure)${reset}`);
|
||||
}
|
||||
} else {
|
||||
console.error('');
|
||||
console.error(` Build manually to retry:`);
|
||||
console.error(` ${cyan}cd <install-dir>/sdk && npm install && npm run build && npm install -g .${reset}`);
|
||||
}
|
||||
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error('');
|
||||
process.exit(exitCode);
|
||||
}
|
||||
|
||||
function installSdkIfNeeded() {
|
||||
if (hasNoSdk) {
|
||||
console.log(`\n ${dim}Skipping GSD SDK install (--no-sdk)${reset}`);
|
||||
console.log(`\n ${dim}Skipping GSD SDK check (--no-sdk)${reset}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { spawnSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
if (!hasSdk) {
|
||||
const resolved = resolveGsdSdk();
|
||||
if (resolved) {
|
||||
console.log(` ${green}✓${reset} GSD SDK already installed (gsd-sdk on PATH at ${resolved})`);
|
||||
return;
|
||||
}
|
||||
const sdkCliPath = path.resolve(__dirname, '..', 'sdk', 'dist', 'cli.js');
|
||||
|
||||
if (!fs.existsSync(sdkCliPath)) {
|
||||
const bar = '━'.repeat(72);
|
||||
const redBold = `${red}${bold}`;
|
||||
console.error('');
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error(`${redBold} ✗ GSD SDK dist not found — /gsd-* commands will not work${reset}`);
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error(` ${red}Reason:${reset} sdk/dist/cli.js not found at ${sdkCliPath}`);
|
||||
console.error('');
|
||||
console.error(` This should not happen with a published tarball install.`);
|
||||
console.error(` If you are running from a git clone, build the SDK first:`);
|
||||
console.error(` ${cyan}cd sdk && npm install && npm run build${reset}`);
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error('');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Locate the in-repo sdk/ directory relative to this installer file.
|
||||
// For global npm installs this resolves inside the published package dir;
|
||||
// for git-based installs (npx github:..., local clone) it resolves to the
|
||||
// repo's sdk/ tree. Both contain the source tree because root package.json
|
||||
// includes "sdk" in its `files` array.
|
||||
const sdkDir = path.resolve(__dirname, '..', 'sdk');
|
||||
const sdkPackageJson = path.join(sdkDir, 'package.json');
|
||||
|
||||
if (!fs.existsSync(sdkPackageJson)) {
|
||||
emitSdkFatal(`SDK source tree not found at ${sdkDir}.`, { globalBin: null, exitCode: 1 });
|
||||
}
|
||||
|
||||
console.log(`\n ${cyan}Building GSD SDK from source (${sdkDir})…${reset}`);
|
||||
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
|
||||
// Windows: Node.js refuses to spawn .cmd/.bat files without `shell: true`
|
||||
// after CVE-2024-27980 (fixed in Node ≥ 18.20.2 / ≥ 20.12.2 / ≥ 21.7.3).
|
||||
// Without shell, spawnSync returns { status: null, error: EINVAL } and
|
||||
// every `status !== 0` check trips — producing a silent build failure
|
||||
// with no underlying diagnostic because stdio: 'inherit' never gets a
|
||||
// child to stream (#2598).
|
||||
const needsShell = process.platform === 'win32';
|
||||
const spawnNpm = (args, opts = {}) =>
|
||||
spawnSync(npmCmd, args, { ...opts, shell: opts.shell ?? needsShell });
|
||||
|
||||
// Format the underlying spawnSync failure so EINVAL / ENOENT / signal exits
|
||||
// surface in the fatal banner instead of being swallowed. The #2598 silent
|
||||
// failure happened precisely because `{ status: null, error: EINVAL }` was
|
||||
// reduced to a generic "Failed to npm install" with no diagnostic — the real
|
||||
// cause (CVE-2024-27980 on Windows) was invisible in the output.
|
||||
const formatSpawnFailure = (result) => {
|
||||
if (!result) return '';
|
||||
if (result.error) return ` (${result.error.code || result.error.name || 'spawn error'}: ${result.error.message})`;
|
||||
if (result.signal) return ` (signal: ${result.signal})`;
|
||||
if (typeof result.status === 'number') return ` (exit status: ${result.status})`;
|
||||
return '';
|
||||
};
|
||||
|
||||
// 1. Install sdk build-time dependencies (tsc, etc.)
|
||||
const installResult = spawnNpm(['install'], { cwd: sdkDir, stdio: 'inherit' });
|
||||
if (installResult.status !== 0) {
|
||||
emitSdkFatal(
|
||||
`Failed to \`npm install\` in sdk/.${formatSpawnFailure(installResult)}`,
|
||||
{ globalBin: null, exitCode: 1 },
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Compile TypeScript → sdk/dist/
|
||||
const buildResult = spawnNpm(['run', 'build'], { cwd: sdkDir, stdio: 'inherit' });
|
||||
if (buildResult.status !== 0) {
|
||||
emitSdkFatal(
|
||||
`Failed to \`npm run build\` in sdk/.${formatSpawnFailure(buildResult)}`,
|
||||
{ globalBin: null, exitCode: 1 },
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Install the built package globally so `gsd-sdk` lands on PATH.
|
||||
const globalResult = spawnNpm(['install', '-g', '.'], { cwd: sdkDir, stdio: 'inherit' });
|
||||
if (globalResult.status !== 0) {
|
||||
emitSdkFatal(
|
||||
`Failed to \`npm install -g .\` from sdk/.${formatSpawnFailure(globalResult)}`,
|
||||
{ globalBin: null, exitCode: 1 },
|
||||
);
|
||||
}
|
||||
|
||||
// 3a. Explicitly chmod dist/cli.js to 0o755 in the global install location.
|
||||
// `tsc` emits files at process umask (typically 0o644 — non-executable), and
|
||||
// `npm install -g` from a local directory does NOT chmod bin-script targets the
|
||||
// way tarball extraction does. Without this, the `gsd-sdk` bin symlink points at
|
||||
// a non-executable file and `command -v gsd-sdk` fails on every first install
|
||||
// (root cause of #2453). Mirrors the pattern used for hook files in this installer.
|
||||
// Ensure execute bit is set. tsc emits files at 0o644; git clone preserves
|
||||
// whatever mode was committed. Fix in-place so node-invoked paths work too.
|
||||
try {
|
||||
const prefixRes = spawnNpm(['config', 'get', 'prefix'], { encoding: 'utf-8' });
|
||||
if (prefixRes.status === 0) {
|
||||
const npmPrefix = (prefixRes.stdout || '').trim();
|
||||
const sdkPkg = JSON.parse(fs.readFileSync(path.join(sdkDir, 'package.json'), 'utf-8'));
|
||||
const sdkName = sdkPkg.name; // '@gsd-build/sdk'
|
||||
const globalModulesDir = process.platform === 'win32'
|
||||
? path.join(npmPrefix, 'node_modules')
|
||||
: path.join(npmPrefix, 'lib', 'node_modules');
|
||||
const cliPath = path.join(globalModulesDir, sdkName, 'dist', 'cli.js');
|
||||
try { fs.chmodSync(cliPath, 0o755); } catch (e) { if (process.platform !== 'win32') throw e; }
|
||||
const stat = fs.statSync(sdkCliPath);
|
||||
const isExecutable = !!(stat.mode & 0o111);
|
||||
if (!isExecutable) {
|
||||
fs.chmodSync(sdkCliPath, stat.mode | 0o111);
|
||||
}
|
||||
} catch (e) { /* Non-fatal: PATH verification in step 4 will catch any real failure */ }
|
||||
|
||||
// 4. Verify gsd-sdk is actually resolvable on PATH. npm's global bin dir is
|
||||
// not always on the current shell's PATH (Homebrew prefixes, nvm setups,
|
||||
// unconfigured npm prefix), so a zero exit status from `npm install -g`
|
||||
// alone is not proof of a working binary (issue #2439 root cause).
|
||||
const resolved = resolveGsdSdk();
|
||||
if (resolved) {
|
||||
console.log(` ${green}✓${reset} Built and installed GSD SDK from source (gsd-sdk resolved at ${resolved})`);
|
||||
return;
|
||||
} catch {
|
||||
// Non-fatal: if chmod fails (e.g. read-only fs) the shim still works via
|
||||
// `node sdkCliPath` invocation in bin/gsd-sdk.js.
|
||||
}
|
||||
|
||||
// Off-PATH: resolve npm global bin dir for actionable remediation.
|
||||
const prefixResult = spawnNpm(['config', 'get', 'prefix'], { encoding: 'utf-8' });
|
||||
const prefix = prefixResult.status === 0 ? (prefixResult.stdout || '').trim() : null;
|
||||
const globalBin = prefix
|
||||
? (process.platform === 'win32' ? prefix : path.join(prefix, 'bin'))
|
||||
: null;
|
||||
|
||||
const allowOffPath = process.env.GSD_ALLOW_OFF_PATH === '1';
|
||||
emitSdkFatal(
|
||||
'Built and installed GSD SDK, but `gsd-sdk` is not on your PATH.',
|
||||
{ globalBin, exitCode: allowOffPath ? 2 : 1 },
|
||||
);
|
||||
console.log(` ${green}✓${reset} GSD SDK ready (sdk/dist/cli.js)`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -7037,13 +6872,10 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
||||
const primaryStatuslineResult = results.find(r => statuslineRuntimes.includes(r.runtime));
|
||||
|
||||
const finalize = (shouldInstallStatusline) => {
|
||||
// Build @gsd-build/sdk from the in-repo sdk/ source and install it globally
|
||||
// so `gsd-sdk` lands on PATH. Every /gsd-* command shells out to
|
||||
// `gsd-sdk query …`; without this, commands fail with "command not found:
|
||||
// gsd-sdk". The npm-published @gsd-build/sdk is kept intentionally frozen
|
||||
// at an older version; we always build from source so users get the SDK
|
||||
// that matches the installed GSD version.
|
||||
// Runs by default; skip with --no-sdk. Idempotent when already present.
|
||||
// Verify sdk/dist/cli.js is present and executable. The dist is shipped
|
||||
// prebuilt in the tarball (fix/2441-sdk-decouple); gsd-sdk reaches users via
|
||||
// the parent package's bin/gsd-sdk.js shim, so no sub-install is needed.
|
||||
// Skip with --no-sdk.
|
||||
installSdkIfNeeded();
|
||||
|
||||
const printSummaries = () => {
|
||||
|
||||
3304
package-lock.json
generated
3304
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -3,7 +3,8 @@
|
||||
"version": "1.38.2",
|
||||
"description": "A meta-prompting, context engineering and spec-driven development system for Claude Code, OpenCode, Gemini and Codex by TÂCHES.",
|
||||
"bin": {
|
||||
"get-shit-done-cc": "bin/install.js"
|
||||
"get-shit-done-cc": "bin/install.js",
|
||||
"gsd-sdk": "bin/gsd-sdk.js"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
@@ -14,6 +15,7 @@
|
||||
"scripts",
|
||||
"sdk/src",
|
||||
"sdk/prompts",
|
||||
"sdk/dist",
|
||||
"sdk/package.json",
|
||||
"sdk/package-lock.json",
|
||||
"sdk/tsconfig.json"
|
||||
@@ -43,14 +45,19 @@
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.2.84",
|
||||
"ws": "^8.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"c8": "^11.0.0",
|
||||
"esbuild": "^0.24.0",
|
||||
"vitest": "^4.1.2"
|
||||
"c8": "^11.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build:hooks": "node scripts/build-hooks.js",
|
||||
"prepublishOnly": "npm run build:hooks",
|
||||
"build:sdk": "cd sdk && npm ci && npm run build",
|
||||
"prepublishOnly": "npm run build:hooks && npm run build:sdk",
|
||||
"pretest": "npm run build:sdk",
|
||||
"pretest:coverage": "npm run build:sdk",
|
||||
"test": "node scripts/run-tests.cjs",
|
||||
"test:coverage": "c8 --check-coverage --lines 70 --reporter text --include 'get-shit-done/bin/lib/*.cjs' --exclude 'tests/**' --all node scripts/run-tests.cjs"
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"prepublishOnly": "npm run build",
|
||||
"prepublishOnly": "rm -rf dist && tsc && chmod +x dist/cli.js",
|
||||
"test": "vitest run",
|
||||
"test:unit": "vitest run --project unit",
|
||||
"test:integration": "vitest run --project integration"
|
||||
|
||||
165
tests/bug-2441-sdk-decouple.test.cjs
Normal file
165
tests/bug-2441-sdk-decouple.test.cjs
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Regression tests for fix/2441-sdk-decouple
|
||||
*
|
||||
* Verifies the architectural invariants introduced by the SDK decouple:
|
||||
*
|
||||
* (a) bin/install.js does NOT invoke `npm install -g` for the SDK at all.
|
||||
* The old `installSdkIfNeeded()` built from source and ran `npm install -g .`
|
||||
* in sdk/; the new version only verifies the prebuilt dist.
|
||||
*
|
||||
* (b) The parent package.json declares a `gsd-sdk` bin entry pointing at
|
||||
* bin/gsd-sdk.js (the back-compat shim), so npm chmods it correctly.
|
||||
*
|
||||
* (c) sdk/dist/ is in the parent package `files` so it ships in the tarball.
|
||||
*
|
||||
* (d) sdk/package.json `prepublishOnly` runs `rm -rf dist && tsc && chmod +x dist/cli.js`
|
||||
* (guards against the mode-644 bug and npm's stale-prepublishOnly issue).
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const INSTALL_JS = path.join(__dirname, '..', 'bin', 'install.js');
|
||||
const ROOT_PKG = path.join(__dirname, '..', 'package.json');
|
||||
const SDK_PKG = path.join(__dirname, '..', 'sdk', 'package.json');
|
||||
const GSD_SDK_SHIM = path.join(__dirname, '..', 'bin', 'gsd-sdk.js');
|
||||
|
||||
const installContent = fs.readFileSync(INSTALL_JS, 'utf-8');
|
||||
const rootPkg = JSON.parse(fs.readFileSync(ROOT_PKG, 'utf-8'));
|
||||
const sdkPkg = JSON.parse(fs.readFileSync(SDK_PKG, 'utf-8'));
|
||||
|
||||
describe('fix #2441: SDK decouple — installer no longer builds from source', () => {
|
||||
test('bin/install.js does not call npm install -g in sdk/', () => {
|
||||
// The old approach ran `npm install -g .` from sdk/. This must be gone.
|
||||
// We check for the specific pattern that installed the SDK globally.
|
||||
const hasGlobalInstallFromSdk =
|
||||
/spawnSync\(npmCmd,\s*\[['"]install['"],\s*['"](-g|--global)['"]/m.test(installContent) &&
|
||||
/cwd:\s*sdkDir/.test(installContent);
|
||||
assert.ok(
|
||||
!hasGlobalInstallFromSdk,
|
||||
'bin/install.js must not run `npm install -g .` from sdk/. ' +
|
||||
'The SDK is shipped prebuilt in the tarball (fix #2441).'
|
||||
);
|
||||
});
|
||||
|
||||
test('bin/install.js does not run npm run build in sdk/', () => {
|
||||
// The old approach ran `npm run build` (tsc) at install time.
|
||||
const hasBuildStep =
|
||||
/spawnSync\(npmCmd,\s*\[['"]run['"],\s*['"]build['"]\]/m.test(installContent) &&
|
||||
/cwd:\s*sdkDir/.test(installContent);
|
||||
assert.ok(
|
||||
!hasBuildStep,
|
||||
'bin/install.js must not run `npm run build` in sdk/ at install time. ' +
|
||||
'TypeScript compilation happens at publish time via prepublishOnly.'
|
||||
);
|
||||
});
|
||||
|
||||
test('installSdkIfNeeded checks sdk/dist/cli.js exists instead of building', () => {
|
||||
assert.ok(
|
||||
installContent.includes('sdk/dist/cli.js') || installContent.includes("'dist', 'cli.js'"),
|
||||
'installSdkIfNeeded() must reference sdk/dist/cli.js to verify the prebuilt dist.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fix #2441: back-compat shim — parent package bin entry', () => {
|
||||
test('root package.json declares gsd-sdk bin entry', () => {
|
||||
assert.ok(
|
||||
rootPkg.bin && rootPkg.bin['gsd-sdk'],
|
||||
'root package.json must have a bin["gsd-sdk"] entry for the back-compat shim.'
|
||||
);
|
||||
});
|
||||
|
||||
test('gsd-sdk bin entry points at bin/gsd-sdk.js', () => {
|
||||
assert.equal(
|
||||
rootPkg.bin['gsd-sdk'],
|
||||
'bin/gsd-sdk.js',
|
||||
'bin["gsd-sdk"] must point at bin/gsd-sdk.js'
|
||||
);
|
||||
});
|
||||
|
||||
test('bin/gsd-sdk.js shim file exists', () => {
|
||||
assert.ok(
|
||||
fs.existsSync(GSD_SDK_SHIM),
|
||||
'bin/gsd-sdk.js must exist as the back-compat PATH shim.'
|
||||
);
|
||||
});
|
||||
|
||||
test('bin/gsd-sdk.js resolves sdk/dist/cli.js relative to itself', () => {
|
||||
const shimContent = fs.readFileSync(GSD_SDK_SHIM, 'utf-8');
|
||||
// Require the actual path.resolve call with the expected segments, not
|
||||
// loose substring matches that would pass from comments or shebangs.
|
||||
assert.match(
|
||||
shimContent,
|
||||
/path\.resolve\(\s*__dirname\s*,\s*['"]\.\.['"]\s*,\s*['"]sdk['"]\s*,\s*['"]dist['"]\s*,\s*['"]cli\.js['"]\s*\)/,
|
||||
'bin/gsd-sdk.js must call path.resolve(__dirname, "..", "sdk", "dist", "cli.js") to locate the prebuilt CLI.'
|
||||
);
|
||||
});
|
||||
|
||||
test('bin/gsd-sdk.js invokes cli.js via spawnSync(process.execPath, ...)', () => {
|
||||
const shimContent = fs.readFileSync(GSD_SDK_SHIM, 'utf-8');
|
||||
// The shim must invoke via node (not rely on execute bit), which means
|
||||
// spawnSync(process.execPath, [cliPath, ...args]).
|
||||
assert.match(
|
||||
shimContent,
|
||||
/spawnSync\(\s*process\.execPath\s*,/,
|
||||
'bin/gsd-sdk.js must spawn node via process.execPath so the execute bit on cli.js is irrelevant (#2453).'
|
||||
);
|
||||
assert.match(
|
||||
shimContent,
|
||||
/process\.argv\.slice\(\s*2\s*\)/,
|
||||
'bin/gsd-sdk.js must forward user args via process.argv.slice(2).'
|
||||
);
|
||||
assert.match(
|
||||
shimContent,
|
||||
/process\.exit\(/,
|
||||
'bin/gsd-sdk.js must propagate the child exit status via process.exit.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fix #2441: sdk/dist shipped in tarball', () => {
|
||||
test('root package.json files includes sdk/dist', () => {
|
||||
assert.ok(
|
||||
Array.isArray(rootPkg.files) && rootPkg.files.some(f => f === 'sdk/dist' || f.startsWith('sdk/dist')),
|
||||
'root package.json files must include "sdk/dist" so the prebuilt CLI ships in the tarball.'
|
||||
);
|
||||
});
|
||||
|
||||
test('root package.json files still includes sdk/src (for dev/clone builds)', () => {
|
||||
assert.ok(
|
||||
Array.isArray(rootPkg.files) && rootPkg.files.some(f => f === 'sdk/src' || f.startsWith('sdk/src')),
|
||||
'root package.json files should still include sdk/src for developer builds.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fix #2453: sdk/package.json prepublishOnly guards execute bit', () => {
|
||||
test('sdk prepublishOnly deletes old dist before build (npm stale-prepublishOnly guard)', () => {
|
||||
const prepub = sdkPkg.scripts && sdkPkg.scripts.prepublishOnly;
|
||||
assert.ok(
|
||||
prepub && prepub.includes('rm -rf dist'),
|
||||
'sdk/package.json prepublishOnly must start with `rm -rf dist` to avoid stale build output.'
|
||||
);
|
||||
});
|
||||
|
||||
test('sdk prepublishOnly chmods dist/cli.js after tsc', () => {
|
||||
const prepub = sdkPkg.scripts && sdkPkg.scripts.prepublishOnly;
|
||||
assert.ok(
|
||||
prepub && prepub.includes('chmod +x dist/cli.js'),
|
||||
'sdk/package.json prepublishOnly must run `chmod +x dist/cli.js` after tsc to fix mode-644 (#2453).'
|
||||
);
|
||||
});
|
||||
|
||||
test('sdk prepublishOnly runs tsc', () => {
|
||||
const prepub = sdkPkg.scripts && sdkPkg.scripts.prepublishOnly;
|
||||
assert.ok(
|
||||
prepub && prepub.includes('tsc'),
|
||||
'sdk/package.json prepublishOnly must include tsc to compile TypeScript.'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,88 +0,0 @@
|
||||
/**
|
||||
* Regression test for bug #2453
|
||||
*
|
||||
* installSdkIfNeeded() builds sdk/dist/cli.js via `tsc` then runs
|
||||
* `npm install -g .`. TypeScript emits files at process umask (0o644) and
|
||||
* npm install from a local directory does NOT chmod bin-script targets the
|
||||
* way tarball extraction does. The result: the globally-installed
|
||||
* dist/cli.js lands with mode 644 (non-executable), the `gsd-sdk` symlink
|
||||
* points at a non-executable file, and `command -v gsd-sdk` fails on every
|
||||
* new install.
|
||||
*
|
||||
* Fix: after `npm install -g .`, the installer must explicitly
|
||||
* `chmodSync(cliPath, 0o755)` on the installed dist/cli.js. This mirrors
|
||||
* the pattern already used four times in install.js for hook files.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const INSTALL_SRC = path.join(__dirname, '..', 'bin', 'install.js');
|
||||
|
||||
describe('bug #2453: installSdkIfNeeded chmods sdk dist/cli.js to 0o755', () => {
|
||||
let installSrc;
|
||||
|
||||
test('install.js source exists', () => {
|
||||
assert.ok(fs.existsSync(INSTALL_SRC), 'bin/install.js must exist');
|
||||
installSrc = fs.readFileSync(INSTALL_SRC, 'utf-8');
|
||||
});
|
||||
|
||||
test('installSdkIfNeeded contains a chmodSync call for dist/cli.js', () => {
|
||||
installSrc = installSrc || fs.readFileSync(INSTALL_SRC, 'utf-8');
|
||||
|
||||
// Locate the installSdkIfNeeded function body
|
||||
const fnStart = installSrc.indexOf('function installSdkIfNeeded()');
|
||||
assert.ok(fnStart !== -1, 'installSdkIfNeeded function must exist in install.js');
|
||||
|
||||
// Find the end of the function (next top-level function declaration)
|
||||
const fnEnd = installSrc.indexOf('\nfunction ', fnStart + 1);
|
||||
const fnBody = fnEnd !== -1 ? installSrc.slice(fnStart, fnEnd) : installSrc.slice(fnStart);
|
||||
|
||||
// Must chmod dist/cli.js to make it executable after npm install -g .
|
||||
const hasChmod = fnBody.includes('chmodSync') && fnBody.includes('dist/cli.js');
|
||||
assert.ok(
|
||||
hasChmod,
|
||||
'installSdkIfNeeded must call chmodSync on dist/cli.js after npm install -g . ' +
|
||||
'(tsc emits 644; npm does not chmod bin targets from local dir installs — ' +
|
||||
'root cause of #2453: gsd-sdk symlink target is non-executable on first install)'
|
||||
);
|
||||
});
|
||||
|
||||
test('chmodSync for dist/cli.js uses mode 0o755', () => {
|
||||
installSrc = installSrc || fs.readFileSync(INSTALL_SRC, 'utf-8');
|
||||
|
||||
const fnStart = installSrc.indexOf('function installSdkIfNeeded()');
|
||||
const fnEnd = installSrc.indexOf('\nfunction ', fnStart + 1);
|
||||
const fnBody = fnEnd !== -1 ? installSrc.slice(fnStart, fnEnd) : installSrc.slice(fnStart);
|
||||
|
||||
// The chmod call must use 0o755 (executable), not 0o644
|
||||
const has755 = fnBody.includes('0o755') && fnBody.includes('dist/cli.js');
|
||||
assert.ok(
|
||||
has755,
|
||||
'chmodSync for dist/cli.js must use mode 0o755 to make the binary executable'
|
||||
);
|
||||
});
|
||||
|
||||
test('chmodSync appears after npm install -g . step', () => {
|
||||
installSrc = installSrc || fs.readFileSync(INSTALL_SRC, 'utf-8');
|
||||
|
||||
const fnStart = installSrc.indexOf('function installSdkIfNeeded()');
|
||||
const fnEnd = installSrc.indexOf('\nfunction ', fnStart + 1);
|
||||
const fnBody = fnEnd !== -1 ? installSrc.slice(fnStart, fnEnd) : installSrc.slice(fnStart);
|
||||
|
||||
const npmGlobalIdx = fnBody.indexOf("'install', '-g', '.'");
|
||||
const chmodIdx = fnBody.indexOf('chmodSync');
|
||||
|
||||
assert.ok(npmGlobalIdx !== -1, "npm install -g . step must be present in installSdkIfNeeded");
|
||||
assert.ok(chmodIdx !== -1, 'chmodSync must be present in installSdkIfNeeded');
|
||||
assert.ok(
|
||||
chmodIdx > npmGlobalIdx,
|
||||
'chmodSync must appear AFTER the npm install -g . step ' +
|
||||
'(the file to chmod does not exist until npm installs it globally)'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* Regression test for bug #2525
|
||||
*
|
||||
* After running the GSD installer on macOS with a Homebrew npm prefix,
|
||||
* `gsd-sdk` is installed but `command -v gsd-sdk` returns nothing because
|
||||
* `dist/cli.js` is installed with mode 644 (no executable bit). tsc emits
|
||||
* .js files as 644, and `npm install -g .` creates the bin symlink without
|
||||
* chmod-ing the target. The kernel then refuses to exec the file.
|
||||
*
|
||||
* Fix: between the `npm run build` step and `npm install -g .`, chmod
|
||||
* dist/cli.js to 0o755. This mirrors the pattern already used for hook
|
||||
* files at lines 5838, 5846, 5959, and 5965.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const INSTALL_PATH = path.join(__dirname, '..', 'bin', 'install.js');
|
||||
|
||||
describe('bug #2525: dist/cli.js chmod 0o755 after tsc build', () => {
|
||||
test('install.js exists', () => {
|
||||
assert.ok(fs.existsSync(INSTALL_PATH), 'bin/install.js should exist');
|
||||
});
|
||||
|
||||
test('chmodSync is called for dist/cli.js after the build step', () => {
|
||||
const content = fs.readFileSync(INSTALL_PATH, 'utf-8');
|
||||
// Find the installSdkIfNeeded function body
|
||||
const fnStart = content.indexOf('function installSdkIfNeeded()');
|
||||
assert.ok(fnStart !== -1, 'installSdkIfNeeded function must exist in bin/install.js');
|
||||
|
||||
// Find the closing brace of the function (next top-level function definition)
|
||||
const nextFnIdx = content.indexOf('\nfunction ', fnStart + 1);
|
||||
const fnEnd = nextFnIdx === -1 ? content.length : nextFnIdx;
|
||||
const fnBody = content.slice(fnStart, fnEnd);
|
||||
|
||||
// Locate the build step
|
||||
const buildStep = fnBody.indexOf("'run', 'build'");
|
||||
assert.ok(buildStep !== -1, "installSdkIfNeeded must contain the 'run', 'build' spawn call");
|
||||
|
||||
// Locate the global install step
|
||||
const globalStep = fnBody.indexOf("'install', '-g', '.'");
|
||||
assert.ok(globalStep !== -1, "installSdkIfNeeded must contain the 'install', '-g', '.' spawn call");
|
||||
|
||||
// Locate chmodSync for dist/cli.js
|
||||
const chmodIdx = fnBody.indexOf("chmodSync");
|
||||
assert.ok(chmodIdx !== -1, "installSdkIfNeeded must call chmodSync to set the executable bit on dist/cli.js");
|
||||
|
||||
// The path may be assembled via path.join(sdkDir, 'dist', 'cli.js') so check
|
||||
// for the component strings rather than the joined slash form.
|
||||
const cliPathIdx = fnBody.indexOf("'cli.js'");
|
||||
assert.ok(cliPathIdx !== -1, "installSdkIfNeeded must reference 'cli.js' (via path.join or literal) for the chmod call");
|
||||
|
||||
// chmodSync must appear AFTER the build step
|
||||
assert.ok(
|
||||
chmodIdx > buildStep,
|
||||
'chmodSync for dist/cli.js must appear AFTER the npm run build step'
|
||||
);
|
||||
|
||||
// chmodSync must appear AFTER the global install step
|
||||
assert.ok(
|
||||
chmodIdx > globalStep,
|
||||
'chmodSync for dist/cli.js must appear AFTER the npm install -g . step'
|
||||
);
|
||||
});
|
||||
|
||||
test('chmod mode is 0o755', () => {
|
||||
const content = fs.readFileSync(INSTALL_PATH, 'utf-8');
|
||||
const fnStart = content.indexOf('function installSdkIfNeeded()');
|
||||
const nextFnIdx = content.indexOf('\nfunction ', fnStart + 1);
|
||||
const fnEnd = nextFnIdx === -1 ? content.length : nextFnIdx;
|
||||
const fnBody = content.slice(fnStart, fnEnd);
|
||||
|
||||
assert.ok(
|
||||
fnBody.includes('0o755'),
|
||||
"chmodSync call in installSdkIfNeeded must use mode 0o755 (not 0o644 or a bare number)"
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,138 +0,0 @@
|
||||
/**
|
||||
* Regression test for bug #2598
|
||||
*
|
||||
* On Windows, Node.js refuses to spawn `.cmd`/`.bat` files via spawnSync
|
||||
* unless `shell: true` is passed (post CVE-2024-27980, fixed in
|
||||
* Node >= 18.20.2 / >= 20.12.2 / >= 21.7.3 and every version since).
|
||||
*
|
||||
* installSdkIfNeeded() picks `npmCmd = 'npm.cmd'` on win32 and then shells
|
||||
* out five times: `npm install`, `npm run build`, `npm install -g .`, and
|
||||
* two `npm config get prefix` calls. Without `shell: true`, every single
|
||||
* one of those returns `{ status: null, error: EINVAL }` before npm ever
|
||||
* launches. The installer checks `status !== 0` (null !== 0 is true) and
|
||||
* trips its failure path — producing a silent SDK build failure with zero
|
||||
* diagnostic output because `stdio: 'inherit'` never got a child to stream.
|
||||
*
|
||||
* Fix: every spawnSync that targets `npmCmd` inside installSdkIfNeeded
|
||||
* must pass `shell: true` on Windows. The structural invariant verified
|
||||
* here is that no bare `spawnSync(npmCmd, ...)` remains inside the
|
||||
* function — all npm invocations go through a helper that injects the
|
||||
* shell option on win32 (or have it explicitly on the call site).
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const INSTALL_SRC = path.join(__dirname, '..', 'bin', 'install.js');
|
||||
|
||||
function installSdkBody() {
|
||||
const src = fs.readFileSync(INSTALL_SRC, 'utf-8');
|
||||
const fnStart = src.indexOf('function installSdkIfNeeded()');
|
||||
assert.ok(fnStart !== -1, 'installSdkIfNeeded function must exist in install.js');
|
||||
const fnEnd = src.indexOf('\nfunction ', fnStart + 1);
|
||||
return fnEnd !== -1 ? src.slice(fnStart, fnEnd) : src.slice(fnStart);
|
||||
}
|
||||
|
||||
describe('bug #2598: Windows npm.cmd spawnSync must pass shell: true', () => {
|
||||
test('install.js source exists', () => {
|
||||
assert.ok(fs.existsSync(INSTALL_SRC), 'bin/install.js must exist');
|
||||
});
|
||||
|
||||
test('installSdkIfNeeded does not call spawnSync(npmCmd, ...) directly', () => {
|
||||
const body = installSdkBody();
|
||||
// A bare `spawnSync(npmCmd,` call will fail with EINVAL on Windows
|
||||
// because npm.cmd requires `shell: true`. Every npm invocation must
|
||||
// go through a wrapper that injects the shell option on win32.
|
||||
//
|
||||
// Exclude the implementation of the wrapper itself (its `spawnSync(npmCmd, args, ...)`
|
||||
// line is the ONE legitimate place that spawns npmCmd directly — and it forwards
|
||||
// a shell option from `opts`).
|
||||
const allCalls = body.match(/spawnSync\s*\(\s*npmCmd[^)]*\)/g) || [];
|
||||
const bareCalls = allCalls.filter(c => !/\bshell\s*:/.test(c));
|
||||
assert.equal(
|
||||
bareCalls.length,
|
||||
0,
|
||||
'installSdkIfNeeded must not call spawnSync(npmCmd, ...) directly. ' +
|
||||
'On Windows, npm.cmd spawns fail with EINVAL (CVE-2024-27980) unless ' +
|
||||
'shell: true is passed. Route all npm invocations through a helper ' +
|
||||
'that injects `shell: process.platform === "win32"`.'
|
||||
);
|
||||
});
|
||||
|
||||
test('installSdkIfNeeded defines a shell-aware npm wrapper', () => {
|
||||
const body = installSdkBody();
|
||||
// The canonical implementation introduces `spawnNpm` (or equivalent)
|
||||
// that injects `shell: true` on win32. Accept either a helper
|
||||
// function or `shell: true` / `shell: needsShell` / `shell: win32`
|
||||
// appearing alongside an npm spawn.
|
||||
const hasHelper = /\bspawnNpm\b/.test(body);
|
||||
const hasShellOption = /shell\s*:\s*(true|needsShell|process\.platform\s*===\s*['"]win32['"])/.test(body);
|
||||
assert.ok(
|
||||
hasHelper || hasShellOption,
|
||||
'installSdkIfNeeded must introduce a helper or pass shell:true/win32-aware ' +
|
||||
'shell option for npm spawnSync calls. Found neither.'
|
||||
);
|
||||
});
|
||||
|
||||
test('installSdkIfNeeded calls the npm wrapper at least five times', () => {
|
||||
const body = installSdkBody();
|
||||
// Five documented npm invocations: install, run build, install -g .,
|
||||
// and two config get prefix calls. If any are still bare spawnSync
|
||||
// calls targeting npmCmd, the first assertion above already fails.
|
||||
// `\bspawnNpm\s*\(` matches real call sites only — a `const spawnNpm = (…)`
|
||||
// declaration does not match because `=` sits between name and `(`. A
|
||||
// `function spawnNpm(…)` declaration WOULD match, so subtract one for that
|
||||
// form. `explicitShellNpm` previously double-counted the wrapper's own
|
||||
// `spawnSync(npmCmd, args, { …, shell })` — when a helper exists, its body
|
||||
// is the only legitimate raw spawn and must be excluded.
|
||||
const wrapperCallMatches = (body.match(/\bspawnNpm\s*\(/g) || []).length;
|
||||
const hasArrowHelper = /\b(?:const|let|var)\s+spawnNpm\s*=/.test(body);
|
||||
const hasFunctionHelper = /\bfunction\s+spawnNpm\s*\(/.test(body);
|
||||
const hasHelper = hasArrowHelper || hasFunctionHelper;
|
||||
const wrapperCalls = hasFunctionHelper
|
||||
? Math.max(0, wrapperCallMatches - 1)
|
||||
: wrapperCallMatches;
|
||||
const explicitShellNpm = hasHelper
|
||||
? 0
|
||||
: (body.match(/spawnSync\s*\(\s*npmCmd[^)]*\bshell\s*:/g) || []).length;
|
||||
const total = wrapperCalls + explicitShellNpm;
|
||||
assert.ok(
|
||||
total >= 5,
|
||||
`installSdkIfNeeded should route at least 5 npm invocations through ` +
|
||||
`a shell-aware wrapper (install, run build, install -g ., and two ` +
|
||||
`config get prefix calls). Found ${total}.`
|
||||
);
|
||||
});
|
||||
|
||||
test('installSdkIfNeeded surfaces underlying spawnSync failure in fatal branches', () => {
|
||||
// Root cause of #2598 was invisible because `{ status: null, error: EINVAL }`
|
||||
// was reduced to a generic "Failed to `npm install` in sdk/." with no
|
||||
// diagnostic — stdio: 'inherit' had no child to stream and result.error was
|
||||
// dropped. Each of the three `emitSdkFatal` calls inside the install/build/
|
||||
// global-install failure paths must now thread spawn diagnostics (error,
|
||||
// signal, or numeric status) into the reason string so future regressions
|
||||
// print their real cause instead of failing silently.
|
||||
const body = installSdkBody();
|
||||
const hasFormatter = /formatSpawnFailure\s*\(/.test(body);
|
||||
const fatalCalls = body.match(/emitSdkFatal\s*\([^)]*`[^`]*`[^)]*\)/gs) || [];
|
||||
const fatalWithDiagnostic = fatalCalls.filter(
|
||||
(c) => /formatSpawnFailure|\.error|\.signal|\.status/.test(c),
|
||||
);
|
||||
assert.ok(
|
||||
hasFormatter,
|
||||
'installSdkIfNeeded must define a spawn-failure formatter so fatal ' +
|
||||
'npm failures surface result.error / result.signal / result.status ' +
|
||||
'instead of swallowing them (root cause of #2598 being invisible).',
|
||||
);
|
||||
assert.ok(
|
||||
fatalWithDiagnostic.length >= 3,
|
||||
`At least 3 emitSdkFatal calls (install, build, install -g .) must ` +
|
||||
`include spawn diagnostics. Found ${fatalWithDiagnostic.length} that ` +
|
||||
`reference formatSpawnFailure or result.error/signal/status.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -80,35 +80,42 @@ describe('#1657 / #2385: SDK install must be wired into installer source', () =>
|
||||
);
|
||||
});
|
||||
|
||||
test('install.js builds gsd-sdk from in-repo sdk/ source (#2385)', () => {
|
||||
test('install.js verifies prebuilt sdk/dist/cli.js instead of building from source (#2441)', () => {
|
||||
src = src || fs.readFileSync(INSTALL_SRC, 'utf-8');
|
||||
// The installer must locate the in-repo sdk/ directory, run the build,
|
||||
// and install it globally. We intentionally do NOT install
|
||||
// @gsd-build/sdk from npm because that published version lags the source
|
||||
// tree and shipping it breaks query handlers added since the last
|
||||
// publish.
|
||||
// As of fix/2441-sdk-decouple, the installer no longer runs `npm run build`
|
||||
// or `npm install -g .` from sdk/. Instead it verifies sdk/dist/cli.js exists
|
||||
// (shipped prebuilt in the tarball) and optionally chmods it.
|
||||
assert.ok(
|
||||
src.includes("path.resolve(__dirname, '..', 'sdk')") ||
|
||||
src.includes('path.resolve(__dirname, "..", "sdk")'),
|
||||
'installer must locate the in-repo sdk/ directory'
|
||||
src.includes('sdk/dist/cli.js') || src.includes("'dist', 'cli.js'"),
|
||||
'installer must reference sdk/dist/cli.js to verify the prebuilt dist (#2441)'
|
||||
);
|
||||
// Confirm the old build-from-source pattern is gone.
|
||||
const hasBuildFromSource =
|
||||
src.includes("['run', 'build']") &&
|
||||
src.includes("cwd: sdkDir");
|
||||
assert.ok(
|
||||
src.includes("'npm install -g .'") ||
|
||||
src.includes("['install', '-g', '.']"),
|
||||
'installer must run `npm install -g .` from sdk/ to install the built package globally'
|
||||
!hasBuildFromSource,
|
||||
'installer must NOT run `npm run build` from sdk/ at install time (#2441)'
|
||||
);
|
||||
const hasGlobalInstall =
|
||||
(src.includes("['install', '-g', '.']") || src.includes("'npm install -g .'")) &&
|
||||
src.includes("cwd: sdkDir");
|
||||
assert.ok(
|
||||
src.includes("['run', 'build']"),
|
||||
'installer must compile TypeScript via `npm run build` before installing globally'
|
||||
!hasGlobalInstall,
|
||||
'installer must NOT run `npm install -g .` from sdk/ (#2441)'
|
||||
);
|
||||
});
|
||||
|
||||
test('package.json ships sdk source in published tarball (#2385)', () => {
|
||||
test('package.json ships sdk dist and source in published tarball (#2441)', () => {
|
||||
const rootPkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
||||
const files = rootPkg.files || [];
|
||||
assert.ok(
|
||||
files.some((f) => f === 'sdk' || f.startsWith('sdk/')),
|
||||
'root package.json `files` must include sdk source so npm-registry installs can build gsd-sdk from source'
|
||||
files.some((f) => f === 'sdk/src' || f.startsWith('sdk/src')),
|
||||
'root package.json `files` must include sdk/src'
|
||||
);
|
||||
assert.ok(
|
||||
files.some((f) => f === 'sdk/dist' || f.startsWith('sdk/dist')),
|
||||
'root package.json `files` must include sdk/dist so the prebuilt CLI ships in the tarball (#2441)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user