@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,426 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { spawnSync } from 'node:child_process'
4
- import { mkdtempSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
5
- import { tmpdir } from 'node:os'
6
- import { join } from 'node:path'
7
- import { fileURLToPath } from 'node:url'
8
- import { probeNpmVersionAbsence } from './npm-version-absence.mjs'
9
-
10
- const script = fileURLToPath(new URL('./npm-version-absence.mjs', import.meta.url))
11
- const request = { packageName: '@example/widget', version: '1.2.3', registry: 'https://registry.example.test/custom/npm/' }
12
- const leakMarker = 'PRIVATE_CREDENTIAL_PAYLOAD_DO_NOT_FORWARD'
13
-
14
- function fixture(t) {
15
- const root = realpathSync(mkdtempSync(join(tmpdir(), 'npm-version-absence-')))
16
- const bin = join(root, 'bin')
17
- const cwd = join(root, 'work')
18
- const calls = join(root, 'calls.jsonl')
19
- mkdirSync(bin)
20
- mkdirSync(cwd)
21
- const savedEnv = { ...process.env }
22
- t.after(() => {
23
- process.env = savedEnv
24
- rmSync(root, { recursive: true, force: true })
25
- })
26
- process.env.PATH = bin
27
- process.env.FAKE_NPM_CALLS = calls
28
- process.env.FAKE_NPM_REPLY = JSON.stringify({ stderr: 'npm error code E404', exitCode: 1 })
29
- process.env.npm_config_registry = 'https://ambient-registry.example.test/ignored/'
30
- const executable = `#!${process.execPath}
31
- const fs = require('node:fs')
32
- fs.appendFileSync(process.env.FAKE_NPM_CALLS, JSON.stringify({ argv: process.argv.slice(2), cwd: process.cwd() }) + '\\n')
33
- const reply = JSON.parse(process.env.FAKE_NPM_REPLY)
34
- fs.writeSync(1, reply.stdout || '')
35
- fs.writeSync(2, reply.stderr || '')
36
- if (reply.stdoutHex) fs.writeSync(1, Buffer.from(reply.stdoutHex, 'hex'))
37
- if (reply.stderrHex) fs.writeSync(2, Buffer.from(reply.stderrHex, 'hex'))
38
- for (const [fd, size] of [[1, reply.stdoutBytes], [2, reply.stderrBytes]]) {
39
- let remaining = size || 0
40
- while (remaining > 0) {
41
- const chunk = Buffer.alloc(Math.min(8192, remaining), ' ')
42
- remaining -= fs.writeSync(fd, chunk)
43
- }
44
- }
45
- if (reply.signal) process.kill(process.pid, reply.signal)
46
- process.exit(reply.exitCode ?? 0)
47
- `
48
- writeFileSync(join(bin, 'npm'), executable, { mode: 0o755 })
49
- for (const command of ['git', 'gh', 'gcloud', 'curl', 'wget', 'touch']) {
50
- writeFileSync(join(bin, command), `#!${process.execPath}
51
- require('node:fs').appendFileSync(process.env.FAKE_NPM_CALLS, JSON.stringify({ forbiddenCommand: ${JSON.stringify(command)}, argv: process.argv.slice(2) }) + '\\n')
52
- process.exit(91)
53
- `, { mode: 0o755 })
54
- }
55
- t.mock.timers.enable({ apis: ['Date'], now: Date.UTC(2030, 0, 2) })
56
- return {
57
- root, bin, cwd,
58
- reply(value) { process.env.FAKE_NPM_REPLY = JSON.stringify(value) },
59
- calls() {
60
- try { return readFileSync(calls, 'utf8').trim().split('\n').map((line) => JSON.parse(line)) }
61
- catch (error) { if (error.code === 'ENOENT') return []; throw error }
62
- },
63
- probe(overrides = {}) { return probeNpmVersionAbsence({ ...request, cwd, ...overrides }) },
64
- cli(args = ['--package', request.packageName, '--version', request.version, '--registry', request.registry]) {
65
- return spawnSync(process.execPath, [script, ...args], { cwd, env: process.env, encoding: 'utf8', timeout: 40_000 })
66
- },
67
- }
68
- }
69
-
70
- function checkResult(result, status, reason, input = request) {
71
- assert.deepEqual(Object.keys(result).sort(), ['status', 'packageName', 'version', 'registry', 'observedAt', 'reason'].sort())
72
- assert.equal(result.status, status)
73
- assert.equal(result.reason, reason)
74
- assert.equal(result.packageName, input.packageName)
75
- assert.equal(result.version, input.version)
76
- assert.equal(result.registry, input.registry)
77
- assert.equal(new Date(result.observedAt).toISOString(), result.observedAt)
78
- assert.equal(JSON.stringify(result).includes(leakMarker), false)
79
- }
80
-
81
- test('clean E404 observes absence through one read-only npm argv with explicit scoped registry', (t) => {
82
- const f = fixture(t)
83
- checkResult(f.probe(), 'absent', 'version-absent')
84
- assert.deepEqual(f.calls(), [{
85
- argv: ['view', '@example/widget@1.2.3', 'version', '--registry=https://registry.example.test/custom/npm/', '--json'],
86
- cwd: f.cwd,
87
- }])
88
- assert.deepEqual(readdirSync(f.cwd), [])
89
- })
90
-
91
- const responses = [
92
- ['plain E404 on stdout', { stdout: 'npm ERR! code E404\n', exitCode: 1 }, 'absent', 'version-absent'],
93
- ['JSON E404', { stdout: '{"error":{"code":"E404","summary":"Not found","detail":""}}', exitCode: 1 }, 'absent', 'version-absent'],
94
- ['JSON E404 with matching stderr', { stdout: '{"error":{"code":"E404"}}', stderr: 'npm error code E404', exitCode: 1 }, 'absent', 'version-absent'],
95
- ['bare E404 on stderr', { stderr: 'E404', exitCode: 1 }, 'absent', 'version-absent'],
96
- ['JSON E404 on stderr', { stderr: '{"error":{"code":"E404"}}', exitCode: 1 }, 'absent', 'version-absent'],
97
- ['escaped JSON E404 code', { stdout: '{"error":{"co\\u0064e":"E\\u0034\\u0030\\u0034"}}', exitCode: 1 }, 'absent', 'version-absent'],
98
- ['E404 with exit 2', { stderr: 'npm error code E404', exitCode: 2 }, 'inconclusive', 'npm-error'],
99
- ['E404 with exit 7', { stderr: 'npm error code E404', exitCode: 7 }, 'inconclusive', 'npm-error'],
100
- ['empty nonzero', { exitCode: 1 }, 'inconclusive', 'npm-error'],
101
- ['unknown nonzero', { stderr: 'Something failed', exitCode: 42 }, 'inconclusive', 'npm-error'],
102
- ['non-code E404 substring', { stderr: 'NOT_E404 or E404suffix', exitCode: 1 }, 'inconclusive', 'npm-error'],
103
- ['E404 with unknown named code', { stderr: 'npm error code E404\nnpm error code UNKNOWN', exitCode: 1 }, 'inconclusive', 'npm-error'],
104
- ['E404 mixed within JSON', { stdout: '{"error":{"code":"E404","detail":"E403"}}', exitCode: 1 }, 'inconclusive', 'npm-error'],
105
- ['escaped mixed JSON code', { stdout: '{"error":{"code":"E404","detail":"E\\u0034\\u0030\\u0033"}}', exitCode: 1 }, 'inconclusive', 'npm-error'],
106
- ['duplicate JSON keys cannot hide mixed codes', { stdout: '{"error":{"code":"E403","code":"E404"}}', exitCode: 1 }, 'inconclusive', 'npm-error'],
107
- ['malformed UTF-8 stdout cannot clear absence', { stdout: 'E404 ', stdoutHex: 'ff', exitCode: 1 }, 'inconclusive', 'unexpected-response'],
108
- ['malformed UTF-8 stderr cannot clear absence', { stdout: 'E404 ', stderrHex: 'ff', exitCode: 1 }, 'inconclusive', 'unexpected-response'],
109
- ['malformed JSON error', { stdout: '{"error":{"code":"E404"', exitCode: 1 }, 'inconclusive', 'unexpected-response'],
110
- ['malformed JSON stderr', { stderr: '["E404",', exitCode: 1 }, 'inconclusive', 'unexpected-response'],
111
- ['successful JSON version', { stdout: '"1.2.3"\n' }, 'present', 'version-present'],
112
- ['successful bare version', { stdout: ' \n1.2.3\t\n' }, 'present', 'version-present'],
113
- ['successful version with warning', { stdout: '"1.2.3"', stderr: 'npm warn using cached metadata' }, 'present', 'version-present'],
114
- ['successful empty output', {}, 'inconclusive', 'unexpected-response'],
115
- ['successful wrong version', { stdout: '"1.2.4"' }, 'inconclusive', 'unexpected-response'],
116
- ['successful bare wrong version', { stdout: '2.0.0' }, 'inconclusive', 'unexpected-response'],
117
- ['successful malformed JSON', { stdout: '"1.2.3' }, 'inconclusive', 'unexpected-response'],
118
- ['successful version object', { stdout: '{"version":"1.2.3"}' }, 'inconclusive', 'unexpected-response'],
119
- ['successful version array', { stdout: '["1.2.3"]' }, 'inconclusive', 'unexpected-response'],
120
- ['successful extra output', { stdout: '1.2.3\n1.2.3' }, 'inconclusive', 'unexpected-response'],
121
- ['successful E404', { stdout: 'E404' }, 'inconclusive', 'unexpected-response'],
122
- ['successful version with error', { stdout: '"1.2.3"', stderr: 'npm error code E404' }, 'inconclusive', 'unexpected-response'],
123
- ['process killed after E404', { stdout: 'E404', signal: 'SIGKILL' }, 'inconclusive', 'process-error'],
124
- ]
125
- for (const code of ['E403', 'ENOTFOUND', 'E500', 'ETARGET', 'ENEEDAUTH', 'ECONNRESET']) {
126
- responses.push(
127
- [code, { stderr: `npm error code ${code}`, exitCode: 1 }, 'inconclusive', 'npm-error'],
128
- [`E404 mixed with ${code}`, { stdout: '{"error":{"code":"E404"}}', stderr: `npm error code ${code}`, exitCode: 1 }, 'inconclusive', 'npm-error'],
129
- )
130
- }
131
- for (const [name, reply, status, reason] of responses) {
132
- test(name, (t) => {
133
- const f = fixture(t)
134
- f.reply(reply)
135
- checkResult(f.probe(), status, reason)
136
- assert.equal(f.calls().length, 1)
137
- })
138
- }
139
-
140
- const incidentalJsonRefusals = [
141
- '{"note":"E404"}',
142
- '{"E404":"diagnostic only"}',
143
- '{"error":{"detail":"previous E404; request failed"}}',
144
- '{"note":"E\\u0034\\u0030\\u0034"}',
145
- '"E404"',
146
- '["E404"]',
147
- '{"code":"E404"}',
148
- '{"causes":[{"code":"E404"}]}',
149
- ]
150
- for (const output of incidentalJsonRefusals) {
151
- for (const channel of ['stdout', 'stderr']) {
152
- for (const otherError of [false, true]) {
153
- test(`incidental JSON cannot prove absence on ${channel}: ${output}; other error ${otherError}`, (t) => {
154
- const f = fixture(t)
155
- f.reply({
156
- [channel]: output,
157
- ...(otherError ? { [channel === 'stdout' ? 'stderr' : 'stdout']: 'npm error code E404' } : {}),
158
- exitCode: 1,
159
- })
160
- const result = f.cli()
161
- assert.equal(result.status, 2)
162
- checkResult(JSON.parse(result.stdout), 'inconclusive', 'npm-error')
163
- })
164
- }
165
- }
166
- }
167
-
168
- const structuredRefusals = [
169
- ['unknown code with E404 detail', '{"error":{"code":"UNKNOWN","detail":"upstream E404"}}'],
170
- ['numeric code', '{"error":{"code":404}}'],
171
- ['null code', '{"error":{"code":null}}'],
172
- ['boolean code', '{"error":{"code":false}}'],
173
- ['array code', '{"error":{"code":["E404"]}}'],
174
- ['object code', '{"error":{"code":{"value":"E404"}}}'],
175
- ['empty code', '{"error":{"code":""}}'],
176
- ['mixed code value', '{"error":{"code":"E404 UNKNOWN"}}'],
177
- ['nested unknown code', '{"error":{"code":"E404","causes":[{"code":"UNKNOWN"}]}}'],
178
- ['escaped unknown code key', '{"error":{"co\\u0064e":"UNKNOWN","detail":"E404"}}'],
179
- ['duplicate unknown then E404', '{"error":{"code":"UNKNOWN","code":"E404"}}'],
180
- ['duplicate E404 then unknown', '{"error":{"code":"E404","code":"UNKNOWN"}}'],
181
- ['escaped duplicate code key', '{"error":{"code":"UNKNOWN","co\\u0064e":"E404"}}'],
182
- ['duplicate error object', '{"error":{"code":"UNKNOWN"},"error":{"code":"E404"}}'],
183
- ['duplicate nested unrelated key', '{"error":{"code":"E404","causes":[{"note":"first","note":"second"}]}}'],
184
- ]
185
- for (const [name, output] of structuredRefusals) {
186
- for (const channel of ['stdout', 'stderr']) {
187
- test(`CLI refuses ${name} on ${channel}`, (t) => {
188
- const f = fixture(t)
189
- f.reply({
190
- [channel]: output,
191
- [channel === 'stdout' ? 'stderr' : 'stdout']: `npm error code E404\n${leakMarker}`,
192
- exitCode: 1,
193
- })
194
- const result = f.cli()
195
- assert.equal(result.error, undefined)
196
- assert.equal(result.status, 2)
197
- assert.equal(result.stderr, '')
198
- checkResult(JSON.parse(result.stdout), 'inconclusive', 'npm-error')
199
- assert.equal(f.calls().length, 1)
200
- assert.deepEqual(readdirSync(f.cwd), [])
201
- })
202
- }
203
- }
204
-
205
- test('CLI refuses an exact successful version with an unknown structured stderr code', (t) => {
206
- const f = fixture(t)
207
- f.reply({ stdout: '"1.2.3"', stderr: JSON.stringify({ error: { code: 'UNKNOWN', detail: leakMarker } }) })
208
- const result = f.cli()
209
- assert.equal(result.error, undefined)
210
- assert.equal(result.status, 2)
211
- assert.equal(result.stderr, '')
212
- checkResult(JSON.parse(result.stdout), 'inconclusive', 'unexpected-response')
213
- assert.equal(f.calls().length, 1)
214
- })
215
-
216
- const framingRefusals = [
217
- ['warning then malformed JSON', 'npm warn diagnostic\n{"error":{"code":"E404"', 'unexpected-response'],
218
- ['warning then one JSON object', 'npm warn diagnostic\n{"error":{"code":"E404"}}', 'unexpected-response'],
219
- ['warning then two JSON documents', 'npm warn diagnostic\n{"error":{"code":"E404"}}\n{"error":{"code":"UNKNOWN"}}', 'unexpected-response'],
220
- ['two JSON documents', '{"error":{"code":"E404"}}\n{"error":{"code":"UNKNOWN"}}', 'unexpected-response'],
221
- ['plain E404 then truncated JSON', 'npm error code E404\n{"error":', 'unexpected-response'],
222
- ['warning then JSON string', 'npm warn diagnostic\n"E404"', 'unexpected-response'],
223
- ['warning then JSON array', 'npm warn diagnostic\n[{"code":"E404"},{"code":"UNKNOWN"}]', 'unexpected-response'],
224
- ['YAML instead of JSON syntax', '{error: {code: E404}}', 'unexpected-response'],
225
- ['missing plain code value', 'npm error code', 'npm-error'],
226
- ['mixed plain code value', 'npm ERR! code E404 UNKNOWN', 'npm-error'],
227
- ]
228
- for (const [name, output, reason] of framingRefusals) {
229
- for (const channel of ['stdout', 'stderr']) {
230
- test(`CLI refuses ${name} on ${channel}`, (t) => {
231
- const f = fixture(t)
232
- f.reply({
233
- [channel]: output,
234
- [channel === 'stdout' ? 'stderr' : 'stdout']: `npm error code E404\n${leakMarker}`,
235
- exitCode: 1,
236
- })
237
- const result = f.cli()
238
- assert.equal(result.error, undefined)
239
- assert.equal(result.status, 2)
240
- assert.equal(result.stderr, '')
241
- checkResult(JSON.parse(result.stdout), 'inconclusive', reason)
242
- assert.equal(f.calls().length, 1)
243
- assert.deepEqual(readdirSync(f.cwd), [])
244
- })
245
- }
246
- }
247
-
248
- const invalidNames = [
249
- '', '@scope', '@scope/', '@/widget', '@scope/widget/extra', 'one/two', '@scope/@widget',
250
- 'Widget', '@Scope/widget', '@scope/Widget', '.widget', '_widget', '-widget', '@.scope/widget',
251
- ' widget', 'widget ', 'widget\n', 'widget\r', 'wid\tget', 'wid\0get',
252
- 'widget@1.2.3', 'widget;touch injected', 'widget$(touch injected)', 'widget%2fother',
253
- 'https://example.test/widget.tgz', '../widget', 'caf\u00e9', 'node_modules', 'favicon.ico',
254
- 'a'.repeat(215), `@a/${'b'.repeat(212)}`, null, 3, {}, ['widget'],
255
- ]
256
- const invalidVersions = [
257
- '', 'v1.2.3', 'V1.2.3', '01.2.3', '1.02.3', '1.2.03', '1.2', '1.2.3.4',
258
- '1.2.3-rc.1', '1.2.3+build', '^1.2.3', 'latest', '*', ' 1.2.3', '1.2.3\n',
259
- '1.2.3;touch injected', '9007199254740992.0.0', null, 123, {},
260
- ]
261
- const invalidRegistries = [
262
- '', 'http://registry.example.test', 'ftp://registry.example.test', '//registry.example.test',
263
- 'https:', 'https://', 'https://user:password@registry.example.test', 'https://user@registry.example.test',
264
- 'https://@registry.example.test', 'https://registry.example.test?token=private',
265
- 'https://registry.example.test/#fragment', 'https://registry.example.test/?', 'https://registry.example.test/#',
266
- ' https://registry.example.test', 'https://registry.example.test ',
267
- 'https://registry.example.test/\npath', 'https://registry.example.test/\rpath',
268
- 'https://registry.example.test/\u2028path', 'https://registry.example.test/\u2029path',
269
- 'https://registry.example.test/\tpath', 'https://registry.example.test/\0path',
270
- 'https://registry.example.test\\@other.test', 'https://registry.example.test:bad',
271
- null, 42, {},
272
- ]
273
-
274
- test('bad API parameters fail with a fixed message before spawning any process', (t) => {
275
- const f = fixture(t)
276
- for (const [field, values] of [['packageName', invalidNames], ['version', invalidVersions], ['registry', invalidRegistries], ['cwd', ['', 2, null, 'bad\0directory']]]) {
277
- for (const value of field === 'cwd' ? values : [...values, undefined]) {
278
- assert.throws(() => f.probe({ [field]: value }), { message: 'npm-version-absence: invalid arguments' })
279
- }
280
- }
281
- for (const value of [undefined, null, 'bad', [], 1, {}]) {
282
- assert.throws(() => probeNpmVersionAbsence(value), { message: 'npm-version-absence: invalid arguments' })
283
- }
284
- assert.deepEqual(f.calls(), [])
285
- assert.deepEqual(readdirSync(f.cwd), [])
286
- })
287
-
288
- test('canonical safe names up to 214 characters and stable versions are accepted', (t) => {
289
- const f = fixture(t)
290
- for (const packageName of ['widget', 'widget.js_v2-extra', '@scope-name/widget.js_v2', 'a'.repeat(214), `@a/${'b'.repeat(211)}`]) {
291
- for (const version of ['0.0.0', '10.20.30']) {
292
- checkResult(f.probe({ packageName, version }), 'absent', 'version-absent', { ...request, packageName, version })
293
- }
294
- }
295
- })
296
-
297
- test('explicit HTTPS registries support custom paths, ports and case-insensitive schemes', (t) => {
298
- const f = fixture(t)
299
- for (const registry of ['https://registry.example.test', 'HTTPS://REGISTRY.EXAMPLE.TEST/npm/', 'https://registry.example.test:8443/custom/npm/', 'https://[::1]:8443/custom/']) {
300
- checkResult(f.probe({ registry }), 'absent', 'version-absent', { ...request, registry })
301
- assert.equal(f.calls().at(-1).argv[3], `--registry=${registry}`)
302
- }
303
- })
304
-
305
- test('the API defaults cwd to the calling directory', (t) => {
306
- const f = fixture(t)
307
- const previous = process.cwd()
308
- try {
309
- process.chdir(f.cwd)
310
- checkResult(probeNpmVersionAbsence(request), 'absent', 'version-absent')
311
- } finally {
312
- process.chdir(previous)
313
- }
314
- assert.equal(f.calls()[0].cwd, f.cwd)
315
- })
316
-
317
- for (const failure of ['missing executable', 'missing cwd']) {
318
- test(`real spawn failure: ${failure}`, (t) => {
319
- const f = fixture(t)
320
- if (failure === 'missing executable') rmSync(join(f.bin, 'npm'))
321
- const cwd = failure === 'missing cwd' ? join(f.root, leakMarker) : f.cwd
322
- checkResult(f.probe({ cwd }), 'inconclusive', 'process-error')
323
- assert.deepEqual(f.calls(), [])
324
- })
325
- }
326
-
327
- for (const [name, reply, status, reason] of [
328
- ['exact 1 MiB', { stdout: 'E404\n', stdoutBytes: 1024 * 1024 - 5, exitCode: 1 }, 'absent', 'version-absent'],
329
- ['over 1 MiB stdout', { stdout: 'E404\n', stdoutBytes: 1024 * 1024, exitCode: 1 }, 'inconclusive', 'process-error'],
330
- ['over 1 MiB stderr', { stderr: 'E404\n', stderrBytes: 1024 * 1024, exitCode: 1 }, 'inconclusive', 'process-error'],
331
- ['over 1 MiB combined', { stdout: 'E404\n', stdoutBytes: 600 * 1024, stderrBytes: 600 * 1024, exitCode: 1 }, 'inconclusive', 'process-error'],
332
- ]) {
333
- test(`bounded output: ${name}`, (t) => {
334
- const f = fixture(t)
335
- f.reply(reply)
336
- checkResult(f.probe(), status, reason)
337
- })
338
- }
339
-
340
- for (const [name, reply, status, reason, exitCode] of [
341
- ['absence', { stdout: '{"error":{"code":"E404"}}', stderr: leakMarker, exitCode: 1 }, 'absent', 'version-absent', 0],
342
- ['presence', { stdout: '"1.2.3"', stderr: leakMarker }, 'present', 'version-present', 1],
343
- ['inconclusive', { stdout: leakMarker, stderr: 'npm error code E403', exitCode: 1 }, 'inconclusive', 'npm-error', 2],
344
- ['signal death', { stdout: `E404 ${leakMarker}`, signal: 'SIGKILL' }, 'inconclusive', 'process-error', 2],
345
- ]) {
346
- test(`CLI maps ${name} to exit ${exitCode} without forwarding child output`, (t) => {
347
- const f = fixture(t)
348
- f.reply(reply)
349
- const result = f.cli()
350
- assert.equal(result.error, undefined)
351
- assert.equal(result.status, exitCode)
352
- assert.equal(result.stderr, '')
353
- checkResult(JSON.parse(result.stdout), status, reason)
354
- assert.equal(result.stdout.includes(leakMarker), false)
355
- assert.deepEqual(f.calls(), [{
356
- argv: ['view', '@example/widget@1.2.3', 'version', `--registry=${request.registry}`, '--json'], cwd: f.cwd,
357
- }])
358
- assert.deepEqual(readdirSync(f.cwd), [])
359
- })
360
- }
361
-
362
- test('CLI rejects missing, duplicate, unknown and unsafe arguments without registry discovery or raw errors', (t) => {
363
- const f = fixture(t)
364
- const valid = ['--package', request.packageName, '--version', request.version, '--registry', request.registry]
365
- const invalidArgs = [
366
- [], valid.slice(0, 4), valid.slice(2), [...valid.slice(0, 2), ...valid.slice(4)],
367
- [...valid, '--registry', request.registry], [...valid, '--package', request.packageName],
368
- [...valid, '--version', request.version], [...valid, '--timeout', '1'],
369
- [...valid, '--help'], [...valid, `--unknown-${leakMarker}`],
370
- [...valid, '--registry'], ['--package', leakMarker, ...valid.slice(2)],
371
- ['--package', request.packageName, '--version', '1.2.3', '--registry', `https://user:${leakMarker}@registry.example.test`],
372
- ]
373
- for (const args of invalidArgs) {
374
- const result = f.cli(args)
375
- assert.equal(result.error, undefined)
376
- assert.equal(result.status, 2)
377
- assert.equal(result.stdout, '')
378
- assert.equal(result.stderr, 'npm-version-absence: invalid arguments\n')
379
- }
380
- assert.deepEqual(f.calls(), [])
381
- assert.deepEqual(readdirSync(f.cwd), [])
382
- })
383
-
384
- test('probing preserves files and tags and invokes no publisher or provider even with shell metacharacters', (t) => {
385
- const f = fixture(t)
386
- mkdirSync(join(f.cwd, '.git', 'refs', 'tags'), { recursive: true })
387
- writeFileSync(join(f.cwd, '.git', 'refs', 'tags', 'existing'), 'historical-tag-sha\n')
388
- writeFileSync(join(f.cwd, 'unchanged'), leakMarker)
389
- const registry = 'https://registry.example.test/custom/npm/;git'
390
- f.reply({ stdout: JSON.stringify({ error: { code: 'E404', detail: leakMarker } }), exitCode: 1 })
391
- checkResult(f.probe({ registry }), 'absent', 'version-absent', { ...request, registry })
392
- assert.deepEqual(f.calls(), [{
393
- argv: ['view', '@example/widget@1.2.3', 'version', `--registry=${registry}`, '--json'], cwd: f.cwd,
394
- }])
395
- assert.deepEqual(readdirSync(f.cwd).sort(), ['.git', 'unchanged'])
396
- assert.deepEqual(readdirSync(join(f.cwd, '.git'), { recursive: true }).sort(), ['refs', 'refs/tags', 'refs/tags/existing'])
397
- assert.equal(readFileSync(join(f.cwd, '.git', 'refs', 'tags', 'existing'), 'utf8'), 'historical-tag-sha\n')
398
- assert.equal(readFileSync(join(f.cwd, 'unchanged'), 'utf8'), leakMarker)
399
- })
400
-
401
- test('importing the module does not run npm or the CLI', (t) => {
402
- const f = fixture(t)
403
- const result = spawnSync(process.execPath, ['--input-type=module', '-e', `await import(${JSON.stringify(new URL('./npm-version-absence.mjs', import.meta.url).href)})`], {
404
- cwd: f.cwd, env: process.env, encoding: 'utf8', timeout: 10_000,
405
- })
406
- assert.equal(result.error, undefined)
407
- assert.equal(result.status, 0)
408
- assert.equal(result.stdout, '')
409
- assert.equal(result.stderr, '')
410
- assert.deepEqual(f.calls(), [])
411
- })
412
-
413
- test('the CLI still blocks a present version when launched through a symlink', (t) => {
414
- const f = fixture(t)
415
- const link = join(f.root, 'installed-helper.mjs')
416
- symlinkSync(script, link)
417
- f.reply({ stdout: '"1.2.3"' })
418
- const result = spawnSync(process.execPath, [link, '--package', request.packageName, '--version', request.version, '--registry', request.registry], {
419
- cwd: f.cwd, env: process.env, encoding: 'utf8', timeout: 10_000,
420
- })
421
- assert.equal(result.error, undefined)
422
- assert.equal(result.status, 1)
423
- assert.equal(result.stderr, '')
424
- checkResult(JSON.parse(result.stdout), 'present', 'version-present')
425
- assert.equal(f.calls().length, 1)
426
- })