@1aboveio/skills 1.0.8 → 1.0.10

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.
Files changed (214) hide show
  1. package/.codex-plugin/plugin.json +13 -0
  2. package/.codex-plugin/resources.json +1827 -0
  3. package/README.md +22 -0
  4. package/package.json +9 -2
  5. package/skills/app-dev/airflow/SKILL.md +1 -1
  6. package/skills/app-dev/airflow/release-check.mjs +3 -1
  7. package/skills/app-dev/android-foundation/SKILL.md +1 -1
  8. package/skills/app-dev/android-foundation/release-check.mjs +3 -1
  9. package/skills/app-dev/app-debug/SKILL.md +1 -1
  10. package/skills/app-dev/app-debug/release-check.mjs +3 -1
  11. package/skills/app-dev/better-auth/SKILL.md +1 -1
  12. package/skills/app-dev/better-auth/release-check.mjs +3 -1
  13. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  14. package/skills/app-dev/data-catalog/release-check.mjs +3 -1
  15. package/skills/app-dev/data-quality/SKILL.md +1 -1
  16. package/skills/app-dev/data-quality/release-check.mjs +3 -1
  17. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  18. package/skills/app-dev/ios-foundation/release-check.mjs +3 -1
  19. package/skills/app-dev/monorepo/SKILL.md +1 -1
  20. package/skills/app-dev/monorepo/release-check.mjs +3 -1
  21. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  22. package/skills/app-dev/nextjs-fullstack/release-check.mjs +3 -1
  23. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  24. package/skills/app-dev/prisma-setup/release-check.mjs +3 -1
  25. package/skills/app-dev/pyspark/SKILL.md +1 -1
  26. package/skills/app-dev/pyspark/release-check.mjs +3 -1
  27. package/skills/app-dev/python-backend/SKILL.md +1 -1
  28. package/skills/app-dev/python-backend/release-check.mjs +3 -1
  29. package/skills/app-dev/shadcn/SKILL.md +1 -1
  30. package/skills/app-dev/shadcn/release-check.mjs +3 -1
  31. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  32. package/skills/app-dev/zod-v4/release-check.mjs +3 -1
  33. package/skills/cicd/cloud-build/SKILL.md +1 -1
  34. package/skills/cicd/cloud-build/release-check.mjs +3 -1
  35. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  36. package/skills/cicd/cloud-debug/release-check.mjs +3 -1
  37. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  38. package/skills/cicd/cloud-deploy/release-check.mjs +3 -1
  39. package/skills/cicd/docker/SKILL.md +1 -1
  40. package/skills/cicd/docker/release-check.mjs +3 -1
  41. package/skills/cicd/github-actions/SKILL.md +1 -1
  42. package/skills/cicd/github-actions/release-check.mjs +3 -1
  43. package/skills/cicd/github-flow/SKILL.md +1 -1
  44. package/skills/cicd/github-flow/release-check.mjs +3 -1
  45. package/skills/cicd/google-cloud/SKILL.md +1 -1
  46. package/skills/cicd/google-cloud/release-check.mjs +3 -1
  47. package/skills/cicd/mergify/SKILL.md +1 -1
  48. package/skills/cicd/mergify/release-check.mjs +3 -1
  49. package/skills/cicd/podman/SKILL.md +1 -1
  50. package/skills/cicd/podman/release-check.mjs +3 -1
  51. package/skills/content/article-review/SKILL.md +1 -1
  52. package/skills/content/article-review/release-check.mjs +3 -1
  53. package/skills/content/article-to-image/SKILL.md +1 -1
  54. package/skills/content/article-to-image/release-check.mjs +3 -1
  55. package/skills/content/flowchart-svg/SKILL.md +1 -1
  56. package/skills/content/flowchart-svg/release-check.mjs +3 -1
  57. package/skills/content/image-gen/SKILL.md +1 -1
  58. package/skills/content/image-gen/release-check.mjs +3 -1
  59. package/skills/content/mermaid/SKILL.md +1 -1
  60. package/skills/content/mermaid/release-check.mjs +3 -1
  61. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  62. package/skills/content/wechat-article-writer/release-check.mjs +3 -1
  63. package/skills/content/wechat-publisher/SKILL.md +1 -1
  64. package/skills/content/wechat-publisher/release-check.mjs +3 -1
  65. package/skills/content/wechat-search/SKILL.md +1 -1
  66. package/skills/content/wechat-search/release-check.mjs +3 -1
  67. package/skills/content/xai-search/SKILL.md +88 -13
  68. package/skills/content/xai-search/release-check.mjs +3 -1
  69. package/skills/content/xai-search/scripts/xai_search.sh +125 -149
  70. package/skills/core/distill-writing/SKILL.md +1 -1
  71. package/skills/core/distill-writing/release-check.mjs +3 -1
  72. package/skills/core/doc-to-md/SKILL.md +1 -1
  73. package/skills/core/doc-to-md/release-check.mjs +3 -1
  74. package/skills/core/editorial-review/SKILL.md +1 -1
  75. package/skills/core/editorial-review/release-check.mjs +3 -1
  76. package/skills/core/house-style/SKILL.md +1 -1
  77. package/skills/core/house-style/release-check.mjs +3 -1
  78. package/skills/core/plain-language/SKILL.md +1 -1
  79. package/skills/core/plain-language/release-check.mjs +3 -1
  80. package/skills/core/polish-cn/SKILL.md +1 -1
  81. package/skills/core/polish-cn/release-check.mjs +3 -1
  82. package/skills/core/reg-parser/SKILL.md +1 -1
  83. package/skills/core/reg-parser/release-check.mjs +3 -1
  84. package/skills/core/terminology/SKILL.md +1 -1
  85. package/skills/core/terminology/release-check.mjs +3 -1
  86. package/skills/core/write-doc/SKILL.md +1 -1
  87. package/skills/core/write-doc/release-check.mjs +3 -1
  88. package/skills/engineering/better-review/SKILL.md +1 -1
  89. package/skills/engineering/better-review/release-check.mjs +3 -1
  90. package/skills/engineering/contributing/SKILL.md +1 -1
  91. package/skills/engineering/contributing/release-check.mjs +3 -1
  92. package/skills/engineering/e2e-test/SKILL.md +1 -1
  93. package/skills/engineering/e2e-test/release-check.mjs +3 -1
  94. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  95. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  96. package/skills/engineering/engineering-runtime/release-check.mjs +3 -1
  97. package/skills/engineering/engineering-runtime/scripts/installation-resolver.mjs +191 -0
  98. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +51 -10
  99. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  100. package/skills/engineering/ensure-coverage/release-check.mjs +3 -1
  101. package/skills/engineering/golden-path/SKILL.md +1 -1
  102. package/skills/engineering/golden-path/release-check.mjs +3 -1
  103. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  104. package/skills/engineering/harness-runtime/release-check.mjs +3 -1
  105. package/skills/engineering/resolve-issues/SKILL.md +1 -1
  106. package/skills/engineering/resolve-issues/references/preflight.md +7 -0
  107. package/skills/engineering/resolve-issues/references/shared-modules.md +27 -0
  108. package/skills/engineering/resolve-issues/references/task-plan.md +5 -1
  109. package/skills/engineering/resolve-issues/release-check.mjs +3 -1
  110. package/skills/engineering/resolve-issues/scripts/active-installation.mjs +32 -0
  111. package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +3 -0
  112. package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +17 -1
  113. package/skills/engineering/resolve-release/SKILL.md +1 -1
  114. package/skills/engineering/resolve-release/release-check.mjs +3 -1
  115. package/skills/engineering/rush-issues/SKILL.md +1 -1
  116. package/skills/engineering/rush-issues/release-check.mjs +3 -1
  117. package/skills/engineering/rush-release/SKILL.md +1 -1
  118. package/skills/engineering/rush-release/release-check.mjs +3 -1
  119. package/skills/engineering/test-quality/SKILL.md +1 -1
  120. package/skills/engineering/test-quality/release-check.mjs +3 -1
  121. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  122. package/skills/risk-compliance/compliance-audit/release-check.mjs +3 -1
  123. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  124. package/skills/risk-compliance/contract-compare/release-check.mjs +3 -1
  125. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  126. package/skills/risk-compliance/contract-review/release-check.mjs +3 -1
  127. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  128. package/skills/risk-compliance/doc-authenticity/release-check.mjs +3 -1
  129. package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
  130. package/skills/risk-compliance/fraud-analysis/release-check.mjs +3 -1
  131. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  132. package/skills/risk-compliance/payment-analysis/release-check.mjs +3 -1
  133. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  134. package/skills/risk-compliance/similar-domain/release-check.mjs +3 -1
  135. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
  136. package/skills/risk-compliance/tld-detection/release-check.mjs +3 -1
  137. package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +0 -436
  138. package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +0 -36
  139. package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +0 -12
  140. package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +0 -637
  141. package/skills/cicd/github-flow/scripts/flow.test.mjs +0 -114
  142. package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +0 -108
  143. package/skills/cicd/mergify/scripts/audit.test.mjs +0 -201
  144. package/skills/cicd/mergify/scripts/entrypoints.test.mjs +0 -106
  145. package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +0 -30
  146. package/skills/cicd/mergify/scripts/stack-push.test.mjs +0 -233
  147. package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +0 -1826
  148. package/skills/content/image-gen/scripts/credentials.test.mjs +0 -212
  149. package/skills/content/image-gen/scripts/options.test.mjs +0 -245
  150. package/skills/content/xai-search/tests/smoke_test.sh +0 -36
  151. package/skills/core/polish-cn/scripts/polish-cn.test.mjs +0 -244
  152. package/skills/core/reg-parser/scripts/reg-parser.test.mjs +0 -165
  153. package/skills/engineering/better-review/scripts/pr-context.test.mjs +0 -168
  154. package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +0 -587
  155. package/skills/engineering/better-review/scripts/scan-diff.test.mjs +0 -414
  156. package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +0 -96
  157. package/skills/engineering/contributing/scripts/persist-conventions.test.mjs +0 -118
  158. package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +0 -106
  159. package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +0 -117
  160. package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +0 -69
  161. package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +0 -98
  162. package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +0 -71
  163. package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +0 -59
  164. package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +0 -76
  165. package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +0 -68
  166. package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +0 -95
  167. package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +0 -288
  168. package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +0 -96
  169. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +0 -96
  170. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +0 -47
  171. package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +0 -86
  172. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +0 -447
  173. package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +0 -552
  174. package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +0 -289
  175. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +0 -119
  176. package/skills/engineering/golden-path/scripts/golden-path.test.mjs +0 -1457
  177. package/skills/engineering/harness-runtime/discover-models.test.mjs +0 -363
  178. package/skills/engineering/harness-runtime/harness-runtime.test.mjs +0 -967
  179. package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +0 -829
  180. package/skills/engineering/harness-runtime/model-catalog.test.mjs +0 -366
  181. package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +0 -280
  182. package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +0 -346
  183. package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +0 -88
  184. package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +0 -264
  185. package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +0 -79
  186. package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +0 -107
  187. package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +0 -14
  188. package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +0 -149
  189. package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +0 -915
  190. package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +0 -156
  191. package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +0 -448
  192. package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +0 -217
  193. package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +0 -91
  194. package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +0 -1060
  195. package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +0 -458
  196. package/skills/engineering/resolve-release/scripts/apply.test.mjs +0 -99
  197. package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +0 -100
  198. package/skills/engineering/resolve-release/scripts/flow.test.mjs +0 -340
  199. package/skills/engineering/resolve-release/scripts/green-head.test.mjs +0 -101
  200. package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +0 -426
  201. package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +0 -659
  202. package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +0 -317
  203. package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +0 -291
  204. package/skills/engineering/resolve-release/scripts/plan.test.mjs +0 -428
  205. package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +0 -591
  206. package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +0 -119
  207. package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +0 -104
  208. package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +0 -14
  209. package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +0 -12
  210. package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +0 -212
  211. package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +0 -14
  212. package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +0 -14
  213. package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +0 -134
  214. package/skills/risk-compliance/tld-detection/tests/test_detect_streams.py +0 -520
@@ -1,659 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { createHash } from 'node:crypto'
4
- import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
5
- import { tmpdir } from 'node:os'
6
- import { join } from 'node:path'
7
- import { spawnSync } from 'node:child_process'
8
- import { planRelease } from './plan.mjs'
9
- import { probeNpmVersionAbsence } from './npm-version-absence.mjs'
10
-
11
- const version = '1.3.0'
12
- const now = new Date('2026-10-19T12:00:00Z')
13
- const original = '## [1.3.0] - 2026-10-18\n\n- Original reviewed notes.\n'
14
- const reviewed = '## [1.3.0] - 2026-10-18\n\n- Corrected reviewed notes.\n'
15
- const digest = (bytes) => createHash('sha256').update(bytes).digest('hex')
16
- const json = (value) => `${JSON.stringify(value, null, 2)}\n`
17
-
18
- function fixture(t, { name = '@example/demo', format = 3, lock = true, preparedVersion = version, transform = (files) => files } = {}) {
19
- const base = mkdtempSync(join(tmpdir(), 'prepared-npm-'))
20
- t.after(() => rmSync(base, { recursive: true, force: true }))
21
- const cwd = join(base, 'repo')
22
- const remote = join(base, 'origin.git')
23
- const home = join(base, 'home')
24
- for (const directory of [cwd, remote, home]) mkdirSync(directory)
25
- const env = {
26
- PATH: process.env.PATH, HOME: home, TMPDIR: base, LC_ALL: 'C', TZ: 'UTC',
27
- GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null', GIT_ATTR_NOSYSTEM: '1',
28
- GIT_ALLOW_PROTOCOL: 'file', GIT_TERMINAL_PROMPT: '0',
29
- GIT_CONFIG_COUNT: '1', GIT_CONFIG_KEY_0: 'core.hooksPath', GIT_CONFIG_VALUE_0: '/dev/null',
30
- GIT_AUTHOR_DATE: now.toISOString(), GIT_COMMITTER_DATE: now.toISOString(),
31
- }
32
- const gitAt = (directory, ...args) => {
33
- const result = spawnSync('git', args, { cwd: directory, env, timeout: 10_000, maxBuffer: 16 * 1024 * 1024 })
34
- assert.equal(result.status, 0, result.stderr?.toString('utf8'))
35
- return result.stdout
36
- }
37
- const bytes = (...args) => gitAt(cwd, ...args)
38
- const git = (...args) => bytes(...args).toString('utf8').trim()
39
- git('init', '--template=', '-b', 'main')
40
- git('config', 'user.email', 'fixture@example.invalid')
41
- git('config', 'user.name', 'Offline Fixture')
42
- git('config', 'commit.gpgsign', 'false')
43
- git('config', 'tag.gpgsign', 'false')
44
- gitAt(remote, 'init', '--bare', '--template=')
45
- git('remote', 'add', 'origin', 'https://github.com/example/demo.git')
46
- git('config', `url.${remote}.insteadOf`, 'https://github.com/example/demo.git')
47
- const snapshots = {}
48
- const commit = (stage, releaseVersion) => {
49
- const pkg = { name, version: releaseVersion, scripts: { test: 'node --test' }, dependencies: { dep: '^1.0.0' } }
50
- const files = {
51
- 'package.json': json(pkg), VERSION: `${releaseVersion}\n`,
52
- 'CHANGELOG.md': `# Changelog\n\n${stage === 'target' ? reviewed : stage === 'parent' ? '## [1.2.0] - 2026-10-01\n\n- Old release.\n' : original}`,
53
- 'index.js': stage === 'target' ? 'export const fixed = true\n' : 'export const fixed = false\n',
54
- }
55
- if (lock) files['package-lock.json'] = json({
56
- name, version: releaseVersion, lockfileVersion: format,
57
- packages: {
58
- '': { name, version: releaseVersion, dependencies: { dep: '^1.0.0' } },
59
- 'node_modules/dep': { version: '1.0.0', resolved: 'https://invalid.example/dep.tgz', integrity: 'sha512-fixture' },
60
- },
61
- })
62
- files['CHANGELOG.md'] = files['CHANGELOG.md'].replaceAll(version, preparedVersion)
63
- const changed = transform(files, stage)
64
- for (const file of Object.keys(files)) if (!Object.hasOwn(changed, file)) rmSync(join(cwd, file), { force: true })
65
- for (const [file, content] of Object.entries(changed)) writeFileSync(join(cwd, file), content)
66
- git('add', '.')
67
- git('commit', '--allow-empty', '-m', `chore: ${stage}`)
68
- snapshots[stage] = Object.fromEntries(Object.keys(changed).map((file) => [file, readFileSync(join(cwd, file))]))
69
- return git('rev-parse', 'HEAD')
70
- }
71
- const parentSha = commit('parent', '1.2.0')
72
- git('tag', 'v1.2.0')
73
- git('switch', '-c', 'metadata')
74
- const cutSha = commit('cut', preparedVersion)
75
- git('switch', 'main')
76
- git('merge', '--no-ff', 'metadata', '-m', 'Merge reviewed metadata')
77
- const mergeSha = git('rev-parse', 'HEAD')
78
- const sourceSha = commit('previous', preparedVersion)
79
- const sha = commit('target', preparedVersion)
80
- git('push', 'origin', 'main', '--tags')
81
- const receipt = join(cwd, '.git', 'resolve-release-authorization.json')
82
- const receiptBytes = Buffer.from('historical receipt: no authority for this attempt\n')
83
- writeFileSync(receipt, receiptBytes)
84
- const preparedMetadata = {
85
- schema: 'prepared-release-metadata/v2', repository: 'example/demo', version: preparedVersion, metadataPr: 42, cutSha, mergeSha,
86
- previousAttempt: { kind: 'pre-tag', receiptId: 'failed-pre-tag-attempt', receiptSha256: digest(receiptBytes), sourceSha },
87
- }
88
- const plan = (options = {}) => {
89
- const inherited = process.env
90
- process.env = env
91
- try { return planRelease({ cwd, sha, preparedMetadata, now, ...options }) } finally { process.env = inherited }
92
- }
93
- const cli = (options = {}) => {
94
- const input = join(cwd, '.git', 'prepared.json')
95
- writeFileSync(input, json(options.input ?? preparedMetadata))
96
- return spawnSync(process.execPath, [join(import.meta.dirname, 'plan.mjs'), '--sha', options.sha ?? sha,
97
- '--prepared-metadata', input, '--json', ...(options.args ?? [])], {
98
- cwd, env: options.env ?? env, encoding: 'utf8', timeout: 20_000,
99
- })
100
- }
101
- const unchanged = () => {
102
- const refs = git('show-ref')
103
- const remoteRefs = gitAt(remote, 'show-ref')
104
- const status = git('status', '--porcelain')
105
- const head = git('rev-parse', 'HEAD')
106
- const files = Object.fromEntries(Object.keys(snapshots.target).map((file) => [file, readFileSync(join(cwd, file))]))
107
- return () => {
108
- assert.equal(git('show-ref'), refs)
109
- assert.deepEqual(gitAt(remote, 'show-ref'), remoteRefs)
110
- assert.equal(git('status', '--porcelain'), status)
111
- assert.equal(git('rev-parse', 'HEAD'), head)
112
- for (const [file, content] of Object.entries(files)) assert.deepEqual(readFileSync(join(cwd, file)), content)
113
- assert.deepEqual(readFileSync(receipt), receiptBytes)
114
- }
115
- }
116
- return { cwd, remote, env, git, bytes, gitAt, sha, sourceSha, parentSha, cutSha, mergeSha, snapshots,
117
- receipt, receiptBytes, preparedMetadata, plan, cli, unchanged }
118
- }
119
-
120
- function editJson(files, file, change) {
121
- const value = JSON.parse(files[file])
122
- change(value)
123
- return { ...files, [file]: json(value) }
124
- }
125
-
126
- test('genuine pre-tag npm reuse needs no RC and preserves frozen source, provenance and repository bytes via API and CLI', (t) => {
127
- const f = fixture(t)
128
- assert.equal(f.git('tag', '--list', '*-rc.*'), '')
129
- assert.equal(f.gitAt(f.remote, 'tag', '--list', '*-rc.*').length, 0)
130
- const verify = f.unchanged()
131
- const plan = f.plan()
132
- assert.equal(plan.sha, f.sha)
133
- assert.equal(plan.releaseSourceSha, f.sha)
134
- assert.equal(plan.bump, 'none')
135
- assert.equal(plan.currentVersion, version)
136
- assert.equal(plan.nextVersion, version)
137
- assert.equal(plan.tag, `v${version}`)
138
- assert.equal(plan.candidateTag, null)
139
- assert.deepEqual(plan.artifact, { kind: 'npm', packageName: '@example/demo', version })
140
- assert.deepEqual(plan.versionFiles, ['package.json', 'package-lock.json', 'VERSION'])
141
- assert.equal(plan.changelogMarkdown, reviewed)
142
- assert.equal(plan.metadata.originalSectionSha256, digest(original))
143
- assert.equal(plan.metadata.preparedSectionSha256, digest(reviewed))
144
- assert.equal(plan.metadata.mode, 'reuse-prepared')
145
- assert.equal(plan.metadata.schema, f.preparedMetadata.schema)
146
- assert.deepEqual(plan.metadata.previousAttempt, f.preparedMetadata.previousAttempt)
147
- assert.deepEqual(plan.metadata.remoteInspection, {
148
- remote: 'origin', finalTagAbsent: `v${version}`, candidateTagAbsent: null, inspectedAt: now.toISOString(),
149
- })
150
- assert.notEqual(plan.receiptId, f.preparedMetadata.previousAttempt.receiptId)
151
- assert.notEqual(f.plan().receiptId, plan.receiptId)
152
- const result = f.cli()
153
- assert.equal(result.status, 0, result.stderr)
154
- const output = JSON.parse(result.stdout)
155
- assert.deepEqual(output.artifact, plan.artifact)
156
- assert.deepEqual(output.metadata.previousAttempt, f.preparedMetadata.previousAttempt)
157
- assert.equal(output.releaseSourceSha, f.sha)
158
- assert.equal(output.candidateTag, null)
159
- verify()
160
- writeFileSync(join(f.cwd, 'package.json'), json({ name: 'dirty', version: '9.0.0' }))
161
- f.git('add', '.')
162
- f.git('commit', '-m', 'chore: later source must not replace frozen target')
163
- assert.equal(f.plan().metadata.preparedSectionSha256, digest(reviewed))
164
- assert.deepEqual(f.plan().artifact, plan.artifact)
165
- })
166
-
167
- for (const [name, format, lock] of [['demo', 2, true], ['@example/demo', 3, true], ['demo', 3, false], ['a'.repeat(214), 2, false]]) {
168
- test(`npm pre-tag accepts ${name.slice(0, 24)} with ${lock ? `lock v${format}` : 'no lock'}`, (t) => {
169
- const f = fixture(t, { name, format, lock, transform: (files) => editJson(files, 'package.json', (pkg) => { pkg.private = false }) })
170
- assert.deepEqual(f.plan().artifact, { kind: 'npm', packageName: name, version })
171
- assert.equal(f.plan({ sha: f.sourceSha }).releaseSourceSha, f.sourceSha)
172
- const input = structuredClone(f.preparedMetadata)
173
- input.previousAttempt.sourceSha = f.mergeSha
174
- assert.equal(f.plan({ sha: f.mergeSha, preparedMetadata: input }).releaseSourceSha, f.mergeSha)
175
- })
176
- }
177
-
178
- const invalidPackages = [
179
- ['missing name', (pkg) => { delete pkg.name }, /package.json.*name/],
180
- ...['Upper', '.dot', '_underscore', '@Scope/pkg', '@scope/.pkg', '@scope', 'a/b', 'has space', 'a%2fb', 'a?b', 'node_modules', 'favicon.ico', '', 'x'.repeat(215)]
181
- .map((name) => [`unsafe name ${name.slice(0, 15)}`, (pkg) => { pkg.name = name }, /package.json.*name/]),
182
- ['private true', (pkg) => { pkg.private = true }, /private/],
183
- ['private null', (pkg) => { pkg.private = null }, /private/],
184
- ['private string', (pkg) => { pkg.private = 'false' }, /private/],
185
- ['empty workspaces', (pkg) => { pkg.workspaces = [] }, /workspaces/],
186
- ['null workspaces', (pkg) => { pkg.workspaces = null }, /workspaces/],
187
- ['workspace object', (pkg) => { pkg.workspaces = { packages: [] } }, /workspaces/],
188
- ['stale package version', (pkg) => { pkg.version = '1.2.0' }, /package.json.*version/],
189
- ['non-string version', (pkg) => { pkg.version = 1.3 }, /package.json.*version/],
190
- ]
191
-
192
- test('npm pre-tag refuses invalid or non-publishable package identities', async (t) => {
193
- for (const [label, change, error] of invalidPackages) {
194
- await t.test(label, (t) => {
195
- const f = fixture(t, { transform: (files, stage) => stage === 'target' ? editJson(files, 'package.json', change) : files })
196
- assert.throws(f.plan, error)
197
- })
198
- }
199
- })
200
-
201
- const invalidLocks = [
202
- ['top name', (value) => { value.name = 'foreign' }, /lock.*name/],
203
- ['missing top name', (value) => { delete value.name }, /lock.*name/],
204
- ['stale top version', (value) => { value.version = '1.2.0' }, /lock.*version/],
205
- ['stale root version', (value) => { value.packages[''].version = '1.2.0' }, /lock.*version/],
206
- ['missing root version', (value) => { delete value.packages[''].version }, /lock.*version/],
207
- ['foreign root name', (value) => { value.packages[''].name = 'foreign' }, /lock.*name/],
208
- ['missing root name', (value) => { delete value.packages[''].name }, /lock.*name/],
209
- ['missing root', (value) => { delete value.packages[''] }, /lock.*root/],
210
- ['missing packages', (value) => { delete value.packages }, /lock.*root/],
211
- ['null root', (value) => { value.packages[''] = null }, /lock.*root/],
212
- ['array root', (value) => { value.packages[''] = [] }, /lock.*root/],
213
- ['array packages', (value) => { value.packages = [] }, /lock.*root/],
214
- ...[1, 4, '3', null, 2.5].map((format) => [`format ${format}`, (value) => { value.lockfileVersion = format }, /lock.*format/]),
215
- ['missing format', (value) => { delete value.lockfileVersion }, /lock.*format/],
216
- ]
217
-
218
- test('npm pre-tag validates lock identity and supported integer formats at cut, previous source and target', async (t) => {
219
- for (const stage of ['cut', 'previous', 'target']) {
220
- for (const [label, change, error] of invalidLocks) {
221
- await t.test(`${stage}: ${label}`, (t) => {
222
- const f = fixture(t, { transform: (files, current) => current === stage ? editJson(files, 'package-lock.json', change) : files })
223
- assert.throws(f.plan, error)
224
- })
225
- }
226
- }
227
- })
228
-
229
- test('npm pre-tag rejects stale package and VERSION identities at historical and new sources', async (t) => {
230
- for (const stage of ['cut', 'previous', 'target']) {
231
- for (const file of ['package.json', 'VERSION']) {
232
- await t.test(`${stage}: ${file}`, (t) => {
233
- const f = fixture(t, { transform: (files, current) => current !== stage ? files : file === 'VERSION'
234
- ? { ...files, VERSION: '1.2.0\n' } : editJson(files, file, (pkg) => { pkg.version = '1.2.0' }) })
235
- assert.throws(f.plan, /version.*does not match/)
236
- })
237
- }
238
- }
239
- })
240
-
241
- test('npm pre-tag refuses coordinated renames at previous source or target', async (t) => {
242
- for (const stage of ['previous', 'target']) {
243
- await t.test(stage, (t) => {
244
- const f = fixture(t, { transform: (files, current) => {
245
- if (current !== stage) return files
246
- return editJson(editJson(files, 'package.json', (pkg) => { pkg.name = 'renamed' }), 'package-lock.json', (lock) => {
247
- lock.name = 'renamed'; lock.packages[''].name = 'renamed'
248
- })
249
- } })
250
- assert.throws(f.plan, /name.*prepared identity/)
251
- })
252
- }
253
- })
254
-
255
- test('metadata-only cut excludes only version positions, never scripts, dependencies, lock edges or integrity', async (t) => {
256
- for (const [label, file, change] of [
257
- ['scripts', 'package.json', (pkg) => { pkg.scripts.test = 'execute-unreviewed-code' }],
258
- ['dependencies', 'package.json', (pkg) => { pkg.dependencies.dep = '^2.0.0' }],
259
- ['lock integrity', 'package-lock.json', (lock) => { lock.packages['node_modules/dep'].integrity = 'changed' }],
260
- ['lock dependency', 'package-lock.json', (lock) => { lock.packages['node_modules/dep'].version = '2.0.0' }],
261
- ['lock root edge', 'package-lock.json', (lock) => { lock.packages[''].dependencies.dep = '^2.0.0' }],
262
- ]) {
263
- await t.test(label, (t) => {
264
- const f = fixture(t, { transform: (files, stage) => stage === 'cut' ? editJson(files, file, change) : files })
265
- assert.throws(f.plan, /metadata-only: .*changes more than version identity/)
266
- })
267
- }
268
- await t.test('code hidden in original cut', (t) => {
269
- const f = fixture(t, { transform: (files, stage) => stage === 'cut' ? { ...files, 'index.js': 'unreviewed\n' } : files })
270
- assert.throws(f.plan, /original cut is not metadata-only/)
271
- })
272
- })
273
-
274
- function legacyInput(f) {
275
- return { ...f.preparedMetadata, schema: 'prepared-release-metadata/v1', previousAttempt: {
276
- receiptId: 'previous-rc', receiptSha256: 'a'.repeat(64),
277
- candidateTag: `v${f.preparedMetadata.version}-rc.1`, candidateSha: f.mergeSha,
278
- } }
279
- }
280
-
281
- function withNumber(files, file, source) {
282
- return { ...files, [file]: files[file].replace(/}\s*$/, `, "config": {"threshold": ${source}}}\n`) }
283
- }
284
-
285
- test('pre-tag numeric identity catches underflow and fraction rounding in both original-cut JSON files; v1 stays compatible', async (t) => {
286
- for (const file of ['package.json', 'package-lock.json']) {
287
- for (const [label, before, after] of [['underflow', '0', '1e-400'], ['fraction rounding', '0.1', '0.10000000000000001']]) {
288
- await t.test(`${file}: ${label}`, (t) => {
289
- const f = fixture(t, { transform: (files, stage) => withNumber(files, file, stage === 'parent' ? before : after) })
290
- const verify = f.unchanged()
291
- assert.throws(f.plan, new RegExp(`metadata-only: ${file} changes more than version identity`))
292
- const result = f.cli()
293
- assert.equal(result.status, 1)
294
- assert.equal(result.stdout, '')
295
- assert.match(result.stderr, /changes more than version identity/)
296
- assert.equal(f.plan({ preparedMetadata: legacyInput(f) }).nextVersion, version)
297
- verify()
298
- })
299
- }
300
- }
301
- })
302
-
303
- test('pre-tag comparison permits unchanged ordinary floats and exact decimal/exponent equivalents with whitespace and key reordering', (t) => {
304
- const f = fixture(t, { transform: (files, stage) => {
305
- for (const file of ['package.json', 'package-lock.json']) {
306
- const value = JSON.parse(files[file])
307
- files[file] = JSON.stringify(Object.fromEntries(Object.entries(value).reverse()), null, stage === 'parent' ? 2 : 4)
308
- files = withNumber(files, file, stage === 'parent'
309
- ? '[0.1, 1e-7, 0.10, 1.25e+2, -0.0100, 1e-400, -0, 0e99999999999999999999, 1e-9007199254740992]'
310
- : '[0.1, 1e-7, 1e-1, 125.0, -1e-2, 10e-401, -0.0, 0, 1e-9007199254740992]')
311
- }
312
- return files
313
- } })
314
- assert.deepEqual(f.plan().artifact, { kind: 'npm', packageName: '@example/demo', version })
315
- })
316
-
317
- test('pre-tag numeric canonical nodes cannot be impersonated by strings, containers or prototype keys', async (t) => {
318
- for (const file of ['package.json', 'package-lock.json']) {
319
- for (const [before, after] of [
320
- ['0.1', '"0.1"'], ['0.1', '["number","1","-1"]'], ['0.1', '{"data":0.1,"canonical":["number","1","-1"]}'],
321
- ['[0.1]', '{"0":0.1}'], ['false', '0'], ['null', '0'], ['-0', '0'],
322
- ['{"__proto__":0.1}', '{"__proto__":0.10000000000000001}'],
323
- ['{"constructor":{"version":0.1}}', '{"constructor":{"version":0.10000000000000001}}'],
324
- ['{"version":0.1}', '{"version":0.10000000000000001}'],
325
- ['1e-9007199254740992', '2e-9007199254740992'],
326
- // Outside the normalization bound, even equal decimals require identical source tokens.
327
- ['1e-9007199254740992', '10e-9007199254740993'],
328
- ]) {
329
- await t.test(`${file}: ${before} -> ${after}`, (t) => {
330
- const f = fixture(t, { transform: (files, stage) => withNumber(files, file, stage === 'parent' ? before : after) })
331
- assert.throws(f.plan, /changes more than version identity/)
332
- })
333
- }
334
- }
335
- })
336
-
337
- test('pre-tag rejects unsafe canonical version components in input and consistent root identities while v1 remains compatible', async (t) => {
338
- for (const preparedVersion of ['9007199254740992.0.0', '1.9007199254740992.0', '1.3.9007199254740992']) {
339
- await t.test(preparedVersion, (t) => {
340
- const f = fixture(t, { preparedVersion })
341
- assert.throws(f.plan, /version.*safe integer/)
342
- const result = f.cli()
343
- assert.equal(result.status, 1)
344
- assert.equal(result.stdout, '')
345
- assert.match(result.stderr, /version.*safe integer/)
346
- assert.equal(f.plan({ preparedMetadata: legacyInput(f) }).nextVersion, preparedVersion)
347
- })
348
- }
349
- })
350
-
351
- test('pre-tag rejects unsafe historical parent versions at every root version position', async (t) => {
352
- for (const position of ['package.json', 'package-lock.json', 'lock root', 'VERSION']) {
353
- await t.test(position, (t) => {
354
- const f = fixture(t, { transform: (files, stage) => {
355
- if (stage !== 'parent') return files
356
- const huge = '9007199254740992.0.0'
357
- if (position === 'VERSION') return { ...files, VERSION: `${huge}\n` }
358
- const file = position === 'lock root' ? 'package-lock.json' : position
359
- return editJson(files, file, (value) => {
360
- if (position === 'lock root') value.packages[''].version = huge
361
- else value.version = huge
362
- })
363
- } })
364
- assert.throws(f.plan, /version.*safe integer/)
365
- assert.equal(f.plan({ preparedMetadata: legacyInput(f) }).nextVersion, version)
366
- })
367
- }
368
- })
369
-
370
- test('a valid pre-tag plan artifact passes the real npm helper input boundary using only a fake external E404', (t) => {
371
- const f = fixture(t, { preparedVersion: '9007199254740991.3.0' })
372
- const artifact = f.plan().artifact
373
- const bin = join(f.cwd, '.git', 'fake-npm')
374
- const calls = join(bin, 'calls.json')
375
- mkdirSync(bin)
376
- writeFileSync(join(bin, 'npm'), `#!${process.execPath}
377
- require('node:fs').writeFileSync(${JSON.stringify(calls)}, JSON.stringify(process.argv.slice(2)))
378
- process.stderr.write('npm error code E404\\n')
379
- process.exit(1)
380
- `, { mode: 0o755 })
381
- const inherited = process.env
382
- let result
383
- try {
384
- process.env = { ...f.env, PATH: bin }
385
- result = probeNpmVersionAbsence({ packageName: artifact.packageName, version: artifact.version,
386
- registry: 'https://registry.example.invalid/custom/', cwd: f.cwd })
387
- assert.throws(() => probeNpmVersionAbsence({ packageName: artifact.packageName, version: '9007199254740992.0.0',
388
- registry: 'https://registry.example.invalid/custom/', cwd: f.cwd }), /invalid arguments/)
389
- } finally { process.env = inherited }
390
- assert.equal(result.status, 'absent')
391
- assert.equal(result.reason, 'version-absent')
392
- assert.deepEqual(JSON.parse(readFileSync(calls, 'utf8')), [
393
- 'view', '@example/demo@9007199254740991.3.0', 'version', '--registry=https://registry.example.invalid/custom/', '--json',
394
- ])
395
- })
396
-
397
- test('reviewed later dependency, script, lock and code edits remain eligible with constant root identities', (t) => {
398
- const f = fixture(t, { transform: (files, stage) => {
399
- if (!['previous', 'target'].includes(stage)) return files
400
- return editJson(editJson(files, 'package.json', (pkg) => {
401
- pkg.dependencies.dep = '^2.0.0'; pkg.scripts.test = 'node --test test'
402
- }), 'package-lock.json', (lock) => {
403
- lock.packages[''].dependencies.dep = '^2.0.0'
404
- lock.packages['node_modules/dep'].version = '2.0.0'
405
- lock.packages['node_modules/dep'].integrity = 'reviewed-integrity'
406
- })
407
- } })
408
- assert.equal(f.plan().releaseSourceSha, f.sha)
409
- assert.notEqual(f.git('rev-parse', `${f.mergeSha}^{tree}`), f.git('rev-parse', `${f.sha}^{tree}`))
410
- })
411
-
412
- test('npm pre-tag requires a real root package and an unchanged metadata file set', async (t) => {
413
- for (const [label, stage, transform, error] of [
414
- ['no package', null, (files) => { const copy = { ...files }; delete copy['package.json']; return copy }, /root package.json/],
415
- ['target new identity file', 'target', (files) => ({ ...files, 'package-lock.json': json({}) }), /file set/],
416
- ['previous missing identity file', 'previous', (files) => { const copy = { ...files }; delete copy.VERSION; return copy }, /file set/],
417
- ['cut missing identity file', 'cut', (files) => { const copy = { ...files }; delete copy.VERSION; return copy }, /file set/],
418
- ]) {
419
- await t.test(label, (t) => {
420
- const f = fixture(t, { lock: label !== 'target new identity file', transform: (files, current) => stage === null || current === stage ? transform(files) : files })
421
- assert.throws(f.plan, error)
422
- })
423
- }
424
- })
425
-
426
- test('strict v2 union rejects unknown, missing, null and mixed fields without plan output or writes', (t) => {
427
- const f = fixture(t)
428
- const p = f.preparedMetadata
429
- const previous = p.previousAttempt
430
- const invalid = [
431
- [{ ...p, schema: 'prepared-release-metadata/v3' }, /schema/],
432
- [{ ...p, extra: true }, /unknown field/],
433
- ...[null, [], {}, { ...previous, kind: null }, { ...previous, kind: 'other' },
434
- { ...previous, candidateTag: null }, { ...previous, candidateSha: f.mergeSha }]
435
- .map((previousAttempt) => [{ ...p, previousAttempt }, /previousAttempt|kind/]),
436
- ...Object.keys(previous).map((key) => {
437
- const value = { ...previous }; delete value[key]
438
- return [{ ...p, previousAttempt: value }, /previousAttempt|kind/]
439
- }),
440
- ...['kind', 'receiptId', 'receiptSha256', 'sourceSha'].map((key) => [{ ...p, previousAttempt: { ...previous, [key]: null } }, /kind|receiptId|receiptSha256|sourceSha/]),
441
- [{ ...p, previousAttempt: { ...previous, sourceSha: 'HEAD' } }, /sourceSha.*full/],
442
- [{ ...p, previousAttempt: { ...previous, receiptId: '../unsafe' } }, /receiptId/],
443
- [{ ...p, previousAttempt: { ...previous, receiptSha256: 'A'.repeat(64) } }, /receiptSha256/],
444
- [{ ...p, previousAttempt: { ...previous, kind: 'cloudRunRC' } }, /previousAttempt/],
445
- [{ ...p, previousAttempt: { kind: 'cloudRunRC', receiptId: 'rc', receiptSha256: 'a'.repeat(64), candidateTag: null, candidateSha: f.mergeSha } }, /candidateTag/],
446
- ]
447
- const verify = f.unchanged()
448
- for (const [preparedMetadata, error] of invalid) assert.throws(() => f.plan({ preparedMetadata }), error)
449
- assert.throws(() => f.plan({ cloudRun: true }), /pre-tag.*cloudRun|pre-tag.*Cloud Run/)
450
- const result = f.cli({ args: ['--cloud-run'] })
451
- assert.equal(result.status, 1)
452
- assert.equal(result.stdout, '')
453
- assert.match(result.stderr, /pre-tag.*cloudRun|pre-tag.*Cloud Run/)
454
- verify()
455
- })
456
-
457
- test('previous source must resolve to a commit on merge -> previous -> frozen target ancestry', (t) => {
458
- const f = fixture(t)
459
- const withSource = (sourceSha) => ({ ...f.preparedMetadata, previousAttempt: { ...f.preparedMetadata.previousAttempt, sourceSha } })
460
- assert.throws(() => f.plan({ preparedMetadata: withSource('f'.repeat(40)) }), /failed|object|revision/i)
461
- assert.throws(() => f.plan({ preparedMetadata: withSource(f.parentSha) }), /historical merge to previous source.*ancestor/)
462
- assert.throws(() => f.plan({ sha: f.mergeSha }), /previous source to target.*ancestor/)
463
- f.git('tag', '-a', 'annotated-source', '-m', 'Not a commit')
464
- const tagObject = f.git('rev-parse', 'annotated-source')
465
- assert.throws(() => f.plan({ preparedMetadata: withSource(tagObject) }), /must identify a commit/)
466
- f.git('switch', '--orphan', 'unrelated')
467
- f.git('add', '.')
468
- f.git('commit', '--allow-empty', '-m', 'unrelated root')
469
- const unrelated = f.git('rev-parse', 'HEAD')
470
- assert.throws(() => f.plan({ preparedMetadata: withSource(unrelated) }), /historical merge to previous source.*ancestor/)
471
- assert.throws(() => f.plan({ sha: unrelated }), /ancestor/)
472
- })
473
-
474
- test('any existing final tag spends the version, including a tag on the old source; inspection failure refuses', async (t) => {
475
- for (const annotated of [false, true]) {
476
- await t.test(annotated ? 'annotated' : 'lightweight', (t) => {
477
- const f = fixture(t)
478
- f.git('tag', ...(annotated ? ['-a', `v${version}`, f.sourceSha, '-m', 'Spent version'] : [`v${version}`, f.sourceSha]))
479
- f.git('push', 'origin', `refs/tags/v${version}`)
480
- const verify = f.unchanged()
481
- const result = f.cli()
482
- assert.equal(result.status, 1)
483
- assert.equal(result.stdout, '')
484
- assert.match(result.stderr, /final tag already exists.*spent/)
485
- verify()
486
- })
487
- }
488
- await t.test('remote inspection unavailable', (t) => {
489
- const f = fixture(t)
490
- f.git('config', '--unset', `url.${f.remote}.insteadOf`)
491
- f.git('config', `url.${join(f.remote, 'missing')}.insteadOf`, 'https://github.com/example/demo.git')
492
- const verify = f.unchanged()
493
- const result = f.cli()
494
- assert.equal(result.status, 1)
495
- assert.equal(result.stdout, '')
496
- assert.match(result.stderr, /remote inspection failed.*absence is unproven/)
497
- verify()
498
- })
499
- })
500
-
501
- for (const file of ['package.json', 'package-lock.json']) {
502
- for (const stage of ['parent', 'cut', 'previous', 'target']) {
503
- test(`strict JSON rejects invalid UTF-8 ${file} at ${stage}`, (t) => {
504
- const f = fixture(t, { transform: (files, current) => {
505
- const value = JSON.parse(files[file]); value.description = '\uFFFD'
506
- const [before, after] = json(value).split('\uFFFD')
507
- files[file] = current === stage
508
- ? Buffer.concat([Buffer.from(before), Buffer.from([0xfe]), Buffer.from(after)])
509
- : json(value)
510
- return files
511
- } })
512
- assert.throws(f.plan, new RegExp(`${file}.*(?:UTF.?8|JSON)`))
513
- })
514
- }
515
- test(`strict JSON refuses non-object, malformed, nonfinite and oversized ${file} without output leakage`, async (t) => {
516
- for (const [label, content, error] of [
517
- ['null', 'null', /JSON.*object/], ['array', '[]', /JSON.*object/], ['string', '"value"', /JSON.*object/],
518
- ['malformed', '{"private-marker":', /JSON/],
519
- ['nonfinite number', '{"private-marker":1e400}', /JSON/],
520
- ['unsafe integer', '{"private-marker":9007199254740993}', /JSON/],
521
- ['oversize', `{"private-marker":"${'x'.repeat(8 * 1024 * 1024)}"}`, /bounded|limit|acquisition/],
522
- ]) {
523
- await t.test(label, (t) => {
524
- const f = fixture(t, { transform: (files, stage) => stage === 'target' ? { ...files, [file]: content } : files })
525
- const result = f.cli()
526
- assert.equal(result.status, 1)
527
- assert.equal(result.stdout, '')
528
- assert.match(result.stderr, new RegExp(`${file}.*(?:${error.source})`))
529
- assert.doesNotMatch(result.stderr, /private-marker/)
530
- })
531
- }
532
- })
533
- test(`strict JSON accepts an exact 8 MiB ${file} and valid Unicode`, (t) => {
534
- const limit = 8 * 1024 * 1024
535
- const f = fixture(t, { transform: (files, stage) => {
536
- if (stage !== 'target') return files
537
- const value = JSON.parse(files[file]); value.description = 'caf\u00e9 \u6771\u4eac'
538
- const text = json(value)
539
- return { ...files, [file]: `${text}${' '.repeat(limit - Buffer.byteLength(text))}` }
540
- } })
541
- assert.equal(f.bytes('show', `${f.sha}:${file}`).length, limit)
542
- assert.equal(f.plan().releaseSourceSha, f.sha)
543
- })
544
- }
545
-
546
- test('npm metadata acquisition fails closed on Git errors and timeouts without leaking blob or process output', async (t) => {
547
- for (const file of ['package.json', 'package-lock.json']) {
548
- for (const [label, action] of [
549
- ['process error', 'process.stderr.write("private-git-error"); process.exit(1)'],
550
- ['timeout', 'setTimeout(() => process.exit(0), 30_000)'],
551
- ]) {
552
- await t.test(`${file}: ${label}`, (t) => {
553
- const f = fixture(t)
554
- const realGit = spawnSync('which', ['git'], { env: f.env, encoding: 'utf8' }).stdout.trim()
555
- const bin = join(f.cwd, '.git', 'git-bin')
556
- mkdirSync(bin)
557
- // Substitute only the external Git blob read; every other Git operation remains real.
558
- writeFileSync(join(bin, 'git'), `#!${process.execPath}
559
- const args = process.argv.slice(2)
560
- if (args[0] === 'show' && args[1].endsWith(${JSON.stringify(`:${file}`)})) {
561
- ${action}
562
- } else {
563
- const { spawnSync } = require('node:child_process')
564
- const result = spawnSync(${JSON.stringify(realGit)}, args, { stdio: 'inherit' })
565
- process.exit(result.status ?? 1)
566
- }
567
- `, { mode: 0o755 })
568
- const verify = f.unchanged()
569
- const result = f.cli({ env: { ...f.env, PATH: `${bin}:${f.env.PATH}` } })
570
- assert.equal(result.status, 1)
571
- assert.equal(result.stdout, '')
572
- assert.match(result.stderr, new RegExp(`${file} Git blob acquisition failed.*bounded`))
573
- assert.doesNotMatch(result.stderr, /private-git-error/)
574
- verify()
575
- })
576
- }
577
- }
578
- })
579
-
580
- test('npm identity files must remain regular non-executable Git blobs', async (t) => {
581
- for (const file of ['package.json', 'package-lock.json']) {
582
- for (const mode of ['symlink', 'executable']) {
583
- await t.test(`${file}: ${mode}`, (t) => {
584
- const f = fixture(t)
585
- if (mode === 'symlink') {
586
- rmSync(join(f.cwd, file))
587
- symlinkSync('VERSION', join(f.cwd, file))
588
- f.git('add', file)
589
- } else {
590
- f.git('update-index', '--chmod=+x', file)
591
- }
592
- f.git('commit', '-m', 'chore: unsafe metadata file mode')
593
- const sha = f.git('rev-parse', 'HEAD')
594
- const result = f.cli({ sha })
595
- assert.equal(result.status, 1)
596
- assert.equal(result.stdout, '')
597
- assert.match(result.stderr, new RegExp(`regular metadata file: ${file}`))
598
- })
599
- }
600
- }
601
- })
602
-
603
- test('Node-only CLI tripwires forbid npm/Python and RC construction or inspection while real Git handles final absence', (t) => {
604
- const f = fixture(t)
605
- const realGit = spawnSync('which', ['git'], { env: f.env, encoding: 'utf8' }).stdout.trim()
606
- const bin = join(f.cwd, '.git', 'tripwires')
607
- const calls = join(f.cwd, '.git', 'external-calls.jsonl')
608
- mkdirSync(bin)
609
- for (const command of ['npm', 'npx', 'python', 'python3']) {
610
- writeFileSync(join(bin, command), `#!${process.execPath}\nrequire('node:fs').appendFileSync(${JSON.stringify(calls)}, ${JSON.stringify(`${command}\n`)})\nprocess.exit(99)\n`, { mode: 0o755 })
611
- }
612
- writeFileSync(join(bin, 'git'), `#!${process.execPath}
613
- const { spawnSync } = require('node:child_process')
614
- const { appendFileSync } = require('node:fs')
615
- const args = process.argv.slice(2)
616
- appendFileSync(${JSON.stringify(calls)}, JSON.stringify(args) + '\\n')
617
- if (args.some(arg => arg.includes('-rc.'))) process.exit(98)
618
- const result = spawnSync(${JSON.stringify(realGit)}, args, { stdio: 'inherit' })
619
- process.exit(result.status ?? 1)
620
- `, { mode: 0o755 })
621
- const verify = f.unchanged()
622
- const result = f.cli({ env: { ...f.env, PATH: `${bin}:${f.env.PATH}` } })
623
- assert.equal(result.status, 0, result.stderr)
624
- assert.deepEqual(JSON.parse(result.stdout).metadata.previousAttempt, f.preparedMetadata.previousAttempt)
625
- const commands = readFileSync(calls, 'utf8').trim().split('\n').map((line) => JSON.parse(line))
626
- assert.deepEqual(commands.filter((args) => args[0] === 'ls-remote'), [
627
- ['ls-remote', '--tags', 'origin', `refs/tags/v${version}`, `refs/tags/v${version}^{}`],
628
- ])
629
- assert.equal(commands.some((args) => ['tag', 'update-ref', 'commit', 'switch'].includes(args[0])), false)
630
- verify()
631
- })
632
-
633
- test('v2 cloudRunRC preserves RC binding, ordinals and immutability, without changing v1 shape or behavior', (t) => {
634
- const f = fixture(t)
635
- f.git('tag', '-a', `v${version}-rc.2`, f.mergeSha, '-m', 'Previous immutable RC')
636
- f.git('push', 'origin', `refs/tags/v${version}-rc.2`)
637
- f.git('tag', `v${version}-rc.4`, f.sha)
638
- f.git('push', 'origin', `refs/tags/v${version}-rc.4`)
639
- f.git('tag', '-d', `v${version}-rc.4`)
640
- const previous = { receiptId: 'previous-rc', receiptSha256: 'a'.repeat(64), candidateTag: `v${version}-rc.2`, candidateSha: f.mergeSha }
641
- const v1 = { ...f.preparedMetadata, schema: 'prepared-release-metadata/v1', previousAttempt: previous }
642
- const v2 = { ...v1, schema: 'prepared-release-metadata/v2', previousAttempt: { kind: 'cloudRunRC', ...previous } }
643
- const verify = f.unchanged()
644
- for (const preparedMetadata of [v1, v2]) {
645
- const plan = f.plan({ cloudRun: true, preparedMetadata })
646
- assert.equal(plan.candidateTag, `v${version}-rc.5`)
647
- assert.deepEqual(plan.metadata.previousAttempt, preparedMetadata.previousAttempt)
648
- assert.equal(Object.hasOwn(plan, 'artifact'), false)
649
- assert.throws(() => f.plan({ cloudRun: true, preparedMetadata: { ...preparedMetadata,
650
- previousAttempt: { ...preparedMetadata.previousAttempt, candidateSha: f.sha } } }), /candidateSha.*historical mergeSha/)
651
- assert.throws(() => f.plan({ cloudRun: true, preparedMetadata: { ...preparedMetadata,
652
- previousAttempt: { ...preparedMetadata.previousAttempt, candidateTag: `v${version}-rc.9007199254740992` } } }), /ordinal/)
653
- }
654
- assert.throws(() => f.plan({ preparedMetadata: { ...v2, previousAttempt: { ...v2.previousAttempt, sourceSha: f.sourceSha } } }), /unknown field/)
655
- assert.throws(() => f.plan({ preparedMetadata: { ...v1, previousAttempt: v2.previousAttempt } }), /unknown field/)
656
- verify()
657
- f.gitAt(f.remote, 'update-ref', `refs/tags/v${version}-rc.2`, f.sha)
658
- assert.throws(() => f.plan({ preparedMetadata: v2, cloudRun: true }), /previous candidate remote tag SHA/)
659
- })