@1aboveio/skills 1.0.7 → 1.0.9

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 (224) 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 +92 -34
  8. package/skills/app-dev/android-foundation/assets/android-ci-job.yml +13 -7
  9. package/skills/app-dev/android-foundation/references/ci.md +23 -13
  10. package/skills/app-dev/android-foundation/references/delivery-focus.md +29 -0
  11. package/skills/app-dev/android-foundation/references/diagnosis.md +136 -0
  12. package/skills/app-dev/android-foundation/references/gotchas.md +20 -11
  13. package/skills/app-dev/android-foundation/references/setup.md +15 -6
  14. package/skills/app-dev/android-foundation/release-check.mjs +3 -1
  15. package/skills/app-dev/android-foundation/scripts/device-run.sh +148 -42
  16. package/skills/app-dev/app-debug/SKILL.md +1 -1
  17. package/skills/app-dev/app-debug/release-check.mjs +3 -1
  18. package/skills/app-dev/better-auth/SKILL.md +1 -1
  19. package/skills/app-dev/better-auth/release-check.mjs +3 -1
  20. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  21. package/skills/app-dev/data-catalog/release-check.mjs +3 -1
  22. package/skills/app-dev/data-quality/SKILL.md +1 -1
  23. package/skills/app-dev/data-quality/release-check.mjs +3 -1
  24. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  25. package/skills/app-dev/ios-foundation/release-check.mjs +3 -1
  26. package/skills/app-dev/monorepo/SKILL.md +1 -1
  27. package/skills/app-dev/monorepo/release-check.mjs +3 -1
  28. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  29. package/skills/app-dev/nextjs-fullstack/release-check.mjs +3 -1
  30. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  31. package/skills/app-dev/prisma-setup/release-check.mjs +3 -1
  32. package/skills/app-dev/pyspark/SKILL.md +1 -1
  33. package/skills/app-dev/pyspark/release-check.mjs +3 -1
  34. package/skills/app-dev/python-backend/SKILL.md +1 -1
  35. package/skills/app-dev/python-backend/release-check.mjs +3 -1
  36. package/skills/app-dev/shadcn/SKILL.md +1 -1
  37. package/skills/app-dev/shadcn/release-check.mjs +3 -1
  38. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  39. package/skills/app-dev/zod-v4/release-check.mjs +3 -1
  40. package/skills/cicd/cloud-build/SKILL.md +1 -1
  41. package/skills/cicd/cloud-build/release-check.mjs +3 -1
  42. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  43. package/skills/cicd/cloud-debug/release-check.mjs +3 -1
  44. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  45. package/skills/cicd/cloud-deploy/release-check.mjs +3 -1
  46. package/skills/cicd/docker/SKILL.md +1 -1
  47. package/skills/cicd/docker/release-check.mjs +3 -1
  48. package/skills/cicd/github-actions/SKILL.md +1 -1
  49. package/skills/cicd/github-actions/release-check.mjs +3 -1
  50. package/skills/cicd/github-flow/SKILL.md +1 -1
  51. package/skills/cicd/github-flow/release-check.mjs +3 -1
  52. package/skills/cicd/google-cloud/SKILL.md +1 -1
  53. package/skills/cicd/google-cloud/release-check.mjs +3 -1
  54. package/skills/cicd/mergify/SKILL.md +1 -1
  55. package/skills/cicd/mergify/release-check.mjs +3 -1
  56. package/skills/cicd/podman/SKILL.md +1 -1
  57. package/skills/cicd/podman/release-check.mjs +3 -1
  58. package/skills/content/article-review/SKILL.md +1 -1
  59. package/skills/content/article-review/release-check.mjs +3 -1
  60. package/skills/content/article-to-image/SKILL.md +1 -1
  61. package/skills/content/article-to-image/release-check.mjs +3 -1
  62. package/skills/content/flowchart-svg/SKILL.md +1 -1
  63. package/skills/content/flowchart-svg/release-check.mjs +3 -1
  64. package/skills/content/image-gen/SKILL.md +1 -1
  65. package/skills/content/image-gen/release-check.mjs +3 -1
  66. package/skills/content/mermaid/SKILL.md +1 -1
  67. package/skills/content/mermaid/release-check.mjs +3 -1
  68. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  69. package/skills/content/wechat-article-writer/release-check.mjs +3 -1
  70. package/skills/content/wechat-publisher/SKILL.md +1 -1
  71. package/skills/content/wechat-publisher/release-check.mjs +3 -1
  72. package/skills/content/wechat-search/SKILL.md +1 -1
  73. package/skills/content/wechat-search/release-check.mjs +3 -1
  74. package/skills/content/xai-search/SKILL.md +88 -13
  75. package/skills/content/xai-search/release-check.mjs +3 -1
  76. package/skills/content/xai-search/scripts/xai_search.sh +125 -149
  77. package/skills/core/distill-writing/SKILL.md +1 -1
  78. package/skills/core/distill-writing/release-check.mjs +3 -1
  79. package/skills/core/doc-to-md/SKILL.md +1 -1
  80. package/skills/core/doc-to-md/release-check.mjs +3 -1
  81. package/skills/core/editorial-review/SKILL.md +1 -1
  82. package/skills/core/editorial-review/release-check.mjs +3 -1
  83. package/skills/core/house-style/SKILL.md +1 -1
  84. package/skills/core/house-style/release-check.mjs +3 -1
  85. package/skills/core/plain-language/SKILL.md +1 -1
  86. package/skills/core/plain-language/release-check.mjs +3 -1
  87. package/skills/core/polish-cn/SKILL.md +1 -1
  88. package/skills/core/polish-cn/release-check.mjs +3 -1
  89. package/skills/core/reg-parser/SKILL.md +1 -1
  90. package/skills/core/reg-parser/release-check.mjs +3 -1
  91. package/skills/core/terminology/SKILL.md +1 -1
  92. package/skills/core/terminology/release-check.mjs +3 -1
  93. package/skills/core/write-doc/SKILL.md +1 -1
  94. package/skills/core/write-doc/release-check.mjs +3 -1
  95. package/skills/engineering/better-review/SKILL.md +1 -1
  96. package/skills/engineering/better-review/release-check.mjs +3 -1
  97. package/skills/engineering/contributing/SKILL.md +1 -1
  98. package/skills/engineering/contributing/release-check.mjs +3 -1
  99. package/skills/engineering/e2e-test/SKILL.md +1 -1
  100. package/skills/engineering/e2e-test/release-check.mjs +3 -1
  101. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  102. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  103. package/skills/engineering/engineering-runtime/release-check.mjs +3 -1
  104. package/skills/engineering/engineering-runtime/scripts/installation-resolver.mjs +191 -0
  105. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +51 -10
  106. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  107. package/skills/engineering/ensure-coverage/release-check.mjs +3 -1
  108. package/skills/engineering/golden-path/SKILL.md +1 -1
  109. package/skills/engineering/golden-path/release-check.mjs +3 -1
  110. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  111. package/skills/engineering/harness-runtime/release-check.mjs +3 -1
  112. package/skills/engineering/resolve-issues/SKILL.md +11 -20
  113. package/skills/engineering/resolve-issues/references/delivery-progress.md +63 -0
  114. package/skills/engineering/resolve-issues/references/expire.md +24 -11
  115. package/skills/engineering/resolve-issues/references/implementation.md +9 -1
  116. package/skills/engineering/resolve-issues/references/preflight.md +7 -0
  117. package/skills/engineering/resolve-issues/references/shared-modules.md +27 -0
  118. package/skills/engineering/resolve-issues/references/task-plan.md +12 -5
  119. package/skills/engineering/resolve-issues/release-check.mjs +3 -1
  120. package/skills/engineering/resolve-issues/scripts/active-installation.mjs +32 -0
  121. package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +3 -0
  122. package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +17 -1
  123. package/skills/engineering/resolve-release/SKILL.md +1 -1
  124. package/skills/engineering/resolve-release/release-check.mjs +3 -1
  125. package/skills/engineering/rush-issues/SKILL.md +5 -2
  126. package/skills/engineering/rush-issues/release-check.mjs +3 -1
  127. package/skills/engineering/rush-release/SKILL.md +1 -1
  128. package/skills/engineering/rush-release/release-check.mjs +3 -1
  129. package/skills/engineering/test-quality/SKILL.md +1 -1
  130. package/skills/engineering/test-quality/release-check.mjs +3 -1
  131. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  132. package/skills/risk-compliance/compliance-audit/release-check.mjs +3 -1
  133. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  134. package/skills/risk-compliance/contract-compare/release-check.mjs +3 -1
  135. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  136. package/skills/risk-compliance/contract-review/release-check.mjs +3 -1
  137. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  138. package/skills/risk-compliance/doc-authenticity/release-check.mjs +3 -1
  139. package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
  140. package/skills/risk-compliance/fraud-analysis/release-check.mjs +3 -1
  141. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  142. package/skills/risk-compliance/payment-analysis/release-check.mjs +3 -1
  143. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  144. package/skills/risk-compliance/similar-domain/release-check.mjs +3 -1
  145. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
  146. package/skills/risk-compliance/tld-detection/release-check.mjs +3 -1
  147. package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +0 -172
  148. package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +0 -36
  149. package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +0 -12
  150. package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +0 -637
  151. package/skills/cicd/github-flow/scripts/flow.test.mjs +0 -114
  152. package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +0 -108
  153. package/skills/cicd/mergify/scripts/audit.test.mjs +0 -201
  154. package/skills/cicd/mergify/scripts/entrypoints.test.mjs +0 -106
  155. package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +0 -30
  156. package/skills/cicd/mergify/scripts/stack-push.test.mjs +0 -233
  157. package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +0 -1826
  158. package/skills/content/image-gen/scripts/credentials.test.mjs +0 -212
  159. package/skills/content/image-gen/scripts/options.test.mjs +0 -245
  160. package/skills/content/xai-search/tests/smoke_test.sh +0 -36
  161. package/skills/core/polish-cn/scripts/polish-cn.test.mjs +0 -244
  162. package/skills/core/reg-parser/scripts/reg-parser.test.mjs +0 -165
  163. package/skills/engineering/better-review/scripts/pr-context.test.mjs +0 -168
  164. package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +0 -587
  165. package/skills/engineering/better-review/scripts/scan-diff.test.mjs +0 -414
  166. package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +0 -96
  167. package/skills/engineering/contributing/scripts/persist-conventions.test.mjs +0 -118
  168. package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +0 -106
  169. package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +0 -117
  170. package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +0 -69
  171. package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +0 -98
  172. package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +0 -71
  173. package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +0 -59
  174. package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +0 -76
  175. package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +0 -68
  176. package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +0 -95
  177. package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +0 -288
  178. package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +0 -96
  179. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +0 -96
  180. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +0 -47
  181. package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +0 -86
  182. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +0 -447
  183. package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +0 -552
  184. package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +0 -289
  185. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +0 -119
  186. package/skills/engineering/golden-path/scripts/golden-path.test.mjs +0 -1457
  187. package/skills/engineering/harness-runtime/discover-models.test.mjs +0 -363
  188. package/skills/engineering/harness-runtime/harness-runtime.test.mjs +0 -967
  189. package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +0 -829
  190. package/skills/engineering/harness-runtime/model-catalog.test.mjs +0 -366
  191. package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +0 -280
  192. package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +0 -346
  193. package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +0 -88
  194. package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +0 -264
  195. package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +0 -79
  196. package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +0 -107
  197. package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +0 -14
  198. package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +0 -149
  199. package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +0 -915
  200. package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +0 -156
  201. package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +0 -448
  202. package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +0 -217
  203. package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +0 -91
  204. package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +0 -1060
  205. package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +0 -458
  206. package/skills/engineering/resolve-release/scripts/apply.test.mjs +0 -99
  207. package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +0 -100
  208. package/skills/engineering/resolve-release/scripts/flow.test.mjs +0 -340
  209. package/skills/engineering/resolve-release/scripts/green-head.test.mjs +0 -101
  210. package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +0 -426
  211. package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +0 -659
  212. package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +0 -317
  213. package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +0 -291
  214. package/skills/engineering/resolve-release/scripts/plan.test.mjs +0 -428
  215. package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +0 -591
  216. package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +0 -119
  217. package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +0 -104
  218. package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +0 -14
  219. package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +0 -12
  220. package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +0 -212
  221. package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +0 -14
  222. package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +0 -14
  223. package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +0 -134
  224. 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
- })