rcf-lite 0.14.0 → 0.16.0
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/CHANGELOG.md +32 -0
- package/bin/view-supervisor-child.mjs +0 -0
- package/blueprints/delivery-ci-workflows/README.md +4 -0
- package/blueprints/delivery-ci-workflows/assets/bootstrap/README.md +26 -0
- package/blueprints/delivery-ci-workflows/assets/bootstrap/adr-bootstrap-coverage-supersession.template.json +28 -0
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/default-branch-checks.yml +12 -6
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/pull-request-checks.yml +16 -7
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/release.yml +4 -0
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/scheduled-audit.yml +4 -0
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/notes.md +5 -5
- package/blueprints/delivery-ci-workflows/assets/report-samples/per-gate.json +1 -1
- package/blueprints/delivery-ci-workflows/blueprint.json +1 -1
- package/blueprints/delivery-ci-workflows/contributions/adrs/adr-702-delivery-ci-workflows-strict-coverage-gate.json +2 -2
- package/blueprints/delivery-ci-workflows/contributions/tacs/tac-701-delivery-ci-workflows-gate-runner.json +2 -2
- package/blueprints/delivery-ci-workflows/contributions/tacs/tac-704-delivery-ci-workflows-workflow-materialiser.json +7 -4
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6111.json +2 -2
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6114.json +6 -6
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6115.json +6 -6
- package/blueprints/delivery-ci-workflows/guide/delivery-ci-workflows.md +39 -1
- package/fixtures/canary-manifest.json +101 -1
- package/guidance/harness-template.md +9 -0
- package/guidance/managed/agent-instructions-block.hash +1 -1
- package/guidance/managed/agent-instructions-block.md +9 -0
- package/package.json +13 -15
- package/releases/releases.yaml +21 -1
- package/src/blueprint/apply.js +15 -6
- package/src/blueprint/index.js +22 -0
- package/src/blueprint/library-cache.js +143 -0
- package/src/blueprint/library-fetcher-git.js +347 -0
- package/src/blueprint/library-fetcher-tarball.js +379 -0
- package/src/blueprint/library-loader.js +21 -0
- package/src/blueprint/shelf-resolver.js +100 -7
- package/src/blueprint/supersede.js +56 -13
- package/src/cli/blueprint-library.js +427 -82
- package/src/cli/blueprint.js +6 -1
|
@@ -1,3 +1,103 @@
|
|
|
1
1
|
{
|
|
2
|
-
"registerCanary": [
|
|
2
|
+
"registerCanary": [
|
|
3
|
+
{
|
|
4
|
+
"id": "rc-2026-09-03-001",
|
|
5
|
+
"createdAt": "2026-09-03T13:16:14.200Z",
|
|
6
|
+
"buildVersion": "0.16.0-mockdriver",
|
|
7
|
+
"fixturePromptId": "canary-prompt-01",
|
|
8
|
+
"responseWordCount": 55,
|
|
9
|
+
"grades": {
|
|
10
|
+
"internalRuleCitation": {
|
|
11
|
+
"verdict": "pass",
|
|
12
|
+
"matches": []
|
|
13
|
+
},
|
|
14
|
+
"unglossedJargon": {
|
|
15
|
+
"verdict": "pass",
|
|
16
|
+
"matches": []
|
|
17
|
+
},
|
|
18
|
+
"redundantPermissionAsk": {
|
|
19
|
+
"verdict": "pass",
|
|
20
|
+
"matches": []
|
|
21
|
+
},
|
|
22
|
+
"bypassOffer": {
|
|
23
|
+
"verdict": "pass",
|
|
24
|
+
"matches": []
|
|
25
|
+
},
|
|
26
|
+
"wordCountBudget": {
|
|
27
|
+
"verdict": "pass",
|
|
28
|
+
"target": 200,
|
|
29
|
+
"actual": 55,
|
|
30
|
+
"matches": []
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"verdict": "fail",
|
|
34
|
+
"shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "rc-2026-09-03-002",
|
|
38
|
+
"createdAt": "2026-09-03T13:16:14.200Z",
|
|
39
|
+
"buildVersion": "0.16.0-mockdriver",
|
|
40
|
+
"fixturePromptId": "canary-prompt-02",
|
|
41
|
+
"responseWordCount": 55,
|
|
42
|
+
"grades": {
|
|
43
|
+
"internalRuleCitation": {
|
|
44
|
+
"verdict": "pass",
|
|
45
|
+
"matches": []
|
|
46
|
+
},
|
|
47
|
+
"unglossedJargon": {
|
|
48
|
+
"verdict": "pass",
|
|
49
|
+
"matches": []
|
|
50
|
+
},
|
|
51
|
+
"redundantPermissionAsk": {
|
|
52
|
+
"verdict": "pass",
|
|
53
|
+
"matches": []
|
|
54
|
+
},
|
|
55
|
+
"bypassOffer": {
|
|
56
|
+
"verdict": "pass",
|
|
57
|
+
"matches": []
|
|
58
|
+
},
|
|
59
|
+
"wordCountBudget": {
|
|
60
|
+
"verdict": "pass",
|
|
61
|
+
"target": 200,
|
|
62
|
+
"actual": 55,
|
|
63
|
+
"matches": []
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"verdict": "fail",
|
|
67
|
+
"shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "rc-2026-09-03-003",
|
|
71
|
+
"createdAt": "2026-09-03T13:16:14.201Z",
|
|
72
|
+
"buildVersion": "0.16.0-mockdriver",
|
|
73
|
+
"fixturePromptId": "canary-prompt-03",
|
|
74
|
+
"responseWordCount": 55,
|
|
75
|
+
"grades": {
|
|
76
|
+
"internalRuleCitation": {
|
|
77
|
+
"verdict": "pass",
|
|
78
|
+
"matches": []
|
|
79
|
+
},
|
|
80
|
+
"unglossedJargon": {
|
|
81
|
+
"verdict": "pass",
|
|
82
|
+
"matches": []
|
|
83
|
+
},
|
|
84
|
+
"redundantPermissionAsk": {
|
|
85
|
+
"verdict": "pass",
|
|
86
|
+
"matches": []
|
|
87
|
+
},
|
|
88
|
+
"bypassOffer": {
|
|
89
|
+
"verdict": "pass",
|
|
90
|
+
"matches": []
|
|
91
|
+
},
|
|
92
|
+
"wordCountBudget": {
|
|
93
|
+
"verdict": "pass",
|
|
94
|
+
"target": 200,
|
|
95
|
+
"actual": 55,
|
|
96
|
+
"matches": []
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"verdict": "fail",
|
|
100
|
+
"shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
|
|
101
|
+
}
|
|
102
|
+
]
|
|
3
103
|
}
|
|
@@ -56,6 +56,15 @@ non-technical; the method must be invisible in what they read.
|
|
|
56
56
|
- Tone: it is in hand. The operator steers; you drive. Confident
|
|
57
57
|
without hedging, and plainly honest when something is genuinely
|
|
58
58
|
blocked or ambiguous.
|
|
59
|
+
- Blueprints and libraries. When the operator asks for a starting
|
|
60
|
+
shape, list what is available (the packaged shelf and any libraries
|
|
61
|
+
registered on this project) in plain words and offer one that fits.
|
|
62
|
+
The operator chooses; you do not pick for them. Registering a
|
|
63
|
+
library is a trust decision the operator makes; when it lands,
|
|
64
|
+
relay it in a sentence ("added the WSD library to this project").
|
|
65
|
+
Library-qualified names like `wsd:auth-oauth2`, the ids each apply
|
|
66
|
+
stamps, and the exact CLI lines belong in files and in `rcf`
|
|
67
|
+
output, not in the conversation.
|
|
59
68
|
|
|
60
69
|
Before / after - the same first status after project setup:
|
|
61
70
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1db0aeb320810137f642e7dcdc7e8ffb4b43c9d050383ae3c9d10d10de0dd690
|
|
@@ -43,6 +43,15 @@ non-technical; the method must be invisible in what they read.
|
|
|
43
43
|
- Tone: it is in hand. The operator steers; you drive. Confident
|
|
44
44
|
without hedging, and plainly honest when something is genuinely
|
|
45
45
|
blocked or ambiguous.
|
|
46
|
+
- Blueprints and libraries. When the operator asks for a starting
|
|
47
|
+
shape, list what is available (the packaged shelf and any libraries
|
|
48
|
+
registered on this project) in plain words and offer one that fits.
|
|
49
|
+
The operator chooses; you do not pick for them. Registering a
|
|
50
|
+
library is a trust decision the operator makes; when it lands,
|
|
51
|
+
relay it in a sentence ("added the WSD library to this project").
|
|
52
|
+
Library-qualified names like `wsd:auth-oauth2`, the ids each apply
|
|
53
|
+
stamps, and the exact CLI lines belong in files and in `rcf`
|
|
54
|
+
output, not in the conversation.
|
|
46
55
|
|
|
47
56
|
Before / after - the same first status after project setup:
|
|
48
57
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rcf-lite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "One-install tooling for the Requirements Confidence Framework (RCF): the unified `rcf` CLI grouped into the five RCF tool groups (discover, define, build, verify, audit) plus a small core set (init, doctor, guidance, mcp), an MCP server, the live tree viewer and the fresh-context adversarial ship-gate verifier. Consumes @stravica-ai/rcf-schemas.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -39,18 +39,6 @@
|
|
|
39
39
|
"registry": "https://registry.npmjs.org/",
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
|
-
"scripts": {
|
|
43
|
-
"test": "node --test --test-concurrency=1 'test/**/*.test.js'",
|
|
44
|
-
"vendor": "node scripts/vendor-mermaid.mjs",
|
|
45
|
-
"build:managed": "node scripts/gen-managed-artefacts.mjs",
|
|
46
|
-
"canary:register": "node scripts/canary-register.mjs",
|
|
47
|
-
"validate:releases": "node scripts/validate-releases.mjs",
|
|
48
|
-
"stage:blueprints": "node scripts/stage-blueprint-shelf.mjs",
|
|
49
|
-
"preinstall": "node scripts/preinstall-node-check.mjs",
|
|
50
|
-
"prepack": "node scripts/stage-blueprint-shelf.mjs",
|
|
51
|
-
"prepublishOnly": "node scripts/gen-managed-artefacts.mjs && node scripts/stage-blueprint-shelf.mjs && node scripts/validate-releases.mjs",
|
|
52
|
-
"rcf": "node bin/rcf.js"
|
|
53
|
-
},
|
|
54
42
|
"repository": {
|
|
55
43
|
"type": "git",
|
|
56
44
|
"url": "git+https://github.com/Stravica/rcf-lite.git",
|
|
@@ -72,7 +60,7 @@
|
|
|
72
60
|
"#admissibility": "./src/admissibility/index.js"
|
|
73
61
|
},
|
|
74
62
|
"dependencies": {
|
|
75
|
-
"@stravica-ai/rcf-schemas": "0.5.
|
|
63
|
+
"@stravica-ai/rcf-schemas": "0.5.1",
|
|
76
64
|
"ajv": "^8.20.0",
|
|
77
65
|
"ajv-formats": "^3.0.1"
|
|
78
66
|
},
|
|
@@ -80,5 +68,15 @@
|
|
|
80
68
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
81
69
|
"js-yaml": "^4.1.0",
|
|
82
70
|
"mermaid": "11.6.0"
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"test": "node --test --test-concurrency=1 'test/**/*.test.js'",
|
|
74
|
+
"vendor": "node scripts/vendor-mermaid.mjs",
|
|
75
|
+
"build:managed": "node scripts/gen-managed-artefacts.mjs",
|
|
76
|
+
"canary:register": "node scripts/canary-register.mjs",
|
|
77
|
+
"validate:releases": "node scripts/validate-releases.mjs",
|
|
78
|
+
"stage:blueprints": "node scripts/stage-blueprint-shelf.mjs",
|
|
79
|
+
"preinstall": "node scripts/preinstall-node-check.mjs",
|
|
80
|
+
"rcf": "node bin/rcf.js"
|
|
83
81
|
}
|
|
84
|
-
}
|
|
82
|
+
}
|
package/releases/releases.yaml
CHANGED
|
@@ -40,8 +40,28 @@
|
|
|
40
40
|
# `npm install rcf-lite`.
|
|
41
41
|
|
|
42
42
|
feedVersion: 1
|
|
43
|
-
latest: "0.
|
|
43
|
+
latest: "0.16.0"
|
|
44
44
|
releases:
|
|
45
|
+
- version: "0.16.0"
|
|
46
|
+
date: "2026-09-03"
|
|
47
|
+
breaking: false
|
|
48
|
+
headlines:
|
|
49
|
+
- "External blueprint libraries now support git and tarball sources with an on-disk cache checked into the tree, plus a refresh command that re-resolves a library's pinned reference and refuses the update on drift."
|
|
50
|
+
- "Adding a blueprint from a local path inside an unregistered library now stamps the same slug and identity a registered add would, closing the author-and-test-locally loop."
|
|
51
|
+
- "The managed agent-instructions block now teaches the agent to offer blueprints and registered libraries in plain words so the operator picks; a new library authoring standard doc and worked-example fixture ship with the package. Rerun rcf init to refresh the block."
|
|
52
|
+
minAgentAction: "rerun-init"
|
|
53
|
+
notesUrl: "https://stravica.ai/docs/rcf/changelog/"
|
|
54
|
+
|
|
55
|
+
- version: "0.15.0"
|
|
56
|
+
date: "2026-08-31"
|
|
57
|
+
breaking: false
|
|
58
|
+
headlines:
|
|
59
|
+
- "Library-applied blueprints now carry a durable ownership stamp so re-registering a library under a different prefix no longer orphans records that came from it."
|
|
60
|
+
- "The review-on-add card gained a global-topics section and a prefix-check line so a library's cross-topic surface is visible before the operator commits."
|
|
61
|
+
- "The delivery-ci-workflows blueprint gained optional package-manager and branch-name shape fields, taught the coverage-strict bootstrap trap in the guide, and shipped a starting-point supersession ADR."
|
|
62
|
+
minAgentAction: null
|
|
63
|
+
notesUrl: "https://stravica.ai/docs/rcf/changelog/"
|
|
64
|
+
|
|
45
65
|
- version: "0.14.0"
|
|
46
66
|
date: "2026-08-31"
|
|
47
67
|
breaking: true
|
package/src/blueprint/apply.js
CHANGED
|
@@ -49,6 +49,13 @@ import { nextResolutionId } from './resolutions.js';
|
|
|
49
49
|
* record, so `rcf define blueprint remove wsd-auth-oauth2` reads back
|
|
50
50
|
* cleanly. `namespaceOverride` still wins over `effectiveSlug` if both
|
|
51
51
|
* are set (operator explicitly chose a different namespace).
|
|
52
|
+
* @param {string} [args.libraryPrefix] - the registered library prefix the
|
|
53
|
+
* blueprint was resolved through (spec §5.3, §7.3). When set the applied
|
|
54
|
+
* record carries a `libraryPrefix` field so `rcf library remove`'s
|
|
55
|
+
* ownership check reads the ownership fact off the record itself
|
|
56
|
+
* rather than string-matching `source`. Absent for shelf and path
|
|
57
|
+
* applies. Requires @stravica-ai/rcf-schemas 0.5.1 or later
|
|
58
|
+
* (`appliedBlueprintRecord.libraryPrefix`, additive optional).
|
|
52
59
|
* @param {{ ac: { start: number, end: number }, suffixBlocks?: Array<{ kind: string, start: number, end: number }> }} [args.libraryBands]
|
|
53
60
|
* Declared bands from the resolved library. When set, every stamped
|
|
54
61
|
* contribution is band-gated before write; a contribution whose numeric
|
|
@@ -72,7 +79,7 @@ import { nextResolutionId } from './resolutions.js';
|
|
|
72
79
|
* prove the rollback runs. Never used in production.
|
|
73
80
|
* @returns {Promise<ApplyResult | import('../core/errors/index.js').RcfError>}
|
|
74
81
|
*/
|
|
75
|
-
export async function applyBlueprint({ projectRoot, tree, source, displaySource, namespaceOverride, effectiveSlug, libraryBands, resolveDeclarations, now = new Date(), dryRun = false, _copyFileForTest }) {
|
|
82
|
+
export async function applyBlueprint({ projectRoot, tree, source, displaySource, namespaceOverride, effectiveSlug, libraryPrefix, libraryBands, resolveDeclarations, now = new Date(), dryRun = false, _copyFileForTest }) {
|
|
76
83
|
const hintSource = typeof displaySource === 'string' && displaySource.length > 0 ? displaySource : source;
|
|
77
84
|
const blueprint = await loadBlueprint(source);
|
|
78
85
|
if (blueprint.kind) return blueprint; // RcfError
|
|
@@ -256,11 +263,12 @@ export async function applyBlueprint({ projectRoot, tree, source, displaySource,
|
|
|
256
263
|
// carries the qualified typed ref for library-resolved blueprints
|
|
257
264
|
// (`wsd:auth-oauth2`) so `rcf define blueprint upgrade` reads back
|
|
258
265
|
// cleanly (spec §5.3); local-path applies carry the absolute path as
|
|
259
|
-
// today.
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
266
|
+
// today. When the apply resolved through a registered external
|
|
267
|
+
// library the record additionally carries `libraryPrefix`: the
|
|
268
|
+
// ownership fact for the library-registered ownership check in
|
|
269
|
+
// `rcf library remove`, so the registry may be edited (renamed,
|
|
270
|
+
// re-pointed, unregistered) without orphaning previously applied
|
|
271
|
+
// records. Shelf and path applies carry no `libraryPrefix`.
|
|
264
272
|
const recordSource = typeof displaySource === 'string' && displaySource.length > 0 && displaySource !== source
|
|
265
273
|
? displaySource
|
|
266
274
|
: source;
|
|
@@ -270,6 +278,7 @@ export async function applyBlueprint({ projectRoot, tree, source, displaySource,
|
|
|
270
278
|
appliedAt: now.toISOString(),
|
|
271
279
|
source: recordSource,
|
|
272
280
|
...(namespaceOverride ? { namespace: namespaceOverride } : {}),
|
|
281
|
+
...(typeof libraryPrefix === 'string' && libraryPrefix.length > 0 ? { libraryPrefix } : {}),
|
|
273
282
|
...(writtenContributions.length > 0 ? { contributions: writtenContributions } : {}),
|
|
274
283
|
};
|
|
275
284
|
const manifestResult = await updateManifest({
|
package/src/blueprint/index.js
CHANGED
|
@@ -23,3 +23,25 @@ export {
|
|
|
23
23
|
detectContributionsOutOfBand,
|
|
24
24
|
detectPrefixCollision,
|
|
25
25
|
} from './library-registry.js';
|
|
26
|
+
export {
|
|
27
|
+
CACHE_ROOT,
|
|
28
|
+
absoluteCachePath,
|
|
29
|
+
ensureEmptyCache,
|
|
30
|
+
relativeCachePath,
|
|
31
|
+
removeCache,
|
|
32
|
+
resolveCachePath,
|
|
33
|
+
sanitiseRef,
|
|
34
|
+
} from './library-cache.js';
|
|
35
|
+
export {
|
|
36
|
+
fetchGitLibrary,
|
|
37
|
+
parseGitRef,
|
|
38
|
+
resolveRemoteSha,
|
|
39
|
+
isFullSha,
|
|
40
|
+
refusedRefs,
|
|
41
|
+
} from './library-fetcher-git.js';
|
|
42
|
+
export {
|
|
43
|
+
createUstarBuffer,
|
|
44
|
+
fetchTarballLibrary,
|
|
45
|
+
parseUstar,
|
|
46
|
+
sha256Hex,
|
|
47
|
+
} from './library-fetcher-tarball.js';
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// External-library on-disk cache helpers (spec §4.4).
|
|
2
|
+
//
|
|
3
|
+
// Fetched library content sits under
|
|
4
|
+
// `rcf/.blueprint-libraries/<libraryPrefix>/<libraryRef>/`, checked
|
|
5
|
+
// into git as ordinary tree content so a fresh clone can `rcf define
|
|
6
|
+
// blueprint list` without a re-fetch. The cache is the working root
|
|
7
|
+
// the resolver reads through; the `cachePath` field on every registry
|
|
8
|
+
// entry points at it.
|
|
9
|
+
//
|
|
10
|
+
// This module owns path computation and a small set of directory
|
|
11
|
+
// primitives shared by the git and tarball fetchers (Phase 2c). It
|
|
12
|
+
// deliberately does NOT know about git or tar; the fetchers layer on
|
|
13
|
+
// top and land their extracted content at the paths computed here.
|
|
14
|
+
|
|
15
|
+
import { existsSync } from 'node:fs';
|
|
16
|
+
import { mkdir, rm, stat } from 'node:fs/promises';
|
|
17
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
18
|
+
|
|
19
|
+
import { rcfError } from '../core/errors/index.js';
|
|
20
|
+
|
|
21
|
+
export const CACHE_ROOT = 'rcf/.blueprint-libraries';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Repo-relative cache path for a library at a given ref. Repo-relative
|
|
25
|
+
* because that is what the registry stores (`entry.cachePath`); the
|
|
26
|
+
* resolver joins it against `projectRoot` at read time.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} libraryPrefix
|
|
29
|
+
* @param {string} libraryRef
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
export function relativeCachePath(libraryPrefix, libraryRef) {
|
|
33
|
+
return `${CACHE_ROOT}/${libraryPrefix}/${sanitiseRef(libraryRef)}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Absolute cache path for a library at a given ref, joined against a
|
|
38
|
+
* project root.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} projectRoot
|
|
41
|
+
* @param {string} libraryPrefix
|
|
42
|
+
* @param {string} libraryRef
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
45
|
+
export function absoluteCachePath(projectRoot, libraryPrefix, libraryRef) {
|
|
46
|
+
const rel = relativeCachePath(libraryPrefix, libraryRef);
|
|
47
|
+
return isAbsolute(rel) ? rel : join(projectRoot, rel);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Prepare an empty cache directory. Refuses when a non-empty cache
|
|
52
|
+
* already exists at the target (the caller must remove it first via
|
|
53
|
+
* `removeCache`, or fail fast and prompt the operator). This keeps
|
|
54
|
+
* fetches deterministic: content lands on a clean slate every time.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} absPath
|
|
57
|
+
* @param {object} [opts]
|
|
58
|
+
* @param {boolean} [opts.replace=false] - when true, remove any
|
|
59
|
+
* pre-existing content at absPath before creating the fresh dir.
|
|
60
|
+
* @returns {Promise<null | import('../core/errors/index.js').RcfError>}
|
|
61
|
+
*/
|
|
62
|
+
export async function ensureEmptyCache(absPath, opts = {}) {
|
|
63
|
+
try {
|
|
64
|
+
if (existsSync(absPath)) {
|
|
65
|
+
if (opts.replace === true) {
|
|
66
|
+
await rm(absPath, { recursive: true, force: true });
|
|
67
|
+
} else {
|
|
68
|
+
const s = await stat(absPath);
|
|
69
|
+
if (s.isDirectory()) {
|
|
70
|
+
return rcfError({
|
|
71
|
+
kind: 'usage',
|
|
72
|
+
message: `library cache: path already exists at ${absPath}. Remove it or run 'library refresh' to re-fetch.`,
|
|
73
|
+
filePath: absPath,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return rcfError({
|
|
77
|
+
kind: 'usage',
|
|
78
|
+
message: `library cache: non-directory blocks cache path ${absPath}.`,
|
|
79
|
+
filePath: absPath,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
await mkdir(absPath, { recursive: true });
|
|
84
|
+
return null;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return rcfError({
|
|
87
|
+
kind: 'ioFailure',
|
|
88
|
+
message: `library cache: could not prepare ${absPath}: ${err.message}`,
|
|
89
|
+
filePath: absPath,
|
|
90
|
+
stack: err.stack,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Recursive remove of a cache directory. No-op when the path does not
|
|
97
|
+
* exist. Used by `library remove` and by the fetchers' rollback path.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} absPath
|
|
100
|
+
* @returns {Promise<null | import('../core/errors/index.js').RcfError>}
|
|
101
|
+
*/
|
|
102
|
+
export async function removeCache(absPath) {
|
|
103
|
+
try {
|
|
104
|
+
await rm(absPath, { recursive: true, force: true });
|
|
105
|
+
return null;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
return rcfError({
|
|
108
|
+
kind: 'ioFailure',
|
|
109
|
+
message: `library cache: could not remove ${absPath}: ${err.message}`,
|
|
110
|
+
filePath: absPath,
|
|
111
|
+
stack: err.stack,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Resolve an absolute cache path from either an absolute
|
|
118
|
+
* `entry.cachePath` (legacy local-source entries in phase 2b stored
|
|
119
|
+
* the library root path here) or a repo-relative one (phase 2c network
|
|
120
|
+
* fetches store the relative form; the resolver joins with the project
|
|
121
|
+
* root at read time).
|
|
122
|
+
*
|
|
123
|
+
* @param {string} projectRoot
|
|
124
|
+
* @param {string} cachePath
|
|
125
|
+
* @returns {string}
|
|
126
|
+
*/
|
|
127
|
+
export function resolveCachePath(projectRoot, cachePath) {
|
|
128
|
+
return isAbsolute(cachePath) ? cachePath : resolve(projectRoot, cachePath);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Replace path-hostile characters in a libraryRef so it can be used as
|
|
133
|
+
* a directory segment. Refs are already semver-ish or short tag names
|
|
134
|
+
* in practice; this guards against a publisher who ships a ref like
|
|
135
|
+
* `1.2.0/rc1` or an operator who hand-types one. Slashes and colons are
|
|
136
|
+
* mapped to a single `-` so the on-disk layout stays flat.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} ref
|
|
139
|
+
* @returns {string}
|
|
140
|
+
*/
|
|
141
|
+
export function sanitiseRef(ref) {
|
|
142
|
+
return String(ref).replace(/[\\/:*?"<>|]+/g, '-');
|
|
143
|
+
}
|