dsh-keys-manager 0.2.1 → 0.2.2

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/locale/zh.json CHANGED
@@ -9,6 +9,12 @@
9
9
  "accountProjected": "服务中",
10
10
  "accountReauth": "重新认证",
11
11
  "accountActivate": "设为当前",
12
+ "testKey": "测试连接",
13
+ "testRunning": "测试中…",
14
+ "testPassed": "连接正常 · {ms} ms",
15
+ "testFailed": "连接失败:{error}",
16
+ "testTimedOut": "测试超时,未收到结果",
17
+ "testFailedUnknown": "测试未完成,未返回原因",
12
18
  "keyStatusEmpty": "暂无密钥",
13
19
  "noKeysAccount": "还没有账户。添加一个账户后即可使用该 Provider。",
14
20
  "loginTitle": "账户登录",
@@ -21,5 +27,15 @@
21
27
  "loginReady": "登录成功。",
22
28
  "deleteAccountTitle": "删除账户“{alias}”?",
23
29
  "deleteAccountBody": "这会从凭证库中删除该账户保存的 OAuth 授权。如果它是当前账户,该 Provider 路由将停用它并回落到 API-key 池;Provider 仍保持接管状态。",
24
- "deleteAccountBodyNoKeys": "这会从凭证库中删除该账户保存的 OAuth 授权。该路由没有 API-key 池,删除后将停止服务,直到重新登录账户。"
30
+ "deleteAccountBodyNoKeys": "这会从凭证库中删除该账户保存的 OAuth 授权。该路由没有 API-key 池,删除后将停止服务,直到重新登录账户。",
31
+ "transportAccounts": "传输账户",
32
+ "transportTag": "传输",
33
+ "transportSignIn": "登录",
34
+ "transportExpires": "有效期至 {time}",
35
+ "transportStatusActive": "正常",
36
+ "transportStatusExpired": "令牌已过期",
37
+ "transportStatusNeedsReauth": "需重新登录",
38
+ "transportStatusSignedOut": "未登录",
39
+ "transportRemoveTitle": "移除 {name} 传输账户?",
40
+ "transportRemoveBody": "这会从凭证库中删除该传输路由保存的授权,并取消仍在进行的登录;路由自身不会重建它。要恢复服务需要重新登录。"
25
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-keys-manager",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "description": "Multi-key management for DSH providers: sticky per-session round-robin, retry failover, cooldown recovery, and searchable extra providers.",
6
6
  "repository": {
@@ -54,15 +54,6 @@
54
54
  ]
55
55
  }
56
56
  },
57
- "scripts": {
58
- "typecheck": "tsc --noEmit && tsc -p tsconfig.client.json --noEmit && tsc -p tsconfig.spec.json --noEmit",
59
- "build": "tsc -p tsconfig.build.json && tsc -p tsconfig.client.json && node scripts/build-client.mjs && node scripts/verify-dist.mjs",
60
- "build:client": "tsc -p tsconfig.client.json && node scripts/build-client.mjs",
61
- "test": "vitest run",
62
- "release:prepare": "node scripts/prepare-release.mjs",
63
- "prepare": "pnpm run build",
64
- "prepack": "pnpm run build"
65
- },
66
57
  "engines": {
67
58
  "node": "^22.19 || >=24"
68
59
  },
@@ -80,6 +71,9 @@
80
71
  "@deepseek-ai/dsh-typert-registry": "^0.2.1-rc.2",
81
72
  "react": "^18.2.0"
82
73
  },
74
+ "dependencies": {
75
+ "dsh-llm-omp-transports": "^0.2.2"
76
+ },
83
77
  "devDependencies": {
84
78
  "@deepseek-ai/cordis": "^4.0.5",
85
79
  "@types/node": "^22.20.0",
@@ -87,6 +81,14 @@
87
81
  "esbuild": "^0.28.2",
88
82
  "react": "^18.3.1",
89
83
  "typescript": "^5.9.3",
90
- "vitest": "^4.1.8"
84
+ "vitest": "^4.1.8",
85
+ "yaml": "^2.9.1"
86
+ },
87
+ "scripts": {
88
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.client.json --noEmit && tsc -p tsconfig.spec.json --noEmit",
89
+ "build": "tsc -p tsconfig.build.json && tsc -p tsconfig.client.json && node scripts/build-client.mjs && node scripts/verify-dist.mjs",
90
+ "build:client": "tsc -p tsconfig.client.json && node scripts/build-client.mjs",
91
+ "test": "vitest run",
92
+ "release:prepare": "node scripts/prepare-release.mjs"
91
93
  }
92
- }
94
+ }
@@ -1,24 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Release-prep version helper (plan dsh-keys-manager-ci-release, task 2 + 4):
3
+ * Release-prep version helper (plans dsh-keys-manager-ci-release, task 2 + 4;
4
+ * omp-transport-cursor-tools, task 5):
4
5
  * resolves the target version — explicit `X.Y.Z` / `X.Y.Z-prerelease`
5
6
  * argument (e.g. 0.1.1-alpha.1) or an auto patch bump from package.json —
6
7
  * refuses already-released versions (existing `v<version>` git tag), aborts
7
8
  * when there are no commits since the previous release tag (nothing to
8
9
  * release), bumps package.json, inserts the `## [<version>]` section into
9
- * CHANGELOG.md (from the same git-log notes the workflows use; keeps
10
- * existing sections, no-op when already present), and prints
11
- * `VERSION=<version>` for the workflow to capture. Node built-ins only, no
12
- * new dependencies.
10
+ * CHANGELOG.md (root only — the transports package has no generated
11
+ * changelog), and prints the root and transports versions and tags for the
12
+ * workflow to capture. Node built-ins only, no new dependencies.
13
+ *
14
+ * The workspace package `packages/llm-omp` (`dsh-llm-omp-transports`) ships as
15
+ * part of the same release and always carries the same version: the resolved
16
+ * version is written to both package.json files, so one version input drives
17
+ * both tags (`v<version>` and `dsh-llm-omp-transports@<version>`). The
18
+ * transport version is checked against the tag the Release workflow creates
19
+ * after publishing, and the version written here is exactly the one
20
+ * `release.yml` publishes (that workflow skips an already-published version).
13
21
  *
14
22
  * Usage:
15
- * node scripts/prepare-release.mjs # auto patch bump
16
- * node scripts/prepare-release.mjs 0.2.0 # explicit version
17
- * node scripts/prepare-release.mjs 0.1.1-alpha.1 # explicit prerelease
23
+ * node scripts/prepare-release.mjs # auto bump
24
+ * node scripts/prepare-release.mjs 0.2.0 # explicit version
25
+ * node scripts/prepare-release.mjs --dry-run # validate + print, write nothing
18
26
  *
19
- * Exit codes: 0 = bumped and printed VERSION; 1 = rejected (unparseable
20
- * version, existing tag, no commits since the previous release tag,
21
- * unparseable current package.json version, or CHANGELOG.md write error).
27
+ * Exit codes: 0 = bumped (or dry-run resolved) and printed versions; 1 =
28
+ * rejected (unparseable version, existing tag, no commits since the previous
29
+ * release tag, unparseable current package.json version, invalid prerelease
30
+ * dist-tag, or CHANGELOG.md write error).
22
31
  */
23
32
 
24
33
  import { execFileSync } from 'node:child_process'
@@ -28,6 +37,8 @@ import { fileURLToPath } from 'node:url'
28
37
 
29
38
  const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
30
39
  const PKG_PATH = join(REPO_ROOT, 'package.json')
40
+ const TRANSPORTS_PKG_PATH = join(REPO_ROOT, 'packages/llm-omp/package.json')
41
+ const TRANSPORT_PACKAGE = 'dsh-llm-omp-transports'
31
42
  const CHANGELOG_PATH = join(REPO_ROOT, 'CHANGELOG.md')
32
43
  const CHANGELOG_HEADER =
33
44
  '# Changelog\n\nAll notable changes to dsh-keys-manager are documented here. Generated from git log by the release-prep workflow.\n'
@@ -53,82 +64,145 @@ function parseVersion(value) {
53
64
  }
54
65
  }
55
66
 
67
+ /**
68
+ * Refuse a prerelease whose derived npm dist-tag the release workflow would
69
+ * reject, for either package. The tag is the text between the first '-' and
70
+ * the first '.' after it ("alpha.1" -> "alpha").
71
+ */
72
+ function assertPublishablePrerelease(prerelease) {
73
+ if (prerelease === undefined) return
74
+ const distTag = prerelease.split('.')[0]
75
+ if (distTag === 'latest' || !/^[a-z][a-z0-9-]*$/.test(distTag)) {
76
+ console.error(
77
+ `Error: prerelease "${prerelease}" maps to invalid npm dist-tag "${distTag}". ` +
78
+ 'Use a prerelease such as alpha.1, beta.1, or rc.1.',
79
+ )
80
+ process.exit(1)
81
+ }
82
+ }
83
+
56
84
  const pkg = JSON.parse(readFileSync(PKG_PATH, 'utf8'))
85
+ const transportsPkg = JSON.parse(readFileSync(TRANSPORTS_PKG_PATH, 'utf8'))
57
86
 
58
- let target
59
- if (process.argv[2] !== undefined) {
60
- target = parseVersion(process.argv[2])
61
- if (target === null) {
62
- console.error(`Error: invalid version "${process.argv[2]}". Expected X.Y.Z or X.Y.Z-prerelease (e.g. 0.2.0, 0.1.1-alpha.1).`)
63
- process.exit(1)
87
+ const { versionArg, dryRun } = parseArgs(process.argv.slice(2))
88
+
89
+ const target = resolveTarget(versionArg, pkg.version, 'package.json')
90
+ const version = formatVersion(target)
91
+
92
+ // Validate the npm dist-tag derived by the release workflow. A prerelease
93
+ // identifier like "latest" would move the stable tag, and a numeric one like
94
+ // "1" is not a legal npm tag. Reject both before the release PR is opened.
95
+ assertPublishablePrerelease(target.prerelease)
96
+
97
+ // The transports package ships from the same version as the root: one input
98
+ // drives both package.json files, so the two can never drift.
99
+ const transportVersion = version
100
+ const transportTag = `${TRANSPORT_PACKAGE}@${transportVersion}`
101
+
102
+ /**
103
+ * Parse CLI flags. The first non-flag argument is the target version.
104
+ */
105
+ function parseArgs(argv) {
106
+ const parsed = { versionArg: undefined, dryRun: false }
107
+ for (let i = 0; i < argv.length; i += 1) {
108
+ const arg = argv[i]
109
+ if (arg === '--dry-run') parsed.dryRun = true
110
+ else if (arg.startsWith('--')) usage(`unknown option "${arg}"`)
111
+ else if (parsed.versionArg === undefined) parsed.versionArg = arg
112
+ else usage(`unexpected extra argument "${arg}"`)
113
+ }
114
+ return parsed
115
+ }
116
+
117
+ function usage(message) {
118
+ console.error(`Error: ${message}.`)
119
+ console.error(
120
+ 'Usage: node scripts/prepare-release.mjs [X.Y.Z[-prerelease]] [--dry-run]',
121
+ )
122
+ process.exit(1)
123
+ }
124
+
125
+ /**
126
+ * Resolve an explicit target version, or auto-bump the current one.
127
+ *
128
+ * Auto bump (mirrors sibling dsh-llm-fallbacks autoBumpPatch): parse the
129
+ * current version as X.Y.Z or X.Y.Z-pre, then:
130
+ * - formal version X.Y.Z -> X.Y.(Z+1) (0.1.2 -> 0.1.3)
131
+ * - prerelease with numeric tail -> bump only the tail, staying on the
132
+ * prerelease line (0.1.3-alpha.3 -> 0.1.3-alpha.4)
133
+ * - prerelease with non-numeric tail (e.g. 0.1.0-alpha) -> cannot auto
134
+ * bump; require an explicit version argument.
135
+ * The looser regex is deliberate: parseVersion (explicit args) requires a
136
+ * digit in the suffix, so it cannot tell "alpha" from a malformed version;
137
+ * this path needs the raw suffix to reject non-numeric tails with the
138
+ * explicit-version hint.
139
+ */
140
+ function resolveTarget(explicit, current, label) {
141
+ if (explicit !== undefined) {
142
+ const target = parseVersion(explicit)
143
+ if (target === null) {
144
+ console.error(
145
+ `Error: invalid version "${explicit}". Expected X.Y.Z or X.Y.Z-prerelease (e.g. 0.2.0, 0.1.1-alpha.1).`,
146
+ )
147
+ process.exit(1)
148
+ }
149
+ return target
64
150
  }
65
- } else {
66
- // Auto patch bump (mirrors sibling dsh-llm-fallbacks autoBumpPatch): parse
67
- // the current version as X.Y.Z or X.Y.Z-pre, then:
68
- // - formal version X.Y.Z -> X.Y.(Z+1) (0.1.2 -> 0.1.3)
69
- // - prerelease with numeric tail -> bump only the tail, staying on the
70
- // prerelease line (0.1.3-alpha.3 -> 0.1.3-alpha.4)
71
- // - prerelease with non-numeric tail (e.g. 0.1.0-alpha) -> cannot auto
72
- // bump; require an explicit version argument.
73
- // The looser regex is deliberate: parseVersion (explicit args) requires a
74
- // digit in the suffix, so it cannot tell "alpha" from a malformed version;
75
- // this path needs the raw suffix to reject non-numeric tails with the
76
- // explicit-version hint.
77
- const m = /^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/.exec(pkg.version)
151
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/.exec(current)
78
152
  if (!m) {
79
- console.error(`Error: cannot parse current package.json version "${pkg.version}".`)
153
+ console.error(`Error: cannot parse current ${label} version "${current}".`)
80
154
  process.exit(1)
81
155
  }
82
156
  const [, major, minor, patch, pre] = m
83
157
  if (pre === undefined) {
84
- target = { major: Number(major), minor: Number(minor), patch: Number(patch) + 1 }
85
- } else {
86
- const tail = pre.split('.').at(-1)
87
- if (tail === undefined || !/^\d+$/.test(tail)) {
88
- console.error(
89
- `Error: cannot auto-bump "${pkg.version}": prerelease tail "${pre}" is not numeric. ` +
90
- 'Pass an explicit version instead (e.g. `node scripts/prepare-release.mjs 0.1.1-alpha.1`).',
91
- )
92
- process.exit(1)
93
- }
94
- const prefix = pre.slice(0, pre.length - tail.length) // "alpha." for "alpha.1"
95
- target = {
96
- major: Number(major),
97
- minor: Number(minor),
98
- patch: Number(patch),
99
- prerelease: `${prefix}${Number(tail) + 1}`,
100
- }
158
+ return { major: Number(major), minor: Number(minor), patch: Number(patch) + 1 }
159
+ }
160
+ const tail = pre.split('.').at(-1)
161
+ if (tail === undefined || !/^\d+$/.test(tail)) {
162
+ console.error(
163
+ `Error: cannot auto-bump "${current}": prerelease tail "${pre}" is not numeric. ` +
164
+ 'Pass an explicit version instead (e.g. `node scripts/prepare-release.mjs 0.1.1-alpha.1`).',
165
+ )
166
+ process.exit(1)
167
+ }
168
+ const prefix = pre.slice(0, pre.length - tail.length) // "alpha." for "alpha.1"
169
+ return {
170
+ major: Number(major),
171
+ minor: Number(minor),
172
+ patch: Number(patch),
173
+ prerelease: `${prefix}${Number(tail) + 1}`,
101
174
  }
102
175
  }
103
176
 
104
- const version =
105
- target.prerelease === undefined
177
+ function formatVersion(target) {
178
+ return target.prerelease === undefined
106
179
  ? `${target.major}.${target.minor}.${target.patch}`
107
180
  : `${target.major}.${target.minor}.${target.patch}-${target.prerelease}`
181
+ }
108
182
 
109
- // Validate the npm dist-tag derived by the release workflow. A prerelease
110
- // identifier like "latest" would move the stable tag, and a numeric one like
111
- // "1" is not a legal npm tag. Reject both before the release PR is opened.
112
- if (target.prerelease !== undefined) {
113
- const distTag = target.prerelease.split('.')[0]
114
- if (distTag === 'latest' || !/^[a-z][a-z0-9-]*$/.test(distTag)) {
115
- console.error(
116
- `Error: prerelease "${target.prerelease}" maps to invalid npm dist-tag "${distTag}". ` +
117
- 'Use a prerelease such as alpha.1, beta.1, or rc.1.',
118
- )
119
- process.exit(1)
183
+ /** Whether an exact tag ref exists. */
184
+ function tagExists(tag) {
185
+ try {
186
+ execFileSync('git', ['rev-parse', '--verify', '--quiet', `refs/tags/${tag}`], {
187
+ cwd: REPO_ROOT,
188
+ stdio: 'ignore',
189
+ })
190
+ return true
191
+ } catch {
192
+ return false
120
193
  }
121
194
  }
122
195
 
123
- // Reject already-released versions: `git rev-parse v<version>` succeeds iff
124
- // the tag exists. Works unchanged for prerelease tags (v0.1.1-alpha.1 is a
125
- // plain tag name). Run from REPO_ROOT so the check is cwd-independent.
126
- try {
127
- execFileSync('git', ['rev-parse', `v${version}`], { cwd: REPO_ROOT, stdio: 'ignore' })
196
+ // Reject already-released versions: the tag exists iff the version was
197
+ // released. Works unchanged for prerelease tags (v0.1.1-alpha.1 is a plain
198
+ // tag name). Run from REPO_ROOT so the check is cwd-independent.
199
+ if (tagExists(`v${version}`)) {
128
200
  console.error(`Error: tag v${version} already exists — this version is already released.`)
129
201
  process.exit(1)
130
- } catch {
131
- // Tag absent → OK to prepare.
202
+ }
203
+ if (tagExists(transportTag)) {
204
+ console.error(`Error: tag ${transportTag} already exists — this transport version is already released.`)
205
+ process.exit(1)
132
206
  }
133
207
 
134
208
  // Fail fast on a no-change release: an empty notes range means no commits
@@ -144,11 +218,17 @@ if (notes === '') {
144
218
  process.exit(1)
145
219
  }
146
220
 
147
- pkg.version = version
148
- // Preserve repo formatting: 2-space indent + trailing newline. The version
149
- // line must be the only diff (verified by `git diff package.json` in the
150
- // dry-run validation).
151
- writeFileSync(PKG_PATH, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8')
221
+ // Write only outside a dry run: a dry run validates and prints the resolved
222
+ // versions exactly like a real run, then leaves the working tree untouched.
223
+ if (!dryRun) {
224
+ pkg.version = version
225
+ // Preserve repo formatting: 2-space indent + trailing newline. Bumping only
226
+ // the version keeps `git diff package.json` to that one line.
227
+ writeFileSync(PKG_PATH, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8')
228
+
229
+ transportsPkg.version = transportVersion
230
+ writeFileSync(TRANSPORTS_PKG_PATH, `${JSON.stringify(transportsPkg, null, 2)}\n`, 'utf8')
231
+ }
152
232
 
153
233
  /**
154
234
  * Resolve the previous release tag (nearest ancestor tag of HEAD; empty
@@ -238,6 +318,13 @@ function updateChangelog(version, notes) {
238
318
  }
239
319
  }
240
320
 
241
- updateChangelog(version, notes)
321
+ // Root changelog only: the transports package has no generated changelog.
322
+ if (!dryRun) updateChangelog(version, notes)
242
323
 
324
+ // The workflow captures VERSION; the remaining lines are informational and
325
+ // let a dry run show both derived tags without touching the tree.
326
+ if (dryRun) console.log('DRY_RUN=1')
243
327
  console.log(`VERSION=${version}`)
328
+ console.log(`TAG=v${version}`)
329
+ console.log(`TRANSPORT_VERSION=${transportVersion}`)
330
+ console.log(`TRANSPORT_TAG=${transportTag}`)
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Install/load integration evidence.
3
+ *
4
+ * Proves: packing this repository and installing the root tarball into a fresh
5
+ * DSH profile yields BOTH bundle components — the profile's node_modules
6
+ * resolves `dsh-keys-manager` and `dsh-llm-omp-transports`, and the composed
7
+ * profile tree (`dsh --profile <name> --dump-config`) contains both rows, so
8
+ * DSH's loader can mount two components from one installed plugin.
9
+ *
10
+ * Does NOT prove: the plugin-manager detail page and its enable/disable
11
+ * toggles (that rendering is DSH's own Web UI), or any Cursor network behavior.
12
+ *
13
+ * The run is fully local and needs no registry access: both tarballs come from
14
+ * `pnpm pack`, the root's `dsh-llm-omp-transports@^0.1.0` dependency is mapped
15
+ * to the local transports tarball through a profile override, and the profile
16
+ * keeps `autoInstallPeers: false` (DSH peers are provided by the installation).
17
+ * On this pnpm 12 the override lives in pnpm-workspace.yaml; the package.json
18
+ * `pnpm.overrides` copy keeps pnpm 9/10 working.
19
+ *
20
+ * Requires `pnpm` and `dsh` on PATH. Usage: node scripts/verify-install.mjs [--keep]
21
+ */
22
+ import { execFile } from 'node:child_process'
23
+ import { access, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
24
+ import { tmpdir } from 'node:os'
25
+ import { dirname, join } from 'node:path'
26
+ import { fileURLToPath } from 'node:url'
27
+ import { promisify } from 'node:util'
28
+
29
+ const execFileAsync = promisify(execFile)
30
+ const repoRoot = dirname(dirname(fileURLToPath(import.meta.url)))
31
+ const transportsDir = join(repoRoot, 'packages/llm-omp')
32
+ const TRANSPORTS = 'dsh-llm-omp-transports'
33
+ const PROFILE = 'install-proof'
34
+ /** The two loader rows the composed profile must carry. */
35
+ const ROWS = [
36
+ { id: 'dsh-keys-manager', name: 'dsh-keys-manager' },
37
+ { id: 'llm-omp-transports', name: TRANSPORTS },
38
+ ]
39
+
40
+ async function run(command, args, options = {}) {
41
+ try {
42
+ return await execFileAsync(command, args, {
43
+ encoding: 'utf8',
44
+ maxBuffer: 64 * 1024 * 1024,
45
+ ...options,
46
+ })
47
+ } catch (error) {
48
+ const output = [error.stdout, error.stderr].filter(Boolean).join('\n')
49
+ throw new Error(`${command} ${args.join(' ')} failed (${error.code ?? 'error'}):\n${output}`)
50
+ }
51
+ }
52
+
53
+ async function manifestOf(dir) {
54
+ return JSON.parse(await readFile(join(dir, 'package.json'), 'utf8'))
55
+ }
56
+
57
+ /** One loader row, quoted or plain, exactly once in the dumped tree. */
58
+ function assertRow(dump, { id, name }) {
59
+ const row = new RegExp(`^- id: ${id}\\n name: '?${name}'?$`, 'mu')
60
+ if (!row.test(dump)) throw new Error(`dump-config is missing the ${id} row:\n${dump}`)
61
+ const count = dump.split(`- id: ${id}\n`).length - 1
62
+ if (count !== 1) throw new Error(`dump-config carries the ${id} row ${count} times:\n${dump}`)
63
+ }
64
+
65
+ let work
66
+ try {
67
+ for (const command of ['pnpm', 'dsh']) await run(command, ['--version'])
68
+
69
+ work = await mkdtemp(join(tmpdir(), 'dsh-install-proof-'))
70
+ const artifacts = join(work, 'artifacts')
71
+ await mkdir(artifacts, { recursive: true })
72
+
73
+ const root = await manifestOf(repoRoot)
74
+ const transports = await manifestOf(transportsDir)
75
+ await run('pnpm', ['pack', '--pack-destination', artifacts], { cwd: repoRoot })
76
+ await run('pnpm', ['pack', '--pack-destination', artifacts], { cwd: transportsDir })
77
+ const rootTarball = join(artifacts, `${root.name}-${root.version}.tgz`)
78
+ const transportsTarball = join(artifacts, `${TRANSPORTS}-${transports.version}.tgz`)
79
+ await access(rootTarball)
80
+ await access(transportsTarball)
81
+
82
+ // A packed workspace dependency must be a registry range, not `workspace:^`.
83
+ // The transports package ships from the same release version, so the
84
+ // expected range follows its manifest rather than a pinned literal.
85
+ const expectedRange = `^${transports.version}`
86
+ const packed = JSON.parse((await run('tar', ['-xzOf', rootTarball, 'package/package.json'])).stdout)
87
+ const pinned = packed.dependencies?.[TRANSPORTS]
88
+ if (pinned !== expectedRange) {
89
+ throw new Error(`packed root dependency ${TRANSPORTS} is ${JSON.stringify(pinned)}, expected ${JSON.stringify(expectedRange)}`)
90
+ }
91
+ console.log(`install-proof: packed dependency ${TRANSPORTS} -> ${pinned}`)
92
+
93
+ // A fresh profile: `dsh plugin install` initializes it (and runs pnpm).
94
+ const home = join(work, 'home')
95
+ const profileDir = join(home, 'profiles', PROFILE)
96
+ const env = { ...process.env, DSH_HOME: home }
97
+ await run('dsh', ['plugin', '--profile', PROFILE, 'install'], { env })
98
+
99
+ // Map the root's registry dependency to the local tarball before installing.
100
+ const profileManifest = await manifestOf(profileDir)
101
+ profileManifest.pnpm = {
102
+ ...profileManifest.pnpm,
103
+ overrides: { ...profileManifest.pnpm?.overrides, [TRANSPORTS]: `file:${transportsTarball}` },
104
+ }
105
+ await writeFile(join(profileDir, 'package.json'), `${JSON.stringify(profileManifest, undefined, 2)}\n`)
106
+ await writeFile(
107
+ join(profileDir, 'pnpm-workspace.yaml'),
108
+ [
109
+ '# Local tarball mapping for the install proof; the packed root asks for',
110
+ `# ${TRANSPORTS}@${expectedRange}, which no registry here serves.`,
111
+ 'overrides:',
112
+ ` ${TRANSPORTS}: ${JSON.stringify(`file:${transportsTarball}`)}`,
113
+ 'packages:',
114
+ ' - .',
115
+ '',
116
+ 'nodeLinker: hoisted',
117
+ 'autoInstallPeers: false',
118
+ '',
119
+ ].join('\n'),
120
+ )
121
+
122
+ // The alias form (`name@file:...`) is what pnpm 12 accepts for tarballs.
123
+ await run('dsh', ['plugin', '--profile', PROFILE, 'add', `${root.name}@file:${rootTarball}`], {
124
+ cwd: work,
125
+ env,
126
+ })
127
+
128
+ const installed = await manifestOf(profileDir)
129
+ if (installed.dependencies?.[root.name] !== `file:${rootTarball}`) {
130
+ throw new Error(`profile does not depend on the packed root: ${JSON.stringify(installed.dependencies)}`)
131
+ }
132
+ if (!installed.dsh?.profile?.bundles?.includes(root.name)) {
133
+ throw new Error(`profile did not select ${root.name} as a bundle: ${JSON.stringify(installed.dsh)}`)
134
+ }
135
+ const versions = []
136
+ for (const name of [root.name, TRANSPORTS]) {
137
+ const manifest = JSON.parse(await readFile(join(profileDir, 'node_modules', name, 'package.json'), 'utf8'))
138
+ if (manifest.name !== name) throw new Error(`${name} resolves to ${JSON.stringify(manifest.name)}`)
139
+ versions.push(`${name}@${manifest.version}`)
140
+ }
141
+ console.log(`install-proof: both components installed: ${versions.join(' + ')}`)
142
+
143
+ const dump = (await run('dsh', ['--profile', PROFILE, '--dump-config'], { env })).stdout
144
+ for (const row of ROWS) assertRow(dump, row)
145
+ console.log('install-proof: dump-config rows:')
146
+ for (const { id, name } of ROWS) console.log(` - id: ${id}\n name: ${name}`)
147
+ console.log('install-proof: OK')
148
+ } catch (error) {
149
+ console.error(`install-proof: FAILED: ${error instanceof Error ? error.message : String(error)}`)
150
+ process.exitCode = 1
151
+ } finally {
152
+ if (work !== undefined) {
153
+ if (process.argv.includes('--keep')) console.log(`install-proof: kept ${work}`)
154
+ else await rm(work, { recursive: true, force: true })
155
+ }
156
+ }