@1aboveio/skills 0.19.2 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/package.json +1 -1
- package/runtime/skills/distribution/generated/recipes.json +54 -48
- package/runtime/skills/distribution/scripts/bundles.mjs +113 -7
- package/runtime/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +4 -6
- package/runtime/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +3 -36
- package/skills/cicd-pipeline/mergify/SKILL.md +1 -1
- package/skills/engineering/engineering-runtime/coherence/workflow.json +14 -83
- package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +4 -6
- package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +3 -36
- package/skills/engineering/smoke/SKILL.md +1 -1
- package/skills/payment/payment-analysis/SKILL.md +13 -7
- package/skills/payment/payment-analysis/references/chargebacks.md +43 -18
- package/skills/payment/payment-analysis/references/event-layers.md +4 -0
- package/skills/payment/payment-analysis/references/metrics.md +9 -7
- package/skills/payment/payment-analysis/references/report-template.md +3 -3
- package/skills/payment/payment-analysis/references/terminology.md +6 -1
- package/skills/payment/payment-analysis/references/visualization.md +1 -1
- package/skills/payment/tld-detection/SKILL.md +163 -0
- package/skills/payment/tld-detection/agents/openai.yaml +9 -0
- package/skills/payment/tld-detection/evals/evals.json +107 -0
- package/skills/payment/tld-detection/references/clean.md +51 -0
- package/skills/payment/tld-detection/references/clustering.md +219 -0
- package/skills/payment/tld-detection/references/decision.md +85 -0
- package/skills/payment/tld-detection/references/report-template.md +169 -0
- package/skills/payment/tld-detection/references/terminology.md +69 -0
- package/skills/payment/tld-detection/references/workflow.md +78 -0
- package/skills/payment/tld-detection/scripts/detect_ladders.py +390 -0
- package/skills/payment/tld-detection/tests/test_detect_ladders.py +132 -0
- package/skills/engineering/resolve-issues/SKILL.md +0 -168
- package/skills/engineering/resolve-issues/agents/openai.yaml +0 -9
- package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +0 -499
- package/skills/engineering/resolve-issues/references/breaker.md +0 -82
- package/skills/engineering/resolve-issues/references/deliverables.md +0 -27
- package/skills/engineering/resolve-issues/references/delivery.md +0 -108
- package/skills/engineering/resolve-issues/references/evidence-lane.md +0 -21
- package/skills/engineering/resolve-issues/references/exact-head-ci.md +0 -287
- package/skills/engineering/resolve-issues/references/fan-out.md +0 -33
- package/skills/engineering/resolve-issues/references/finalization.md +0 -68
- package/skills/engineering/resolve-issues/references/guarantees.md +0 -10
- package/skills/engineering/resolve-issues/references/high-risk.md +0 -29
- package/skills/engineering/resolve-issues/references/incidents/848/README.md +0 -156
- package/skills/engineering/resolve-issues/references/intake.md +0 -86
- package/skills/engineering/resolve-issues/references/integration-gate.md +0 -53
- package/skills/engineering/resolve-issues/references/interference.md +0 -87
- package/skills/engineering/resolve-issues/references/loop.md +0 -134
- package/skills/engineering/resolve-issues/references/model-catalog.md +0 -9
- package/skills/engineering/resolve-issues/references/postmortem.md +0 -27
- package/skills/engineering/resolve-issues/references/pre-flight-model-slots.md +0 -41
- package/skills/engineering/resolve-issues/references/pre-flight-recording-and-checkout.md +0 -48
- package/skills/engineering/resolve-issues/references/pre-flight.md +0 -41
- package/skills/engineering/resolve-issues/references/regression-checklist.md +0 -26
- package/skills/engineering/resolve-issues/references/run-state.md +0 -288
- package/skills/engineering/resolve-issues/references/sandboxed-testing.md +0 -48
- package/skills/engineering/resolve-issues/references/spawn-contract.md +0 -96
- package/skills/engineering/resolve-issues/references/terminal-evidence-journal.md +0 -40
- package/skills/engineering/resolve-issues/references/why.md +0 -653
- package/skills/engineering/resolve-issues/schemas/fix-round.schema.json +0 -49
- package/skills/engineering/resolve-issues/scripts/combine-and-verify.mjs +0 -721
- package/skills/engineering/resolve-issues/scripts/component-candidate.mjs +0 -962
- package/skills/engineering/resolve-issues/scripts/contract-revision.mjs +0 -220
- package/skills/engineering/resolve-issues/scripts/detect-delivery-mode.mjs +0 -420
- package/skills/engineering/resolve-issues/scripts/detect-target-branch.mjs +0 -256
- package/skills/engineering/resolve-issues/scripts/detect-workspace-mode.mjs +0 -168
- package/skills/engineering/resolve-issues/scripts/discover-models.mjs +0 -9
- package/skills/engineering/resolve-issues/scripts/doctrine.mjs +0 -62
- package/skills/engineering/resolve-issues/scripts/evidence-lifecycle-contract.mjs +0 -191
- package/skills/engineering/resolve-issues/scripts/exact-head-ci.mjs +0 -413
- package/skills/engineering/resolve-issues/scripts/exact-head-github-provider.mjs +0 -332
- package/skills/engineering/resolve-issues/scripts/finalize.mjs +0 -488
- package/skills/engineering/resolve-issues/scripts/fix-rounds.mjs +0 -3307
- package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-circular-1001.json +0 -16
- package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-valid-sequencing.json +0 -51
- package/skills/engineering/resolve-issues/scripts/fixtures/fmm-express-830-component-candidate.json +0 -17
- package/skills/engineering/resolve-issues/scripts/fixtures/head-check-set-1081.json +0 -166
- package/skills/engineering/resolve-issues/scripts/gate-value-series.mjs +0 -92
- package/skills/engineering/resolve-issues/scripts/guide-index.mjs +0 -73
- package/skills/engineering/resolve-issues/scripts/head-check-set.mjs +0 -159
- package/skills/engineering/resolve-issues/scripts/interference.mjs +0 -427
- package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +0 -9
- package/skills/engineering/resolve-issues/scripts/next-operations.mjs +0 -419
- package/skills/engineering/resolve-issues/scripts/postmortem.mjs +0 -909
- package/skills/engineering/resolve-issues/scripts/preflight-questions.mjs +0 -345
- package/skills/engineering/resolve-issues/scripts/reconcile-contained-unit-prs.mjs +0 -415
- package/skills/engineering/resolve-issues/scripts/release-state-contract.mjs +0 -697
- package/skills/engineering/resolve-issues/scripts/report.mjs +0 -494
- package/skills/engineering/resolve-issues/scripts/required-check-plan.mjs +0 -172
- package/skills/engineering/resolve-issues/scripts/round-metadata.mjs +0 -79
- package/skills/engineering/resolve-issues/scripts/run-state-review6-cases.mjs +0 -334
- package/skills/engineering/resolve-issues/scripts/run-state.mjs +0 -4792
- package/skills/engineering/resolve-issues/scripts/sandbox-selftest.mjs +0 -395
- package/skills/engineering/resolve-issues/scripts/spawn-contract.mjs +0 -290
- package/skills/engineering/resolve-issues/scripts/terminal-dispositions.mjs +0 -170
- package/skills/engineering/resolve-issues/scripts/terminal-evidence-journal.mjs +0 -293
- package/skills/engineering/resolve-issues/scripts/unit-kind.mjs +0 -197
- package/skills/engineering/resolve-issues/scripts/unit-lifecycle.mjs +0 -127
- package/skills/engineering/resolve-issues/scripts/watch-delivery.mjs +0 -893
- package/skills/engineering/resolve-issues/scripts/workspaces.mjs +0 -829
- package/skills/engineering/resolve-issues/workflows/independent-review.workflow.js +0 -290
- package/skills/engineering/resolve-issues/workflows/prior-art-scan.workflow.js +0 -80
- package/skills/engineering/resolve-issues/workflows/workflow-smoke.mjs +0 -102
- package/skills/engineering/resolve-release/LICENSE +0 -3
- package/skills/engineering/resolve-release/SKILL.md +0 -113
- package/skills/engineering/resolve-release/agents/openai.yaml +0 -9
- package/skills/engineering/resolve-release/references/assembly.md +0 -137
- package/skills/engineering/resolve-release/references/auto-when-green.md +0 -56
- package/skills/engineering/resolve-release/references/candidate.md +0 -167
- package/skills/engineering/resolve-release/references/exposure.md +0 -178
- package/skills/engineering/resolve-release/references/handoff.md +0 -24
- package/skills/engineering/resolve-release/references/postmortem.md +0 -230
- package/skills/engineering/resolve-release/references/preflight.md +0 -208
- package/skills/engineering/resolve-release/references/principles.md +0 -94
- package/skills/engineering/resolve-release/references/regression-checklist.md +0 -36
- package/skills/engineering/resolve-release/references/related-skills.md +0 -13
- package/skills/engineering/resolve-release/references/routing.md +0 -149
- package/skills/engineering/resolve-release/references/verified-sha-github-flow.md +0 -285
- package/skills/engineering/resolve-release/references/versioning.md +0 -202
- package/skills/engineering/resolve-release/references/why.md +0 -53
- package/skills/engineering/resolve-release/scripts/adapter-completion-artifact.mjs +0 -389
- package/skills/engineering/resolve-release/scripts/build-changes.mjs +0 -209
- package/skills/engineering/resolve-release/scripts/candidate-hygiene.mjs +0 -407
- package/skills/engineering/resolve-release/scripts/candidate-identity.mjs +0 -904
- package/skills/engineering/resolve-release/scripts/candidate-traffic.mjs +0 -81
- package/skills/engineering/resolve-release/scripts/checked-adapter-loader.mjs +0 -612
- package/skills/engineering/resolve-release/scripts/close-attempt.mjs +0 -135
- package/skills/engineering/resolve-release/scripts/closeout-release.mjs +0 -161
- package/skills/engineering/resolve-release/scripts/doctrine.mjs +0 -106
- package/skills/engineering/resolve-release/scripts/durable-processing.mjs +0 -522
- package/skills/engineering/resolve-release/scripts/ensure-target-green.mjs +0 -659
- package/skills/engineering/resolve-release/scripts/evidence-bundle.mjs +0 -1014
- package/skills/engineering/resolve-release/scripts/finalize-release.mjs +0 -526
- package/skills/engineering/resolve-release/scripts/fixtures/durable-processing-adapter.mjs +0 -169
- package/skills/engineering/resolve-release/scripts/green-gate.mjs +0 -599
- package/skills/engineering/resolve-release/scripts/isolated-adapter-evaluator.mjs +0 -752
- package/skills/engineering/resolve-release/scripts/metadata-pr-status.mjs +0 -56
- package/skills/engineering/resolve-release/scripts/metadata-sync.mjs +0 -1538
- package/skills/engineering/resolve-release/scripts/postmortem.mjs +0 -381
- package/skills/engineering/resolve-release/scripts/preflight-probes.mjs +0 -510
- package/skills/engineering/resolve-release/scripts/production-endpoints.mjs +0 -326
- package/skills/engineering/resolve-release/scripts/rc-circuit-breaker.mjs +0 -272
- package/skills/engineering/resolve-release/scripts/report.mjs +0 -417
- package/skills/engineering/resolve-release/scripts/reprobe-credentials.mjs +0 -114
- package/skills/engineering/resolve-release/scripts/revalidate-candidate.mjs +0 -238
- package/skills/engineering/resolve-release/scripts/review-packet.mjs +0 -503
- package/skills/engineering/resolve-release/scripts/rollback-floor.mjs +0 -263
- package/skills/engineering/resolve-release/scripts/version-assert.mjs +0 -339
- package/skills/engineering/resolve-release/scripts/version-postmortem.mjs +0 -485
- package/skills/engineering/resolve-release/scripts/version.mjs +0 -1199
- package/skills/engineering/resolve-release/scripts/watch-candidate-delivery.mjs +0 -449
- package/skills/engineering/resolve-release/vendor/ACORN-LICENSE +0 -21
- package/skills/engineering/resolve-release/vendor/README.md +0 -60
- package/skills/engineering/resolve-release/vendor/acorn.mjs +0 -6233
- package/skills/engineering/resolve-release/vendor/quickjs/core/LICENSE +0 -21
- package/skills/engineering/resolve-release/vendor/quickjs/core/README.md +0 -341
- package/skills/engineering/resolve-release/vendor/quickjs/core/README.template.md +0 -70
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs +0 -7
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.mts +0 -2033
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.ts +0 -2033
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js +0 -7
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/core/package.json +0 -49
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/LICENSE +0 -21
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/README.md +0 -5
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.mts +0 -549
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.ts +0 -549
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/package.json +0 -36
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/LICENSE +0 -47
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/README.md +0 -82
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.d.ts +0 -11
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.mjs +0 -22
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cjs +0 -21
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.cjs +0 -21
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.d.ts +0 -11
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.d.ts +0 -11
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.mjs +0 -25
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.wasm +0 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.mts +0 -85
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.ts +0 -85
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.mts +0 -20
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.ts +0 -20
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/package.json +0 -61
- /package/skills/{engineering/resolve-issues → payment/tld-detection}/LICENSE +0 -0
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import crypto from 'node:crypto'
|
|
4
|
-
import fs from 'node:fs'
|
|
5
|
-
import { TextDecoder } from 'node:util'
|
|
6
|
-
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
7
|
-
|
|
8
|
-
export const COMPLETION_SCHEMA = 'resolve-release/isolated-adapter-completion@2'
|
|
9
|
-
export const COMPLETION_ARTIFACT_MAX_BYTES = 1024 * 1024
|
|
10
|
-
export const COMPLETION_COUNT_MAX = 1_000_000
|
|
11
|
-
export const COMPLETION_MATRICES = Object.freeze({
|
|
12
|
-
ambient: 81,
|
|
13
|
-
processAdjacent: 18,
|
|
14
|
-
generated: 81,
|
|
15
|
-
})
|
|
16
|
-
export const COMPLETION_POLICY = Object.freeze({
|
|
17
|
-
id: 'resolve-release/isolated-adapter-completion-policy@1',
|
|
18
|
-
suites: Object.freeze({
|
|
19
|
-
focused: Object.freeze({
|
|
20
|
-
id: 'resolve-release/isolated-adapter-focused@1',
|
|
21
|
-
minimumPassed: 77,
|
|
22
|
-
}),
|
|
23
|
-
full: Object.freeze({
|
|
24
|
-
id: 'resolve-release/full-suite@1',
|
|
25
|
-
minimumPassed: 900,
|
|
26
|
-
}),
|
|
27
|
-
}),
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
const SHA_PATTERN = /^[a-f0-9]{40}$/
|
|
31
|
-
const HASH_PATTERN = /^[a-f0-9]{64}$/
|
|
32
|
-
const ROOT_FIELDS = Object.freeze([
|
|
33
|
-
'schema', 'status', 'issue', 'parent', 'epic', 'componentHead',
|
|
34
|
-
'callerVerifiedHead', 'publicLoaderExecuted', 'matrices', 'policy', 'tests', 'integrity',
|
|
35
|
-
])
|
|
36
|
-
const TEST_FIELDS = Object.freeze(['suite', 'passed', 'failed', 'floor'])
|
|
37
|
-
const INTEGRITY_FIELDS = Object.freeze(['algorithm', 'sha256'])
|
|
38
|
-
const UTF8_DECODER = new TextDecoder('utf-8', { fatal: true })
|
|
39
|
-
|
|
40
|
-
function fail(code) {
|
|
41
|
-
const error = new Error(code)
|
|
42
|
-
error.code = code
|
|
43
|
-
throw error
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function isObject(value) {
|
|
47
|
-
return value != null && typeof value === 'object' && !Array.isArray(value)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function exactFields(value, expected, code) {
|
|
51
|
-
if (!isObject(value) || Object.keys(value).sort().join('\0') !== [...expected].sort().join('\0')) fail(code)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function positiveInteger(value, code) {
|
|
55
|
-
if (!Number.isSafeInteger(value) || value <= 0 || value > COMPLETION_COUNT_MAX) fail(code)
|
|
56
|
-
return value
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function boundedInteger(value, code) {
|
|
60
|
-
if (!Number.isSafeInteger(value) || value < 0 || value > COMPLETION_COUNT_MAX) fail(code)
|
|
61
|
-
return value
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function canonicalize(value) {
|
|
65
|
-
if (Array.isArray(value)) return `[${value.map(canonicalize).join(',')}]`
|
|
66
|
-
if (isObject(value)) {
|
|
67
|
-
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalize(value[key])}`).join(',')}}`
|
|
68
|
-
}
|
|
69
|
-
const encoded = JSON.stringify(value)
|
|
70
|
-
if (encoded === undefined) fail('e:completion-not-serializable')
|
|
71
|
-
return encoded
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function digestBody(body) {
|
|
75
|
-
return crypto.createHash('sha256').update(canonicalize(body)).digest('hex')
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function completionBody({
|
|
79
|
-
componentHead,
|
|
80
|
-
callerVerifiedHead,
|
|
81
|
-
focusedPassed,
|
|
82
|
-
focusedFailed = 0,
|
|
83
|
-
focusedFloor,
|
|
84
|
-
fullPassed,
|
|
85
|
-
fullFailed = 0,
|
|
86
|
-
fullFloor,
|
|
87
|
-
}) {
|
|
88
|
-
if (!SHA_PATTERN.test(componentHead ?? '') || !SHA_PATTERN.test(callerVerifiedHead ?? '')) {
|
|
89
|
-
fail('e:completion-head-invalid')
|
|
90
|
-
}
|
|
91
|
-
if (componentHead !== callerVerifiedHead) fail('e:completion-caller-head-mismatch')
|
|
92
|
-
const focused = {
|
|
93
|
-
suite: COMPLETION_POLICY.suites.focused.id,
|
|
94
|
-
passed: positiveInteger(focusedPassed, 'e:completion-focused-count-invalid'),
|
|
95
|
-
failed: boundedInteger(focusedFailed, 'e:completion-focused-count-invalid'),
|
|
96
|
-
floor: positiveInteger(focusedFloor, 'e:completion-focused-floor-invalid'),
|
|
97
|
-
}
|
|
98
|
-
const full = {
|
|
99
|
-
suite: COMPLETION_POLICY.suites.full.id,
|
|
100
|
-
passed: positiveInteger(fullPassed, 'e:completion-full-count-invalid'),
|
|
101
|
-
failed: boundedInteger(fullFailed, 'e:completion-full-count-invalid'),
|
|
102
|
-
floor: positiveInteger(fullFloor, 'e:completion-full-floor-invalid'),
|
|
103
|
-
}
|
|
104
|
-
if (focused.floor !== COMPLETION_POLICY.suites.focused.minimumPassed) {
|
|
105
|
-
fail('e:completion-focused-policy-floor-mismatch')
|
|
106
|
-
}
|
|
107
|
-
if (full.floor !== COMPLETION_POLICY.suites.full.minimumPassed) {
|
|
108
|
-
fail('e:completion-full-policy-floor-mismatch')
|
|
109
|
-
}
|
|
110
|
-
if (focused.failed !== 0 || focused.passed < focused.floor) fail('e:completion-focused-floor-unmet')
|
|
111
|
-
if (full.failed !== 0 || full.passed < full.floor) fail('e:completion-full-floor-unmet')
|
|
112
|
-
return {
|
|
113
|
-
schema: COMPLETION_SCHEMA,
|
|
114
|
-
status: 'complete',
|
|
115
|
-
issue: 980,
|
|
116
|
-
parent: 939,
|
|
117
|
-
epic: 904,
|
|
118
|
-
componentHead,
|
|
119
|
-
callerVerifiedHead,
|
|
120
|
-
publicLoaderExecuted: true,
|
|
121
|
-
matrices: { ...COMPLETION_MATRICES },
|
|
122
|
-
policy: COMPLETION_POLICY.id,
|
|
123
|
-
tests: { focused, full },
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function createCompletionArtifact(input) {
|
|
128
|
-
const body = completionBody(input)
|
|
129
|
-
return Object.freeze({
|
|
130
|
-
...body,
|
|
131
|
-
matrices: Object.freeze(body.matrices),
|
|
132
|
-
tests: Object.freeze({
|
|
133
|
-
focused: Object.freeze(body.tests.focused),
|
|
134
|
-
full: Object.freeze(body.tests.full),
|
|
135
|
-
}),
|
|
136
|
-
integrity: Object.freeze({ algorithm: 'sha256', sha256: digestBody(body) }),
|
|
137
|
-
})
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function artifactStat(file, code) {
|
|
141
|
-
try {
|
|
142
|
-
return fs.lstatSync(file, { bigint: true })
|
|
143
|
-
} catch {
|
|
144
|
-
fail(code)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function sameArtifactIdentity(left, right) {
|
|
149
|
-
return left.dev === right.dev
|
|
150
|
-
&& left.ino === right.ino
|
|
151
|
-
&& left.mode === right.mode
|
|
152
|
-
&& left.size === right.size
|
|
153
|
-
&& left.mtimeNs === right.mtimeNs
|
|
154
|
-
&& left.ctimeNs === right.ctimeNs
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function validateArtifactStat(stat) {
|
|
158
|
-
if (stat.isSymbolicLink()) fail('e:completion-artifact-symlink')
|
|
159
|
-
if (!stat.isFile()) fail('e:completion-artifact-not-regular-file')
|
|
160
|
-
if ((stat.mode & 0o222n) !== 0n) fail('e:completion-artifact-writable')
|
|
161
|
-
if (stat.size > BigInt(COMPLETION_ARTIFACT_MAX_BYTES)) fail('e:completion-artifact-too-large')
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function openArtifact(file) {
|
|
165
|
-
if (!Number.isInteger(fs.constants.O_NOFOLLOW)) fail('e:completion-artifact-nofollow-unsupported')
|
|
166
|
-
if (!Number.isInteger(fs.constants.O_NONBLOCK)) fail('e:completion-artifact-nonblock-unsupported')
|
|
167
|
-
try {
|
|
168
|
-
return fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK)
|
|
169
|
-
} catch {
|
|
170
|
-
fail('e:completion-artifact-unreadable')
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function readArtifactBytes(fd, expectedSize) {
|
|
175
|
-
const size = Number(expectedSize)
|
|
176
|
-
const bytes = Buffer.alloc(size)
|
|
177
|
-
let offset = 0
|
|
178
|
-
while (offset < size) {
|
|
179
|
-
const count = fs.readSync(fd, bytes, offset, size - offset, offset)
|
|
180
|
-
if (count === 0) fail('e:completion-artifact-changed-during-acquisition')
|
|
181
|
-
offset += count
|
|
182
|
-
}
|
|
183
|
-
const extra = Buffer.alloc(1)
|
|
184
|
-
if (fs.readSync(fd, extra, 0, 1, offset) !== 0) {
|
|
185
|
-
fail('e:completion-artifact-changed-during-acquisition')
|
|
186
|
-
}
|
|
187
|
-
return bytes
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function readCheckedCompletionArtifact(file) {
|
|
191
|
-
if (typeof file !== 'string' || file.length === 0) fail('e:completion-artifact-unreadable')
|
|
192
|
-
const approved = artifactStat(file, 'e:completion-artifact-unreadable')
|
|
193
|
-
validateArtifactStat(approved)
|
|
194
|
-
|
|
195
|
-
const fd = openArtifact(file)
|
|
196
|
-
let bytes
|
|
197
|
-
let opened
|
|
198
|
-
let acquisitionError
|
|
199
|
-
try {
|
|
200
|
-
opened = fs.fstatSync(fd, { bigint: true })
|
|
201
|
-
validateArtifactStat(opened)
|
|
202
|
-
if (!sameArtifactIdentity(approved, opened)) fail('e:completion-artifact-replaced-before-open')
|
|
203
|
-
|
|
204
|
-
const afterOpenPath = artifactStat(file, 'e:completion-artifact-replaced-after-open')
|
|
205
|
-
if (!sameArtifactIdentity(opened, afterOpenPath)) {
|
|
206
|
-
fail('e:completion-artifact-replaced-after-open')
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
bytes = readArtifactBytes(fd, opened.size)
|
|
210
|
-
const afterRead = fs.fstatSync(fd, { bigint: true })
|
|
211
|
-
if (!sameArtifactIdentity(opened, afterRead)) {
|
|
212
|
-
fail('e:completion-artifact-changed-during-acquisition')
|
|
213
|
-
}
|
|
214
|
-
const afterReadPath = artifactStat(file, 'e:completion-artifact-replaced-after-open')
|
|
215
|
-
if (!sameArtifactIdentity(opened, afterReadPath)) {
|
|
216
|
-
fail('e:completion-artifact-replaced-after-open')
|
|
217
|
-
}
|
|
218
|
-
} catch (error) {
|
|
219
|
-
acquisitionError = error?.code?.startsWith?.('e:completion-')
|
|
220
|
-
? error
|
|
221
|
-
: Object.assign(new Error('e:completion-artifact-unreadable'), {
|
|
222
|
-
code: 'e:completion-artifact-unreadable',
|
|
223
|
-
})
|
|
224
|
-
} finally {
|
|
225
|
-
try {
|
|
226
|
-
fs.closeSync(fd)
|
|
227
|
-
} catch {
|
|
228
|
-
if (acquisitionError == null) {
|
|
229
|
-
acquisitionError = Object.assign(new Error('e:completion-artifact-close-failed'), {
|
|
230
|
-
code: 'e:completion-artifact-close-failed',
|
|
231
|
-
})
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
if (acquisitionError != null) throw acquisitionError
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
return UTF8_DECODER.decode(bytes)
|
|
239
|
-
} catch {
|
|
240
|
-
fail('e:completion-artifact-invalid-utf8')
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function parseCheckedCompletionArtifact(file) {
|
|
245
|
-
const text = readCheckedCompletionArtifact(file)
|
|
246
|
-
try {
|
|
247
|
-
return JSON.parse(text)
|
|
248
|
-
} catch {
|
|
249
|
-
fail('e:completion-artifact-json-invalid')
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function validateTestResult(value, name) {
|
|
254
|
-
exactFields(value, TEST_FIELDS, `e:completion-${name}-shape-invalid`)
|
|
255
|
-
const trusted = COMPLETION_POLICY.suites[name]
|
|
256
|
-
if (value.suite !== trusted.id) fail(`e:completion-${name}-suite-unsupported`)
|
|
257
|
-
const passed = positiveInteger(value.passed, `e:completion-${name}-count-invalid`)
|
|
258
|
-
const failed = boundedInteger(value.failed, `e:completion-${name}-count-invalid`)
|
|
259
|
-
const floor = positiveInteger(value.floor, `e:completion-${name}-floor-invalid`)
|
|
260
|
-
if (floor !== trusted.minimumPassed) fail(`e:completion-${name}-policy-floor-mismatch`)
|
|
261
|
-
if (failed !== 0 || passed < floor) fail(`e:completion-${name}-floor-unmet`)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function verifyCompletionValue(artifact, { expectedComponentHead, expectedSha256 }) {
|
|
265
|
-
exactFields(artifact, ROOT_FIELDS, 'e:completion-shape-invalid')
|
|
266
|
-
if (artifact.schema !== COMPLETION_SCHEMA || artifact.status !== 'complete'
|
|
267
|
-
|| artifact.issue !== 980 || artifact.parent !== 939 || artifact.epic !== 904
|
|
268
|
-
|| artifact.publicLoaderExecuted !== true || artifact.policy !== COMPLETION_POLICY.id) {
|
|
269
|
-
fail('e:completion-contract-invalid')
|
|
270
|
-
}
|
|
271
|
-
if (!SHA_PATTERN.test(expectedComponentHead ?? '') || artifact.componentHead !== expectedComponentHead
|
|
272
|
-
|| artifact.callerVerifiedHead !== expectedComponentHead) {
|
|
273
|
-
fail('e:completion-finalizer-head-mismatch')
|
|
274
|
-
}
|
|
275
|
-
exactFields(artifact.matrices, Object.keys(COMPLETION_MATRICES), 'e:completion-matrices-invalid')
|
|
276
|
-
if (Object.entries(COMPLETION_MATRICES).some(([name, count]) => artifact.matrices[name] !== count)) {
|
|
277
|
-
fail('e:completion-matrices-invalid')
|
|
278
|
-
}
|
|
279
|
-
exactFields(artifact.tests, ['focused', 'full'], 'e:completion-tests-shape-invalid')
|
|
280
|
-
validateTestResult(artifact.tests.focused, 'focused')
|
|
281
|
-
validateTestResult(artifact.tests.full, 'full')
|
|
282
|
-
exactFields(artifact.integrity, INTEGRITY_FIELDS, 'e:completion-integrity-invalid')
|
|
283
|
-
if (artifact.integrity.algorithm !== 'sha256' || !HASH_PATTERN.test(artifact.integrity.sha256 ?? '')
|
|
284
|
-
|| !HASH_PATTERN.test(expectedSha256 ?? '')) {
|
|
285
|
-
fail('e:completion-integrity-invalid')
|
|
286
|
-
}
|
|
287
|
-
const { integrity, ...body } = artifact
|
|
288
|
-
const actual = digestBody(body)
|
|
289
|
-
if (integrity.sha256 !== actual || expectedSha256 !== actual) fail('e:completion-sha256-mismatch')
|
|
290
|
-
return Object.freeze({
|
|
291
|
-
status: 'verified',
|
|
292
|
-
policy: COMPLETION_POLICY.id,
|
|
293
|
-
componentHead: artifact.componentHead,
|
|
294
|
-
callerVerifiedHead: artifact.callerVerifiedHead,
|
|
295
|
-
sha256: actual,
|
|
296
|
-
tests: Object.freeze({
|
|
297
|
-
focused: Object.freeze({
|
|
298
|
-
suite: artifact.tests.focused.suite,
|
|
299
|
-
passed: artifact.tests.focused.passed,
|
|
300
|
-
minimumPassed: COMPLETION_POLICY.suites.focused.minimumPassed,
|
|
301
|
-
}),
|
|
302
|
-
full: Object.freeze({
|
|
303
|
-
suite: artifact.tests.full.suite,
|
|
304
|
-
passed: artifact.tests.full.passed,
|
|
305
|
-
minimumPassed: COMPLETION_POLICY.suites.full.minimumPassed,
|
|
306
|
-
}),
|
|
307
|
-
}),
|
|
308
|
-
})
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
export function verifyCompletionArtifact(file, expected) {
|
|
312
|
-
const artifact = parseCheckedCompletionArtifact(file)
|
|
313
|
-
return verifyCompletionValue(artifact, expected)
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function parseArgs(argv) {
|
|
317
|
-
const positional = []
|
|
318
|
-
const options = {}
|
|
319
|
-
const known = new Set([
|
|
320
|
-
'--component-head', '--caller-verified-head', '--focused-passed', '--focused-failed',
|
|
321
|
-
'--focused-floor', '--full-passed', '--full-failed', '--full-floor', '--artifact',
|
|
322
|
-
'--sha256', '--output',
|
|
323
|
-
])
|
|
324
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
325
|
-
const value = argv[index]
|
|
326
|
-
if (!value.startsWith('--')) positional.push(value)
|
|
327
|
-
else {
|
|
328
|
-
if (!known.has(value) || options[value] !== undefined) fail('e:completion-option-invalid')
|
|
329
|
-
const next = argv[++index]
|
|
330
|
-
if (next == null || next.startsWith('--')) fail('e:completion-option-invalid')
|
|
331
|
-
options[value] = next
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
return { positional, options }
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function numberOption(options, name, defaultValue) {
|
|
338
|
-
if (options[name] == null) return defaultValue
|
|
339
|
-
if (!/^\d+$/.test(options[name])) fail('e:completion-option-invalid')
|
|
340
|
-
return Number(options[name])
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function writeOutput(value, output) {
|
|
344
|
-
const text = `${JSON.stringify(value)}\n`
|
|
345
|
-
if (output == null) process.stdout.write(text)
|
|
346
|
-
else fs.writeFileSync(output, text, { encoding: 'utf8', flag: 'wx', mode: 0o444 })
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
export function main(argv) {
|
|
350
|
-
const { positional, options } = parseArgs(argv)
|
|
351
|
-
const command = positional[0]
|
|
352
|
-
if (positional.length !== 1 || !['emit', 'verify'].includes(command)) fail('e:completion-command-invalid')
|
|
353
|
-
const commandOptions = command === 'emit'
|
|
354
|
-
? new Set(['--component-head', '--caller-verified-head', '--focused-passed',
|
|
355
|
-
'--focused-failed', '--focused-floor', '--full-passed', '--full-failed',
|
|
356
|
-
'--full-floor', '--output'])
|
|
357
|
-
: new Set(['--artifact', '--component-head', '--sha256', '--output'])
|
|
358
|
-
if (Object.keys(options).some((name) => !commandOptions.has(name))) fail('e:completion-option-invalid')
|
|
359
|
-
if (command === 'emit') {
|
|
360
|
-
const artifact = createCompletionArtifact({
|
|
361
|
-
componentHead: options['--component-head'],
|
|
362
|
-
callerVerifiedHead: options['--caller-verified-head'],
|
|
363
|
-
focusedPassed: numberOption(options, '--focused-passed'),
|
|
364
|
-
focusedFailed: numberOption(options, '--focused-failed', 0),
|
|
365
|
-
focusedFloor: numberOption(options, '--focused-floor'),
|
|
366
|
-
fullPassed: numberOption(options, '--full-passed'),
|
|
367
|
-
fullFailed: numberOption(options, '--full-failed', 0),
|
|
368
|
-
fullFloor: numberOption(options, '--full-floor'),
|
|
369
|
-
})
|
|
370
|
-
writeOutput(artifact, options['--output'])
|
|
371
|
-
return 0
|
|
372
|
-
}
|
|
373
|
-
if (options['--artifact'] == null) fail('e:completion-option-invalid')
|
|
374
|
-
const verified = verifyCompletionArtifact(options['--artifact'], {
|
|
375
|
-
expectedComponentHead: options['--component-head'],
|
|
376
|
-
expectedSha256: options['--sha256'],
|
|
377
|
-
})
|
|
378
|
-
writeOutput(verified, options['--output'])
|
|
379
|
-
return 0
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (isMainModule(import.meta.url)) {
|
|
383
|
-
try {
|
|
384
|
-
process.exitCode = main(process.argv.slice(2))
|
|
385
|
-
} catch (error) {
|
|
386
|
-
process.stderr.write(`${error?.code ?? 'e:completion-failed'}\n`)
|
|
387
|
-
process.exitCode = 1
|
|
388
|
-
}
|
|
389
|
-
}
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
// build-changes — assemble the `--changes` JSON that `version.mjs plan|changelog` consumes,
|
|
2
|
-
// from the constituent PRs, WITHOUT the two silent failures the hand-run gh→jq recipe carries.
|
|
3
|
-
//
|
|
4
|
-
// `version.mjs plan --changes <json>` reads an array of `{ pr, title, labels?, body?, issues? }`
|
|
5
|
-
// (see version.mjs `readChanges` / `classifyChange`). `gh pr view --json` does NOT emit that
|
|
6
|
-
// shape, and copying its output straight through fails SILENTLY in two ways this script closes:
|
|
7
|
-
//
|
|
8
|
-
// #407 — number → pr, and the vanishing close-links. gh emits `number`, not `pr`, and emits
|
|
9
|
-
// no `issues` field at all. `classifyChange` still computes the bump off `title`/`labels`,
|
|
10
|
-
// so `plan` LOOKS correct — while every `closes #n` link silently vanishes from the cut
|
|
11
|
-
// CHANGELOG, discovered only when a human reads the published notes. This script renames
|
|
12
|
-
// `number` → `pr` and derives `issues` (from gh's `closingIssuesReferences` PLUS a parse of
|
|
13
|
-
// `Closes/Fixes/Resolves #N` in the body), so the links survive.
|
|
14
|
-
//
|
|
15
|
-
// #408 — a BREAKING CHANGE in the body escaping the bump. `classifyChange` (version.mjs:~112)
|
|
16
|
-
// reads `BREAKING[ -]CHANGE:` out of `change.body`, but the hand recipe never fetched
|
|
17
|
-
// `body`, so a breaking footer living only in a PR body was invisible → a `minor` bump where
|
|
18
|
-
// a `major` was owed, a silently under-versioned release. This script fetches `body` and
|
|
19
|
-
// carries it through, so `classifyChange` sees the footer and bumps major.
|
|
20
|
-
//
|
|
21
|
-
// The `gh pr view --json` query below therefore MUST include `body` (for #408) and
|
|
22
|
-
// `closingIssuesReferences` (for #407) alongside `number`/`title`/`labels`. Dropping either from
|
|
23
|
-
// the field list re-opens the corresponding silent failure — the CLI test asserts the query
|
|
24
|
-
// carries both.
|
|
25
|
-
|
|
26
|
-
import fs from 'node:fs'
|
|
27
|
-
import { spawnSync } from 'node:child_process'
|
|
28
|
-
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
29
|
-
|
|
30
|
-
// The gh `--json` field list the query MUST request. `body` is load-bearing for #408 and
|
|
31
|
-
// `closingIssuesReferences` for #407 — see the header. Exported so the test can assert the
|
|
32
|
-
// query the CLI builds still carries both rather than silently regressing.
|
|
33
|
-
export const GH_JSON_FIELDS = ['number', 'title', 'labels', 'body', 'closingIssuesReferences']
|
|
34
|
-
|
|
35
|
-
// GitHub's closing keywords (https://docs.github.com/issues/tracking-your-work-with-issues/
|
|
36
|
-
// linking-a-pull-request-to-an-issue): close/closes/closed, fix/fixes/fixed, resolve/resolves/
|
|
37
|
-
// resolved, case-insensitive, followed by `#N`. This is the same set gh resolves into
|
|
38
|
-
// `closingIssuesReferences`; we parse it out of the body too because a body may name a link gh
|
|
39
|
-
// did not resolve (a cross-repo `owner/repo#N`, a PR opened before the issue existed), and
|
|
40
|
-
// because a caller feeding a captured body without that gh field still gets its links.
|
|
41
|
-
const CLOSING_RE = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\b[:\s]+#(\d+)/gi
|
|
42
|
-
|
|
43
|
-
// Parse `Closes #N` / `Fixes #N` / `Resolves #N` (and their case/tense variants) out of a PR
|
|
44
|
-
// body, returning `#N` strings in first-seen order. Never throws on a null/empty body.
|
|
45
|
-
export function parseClosingIssues(body) {
|
|
46
|
-
const out = []
|
|
47
|
-
const seen = new Set()
|
|
48
|
-
for (const m of String(body || '').matchAll(CLOSING_RE)) {
|
|
49
|
-
const ref = `#${m[1]}`
|
|
50
|
-
if (!seen.has(ref)) {
|
|
51
|
-
seen.add(ref)
|
|
52
|
-
out.push(ref)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return out
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Normalize gh's `labels` (array of `{name}` objects) OR an already-flattened array of strings
|
|
59
|
-
// into a `string[]`. `classifyChange` lowercases labels itself, so we keep the raw name.
|
|
60
|
-
function normalizeLabels(labels) {
|
|
61
|
-
if (!Array.isArray(labels)) return []
|
|
62
|
-
return labels.map((l) => (typeof l === 'string' ? l : l && l.name ? String(l.name) : null)).filter(Boolean)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// The `issues` a change closes: gh's resolved `closingIssuesReferences` FIRST (the authoritative
|
|
66
|
-
// linked-issue field), then any body-parsed `Closes #N` the field did not already carry. Deduped,
|
|
67
|
-
// order-preserving, each as `#N`.
|
|
68
|
-
function deriveIssues(raw) {
|
|
69
|
-
const out = []
|
|
70
|
-
const seen = new Set()
|
|
71
|
-
const push = (ref) => {
|
|
72
|
-
const norm = `#${String(ref).replace(/^#/, '')}`
|
|
73
|
-
if (!seen.has(norm)) {
|
|
74
|
-
seen.add(norm)
|
|
75
|
-
out.push(norm)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (Array.isArray(raw.closingIssuesReferences)) {
|
|
79
|
-
for (const ref of raw.closingIssuesReferences) {
|
|
80
|
-
if (ref && ref.number != null) push(ref.number)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
for (const ref of parseClosingIssues(raw.body)) push(ref)
|
|
84
|
-
return out
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// The pure transform: one raw gh PR object → one `{ pr, title, labels, body, issues }` change.
|
|
88
|
-
// This is where #407's `number` → `pr` rename and `issues` derivation, and #408's `body`
|
|
89
|
-
// pass-through, actually happen.
|
|
90
|
-
export function buildChange(raw) {
|
|
91
|
-
if (!raw || typeof raw !== 'object') {
|
|
92
|
-
throw new TypeError('build-changes: each gh PR record must be an object')
|
|
93
|
-
}
|
|
94
|
-
if (raw.number == null) {
|
|
95
|
-
throw new Error('build-changes: gh PR record has no `number` — was the query missing `number`?')
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
pr: `#${String(raw.number).replace(/^#/, '')}`,
|
|
99
|
-
title: String(raw.title || ''),
|
|
100
|
-
labels: normalizeLabels(raw.labels),
|
|
101
|
-
body: raw.body == null ? '' : String(raw.body),
|
|
102
|
-
issues: deriveIssues(raw),
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// The pure transform over a whole gh result: accepts either the array `gh pr list --json` emits
|
|
107
|
-
// or a single object from `gh pr view --json`, and returns the `--changes` array `version.mjs
|
|
108
|
-
// plan` accepts. No gh, no network — this is the unit under test.
|
|
109
|
-
export function buildChanges(rawGhJson) {
|
|
110
|
-
const records = Array.isArray(rawGhJson) ? rawGhJson : [rawGhJson]
|
|
111
|
-
return records.map(buildChange)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ---- gh (CLI only) ----
|
|
115
|
-
|
|
116
|
-
// Fetch one PR's raw gh JSON. The `--json` list MUST carry GH_JSON_FIELDS (body +
|
|
117
|
-
// closingIssuesReferences included) or the silent failures re-open. `run` is injectable so the
|
|
118
|
-
// CLI path is testable without a live gh (the test puts a captured-gh shim on PATH, which is the
|
|
119
|
-
// default `gh` here).
|
|
120
|
-
function fetchPr(pr, { repo, run = defaultGh } = {}) {
|
|
121
|
-
const args = ['pr', 'view', String(pr), '--json', GH_JSON_FIELDS.join(',')]
|
|
122
|
-
if (repo) args.push('--repo', repo)
|
|
123
|
-
const r = run(args)
|
|
124
|
-
if (r.status !== 0) {
|
|
125
|
-
throw new Error(`build-changes: \`gh ${args.join(' ')}\` failed (exit ${r.status}): ${(r.stderr || '').trim()}`)
|
|
126
|
-
}
|
|
127
|
-
return JSON.parse(r.stdout)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function defaultGh(args) {
|
|
131
|
-
const r = spawnSync('gh', args, { encoding: 'utf8' })
|
|
132
|
-
return { status: r.status ?? 1, stdout: r.stdout || '', stderr: r.stderr || '' }
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const USAGE = `build-changes — assemble the \`--changes\` JSON for \`version.mjs plan|changelog\`.
|
|
136
|
-
|
|
137
|
-
Runs \`gh pr view --json\` for each constituent PR (including \`body\` and
|
|
138
|
-
\`closingIssuesReferences\`), renames gh's \`number\` → the \`pr\` field \`version.mjs\` expects,
|
|
139
|
-
derives \`issues\` (from \`closingIssuesReferences\` + \`Closes/Fixes/Resolves #N\` in the body),
|
|
140
|
-
and carries \`body\` through so a \`BREAKING CHANGE:\` living only in a PR body still bumps major.
|
|
141
|
-
|
|
142
|
-
Usage:
|
|
143
|
-
build-changes <pr>... [--repo owner/name] query gh for each PR, emit the --changes array
|
|
144
|
-
build-changes --raw <file>|- transform captured gh JSON (array or object); no gh call
|
|
145
|
-
build-changes --help | -h print this usage and exit 0
|
|
146
|
-
|
|
147
|
-
Output: a JSON array of { pr, title, labels, body, issues } on stdout — feed it to
|
|
148
|
-
\`version.mjs plan --changes -\` (pipe) or \`--changes <file>\`.
|
|
149
|
-
|
|
150
|
-
Why a script and not the raw gh→jq recipe (both fail SILENTLY):
|
|
151
|
-
#407 gh emits \`number\`, not \`pr\`, and no \`issues\` — copied straight through, the bump still
|
|
152
|
-
computes off titles/labels so \`plan\` looks correct while every \`closes #n\` link vanishes
|
|
153
|
-
from the published notes. This renames number→pr and derives issues.
|
|
154
|
-
#408 the hand recipe never fetched \`body\`, so a \`BREAKING CHANGE:\` in a PR body escaped
|
|
155
|
-
classification → a minor bump where major was owed. This fetches and carries body.
|
|
156
|
-
|
|
157
|
-
Doctrine: references/versioning.md (§6, the bump). Bump/flag reference: version.mjs --help.`
|
|
158
|
-
|
|
159
|
-
export function main(argv, { run = defaultGh, readStdin = () => fs.readFileSync(0, 'utf8') } = {}) {
|
|
160
|
-
const args = [...argv]
|
|
161
|
-
const prs = []
|
|
162
|
-
let repo = null
|
|
163
|
-
let rawSource = null
|
|
164
|
-
|
|
165
|
-
for (let i = 0; i < args.length; i++) {
|
|
166
|
-
const a = args[i]
|
|
167
|
-
if (a === '--help' || a === '-h' || a === 'help') {
|
|
168
|
-
console.log(USAGE)
|
|
169
|
-
return 0
|
|
170
|
-
}
|
|
171
|
-
if (a === '--repo') {
|
|
172
|
-
repo = args[++i]
|
|
173
|
-
continue
|
|
174
|
-
}
|
|
175
|
-
if (a === '--raw') {
|
|
176
|
-
rawSource = args[++i] ?? '-'
|
|
177
|
-
continue
|
|
178
|
-
}
|
|
179
|
-
if (a.startsWith('-') && a !== '-') {
|
|
180
|
-
console.error(`build-changes: unknown flag "${a}" (see --help).`)
|
|
181
|
-
return 2
|
|
182
|
-
}
|
|
183
|
-
prs.push(a)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
try {
|
|
187
|
-
let changes
|
|
188
|
-
if (rawSource != null) {
|
|
189
|
-
const text = rawSource === '-' ? readStdin() : fs.readFileSync(rawSource, 'utf8')
|
|
190
|
-
changes = buildChanges(JSON.parse(text))
|
|
191
|
-
} else {
|
|
192
|
-
if (prs.length === 0) {
|
|
193
|
-
console.error('build-changes: give one or more PR numbers, or --raw <file>|- (see --help).')
|
|
194
|
-
return 2
|
|
195
|
-
}
|
|
196
|
-
changes = buildChanges(prs.map((pr) => fetchPr(pr, { repo, run })))
|
|
197
|
-
}
|
|
198
|
-
console.log(JSON.stringify(changes, null, 2))
|
|
199
|
-
return 0
|
|
200
|
-
} catch (err) {
|
|
201
|
-
console.error(`build-changes: ${err.message}`)
|
|
202
|
-
return 1
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Only run the CLI when invoked directly (so tests import the pure transforms).
|
|
207
|
-
if (isMainModule(import.meta.url)) {
|
|
208
|
-
process.exit(main(process.argv.slice(2)))
|
|
209
|
-
}
|