rcf-lite 0.0.1 → 0.8.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 +344 -0
- package/LICENSE +202 -0
- package/README.md +92 -4
- package/bin/rcf-verify.js +122 -0
- package/bin/rcf.js +174 -0
- package/bin/view-supervisor-child.mjs +14 -0
- package/fixtures/canary-manifest.json +103 -0
- package/guidance/README.md +25 -0
- package/guidance/build-cycle-playbook.md +492 -0
- package/guidance/build-cycle.md +40 -0
- package/guidance/document-model.md +45 -0
- package/guidance/elicitation-playbook.md +442 -0
- package/guidance/harness-template.md +246 -0
- package/guidance/managed/README.md +63 -0
- package/guidance/managed/agent-instructions-block.hash +1 -0
- package/guidance/managed/agent-instructions-block.md +220 -0
- package/guidance/managed/legacy-fragment-hashes.json +22 -0
- package/guidance/manifest.json +21 -0
- package/guidance/overview.md +48 -0
- package/guidance/persona-programme.md +44 -0
- package/package.json +71 -8
- package/rcf/adrs/adr-001.json +25 -0
- package/rcf/adrs/adr-002.json +25 -0
- package/rcf/adrs/adr-003.json +25 -0
- package/rcf/adrs/adr-004.json +20 -0
- package/rcf/adrs/adr-005.json +20 -0
- package/rcf/adrs/adr-006.json +25 -0
- package/rcf/adrs/adr-007.json +25 -0
- package/rcf/adrs/adr-008.json +25 -0
- package/rcf/adrs/adr-009.json +25 -0
- package/rcf/build-sequence.json +11 -0
- package/rcf/code-nodes/cn-001.json +14 -0
- package/rcf/code-nodes/cn-002.json +14 -0
- package/rcf/code-nodes/cn-003.json +15 -0
- package/rcf/code-nodes/cn-004.json +17 -0
- package/rcf/code-nodes/cn-005.json +19 -0
- package/rcf/code-nodes/cn-006.json +17 -0
- package/rcf/code-nodes/cn-007.json +16 -0
- package/rcf/code-nodes/cn-008.json +19 -0
- package/rcf/code-nodes/cn-009.json +16 -0
- package/rcf/code-nodes/cn-010.json +18 -0
- package/rcf/code-nodes/cn-011.json +18 -0
- package/rcf/code-nodes/cn-012.json +18 -0
- package/rcf/code-nodes/cn-013.json +19 -0
- package/rcf/code-nodes/cn-014.json +16 -0
- package/rcf/code-nodes/cn-015.json +16 -0
- package/rcf/code-nodes/cn-016.json +16 -0
- package/rcf/code-nodes/cn-017.json +16 -0
- package/rcf/code-nodes/cn-018.json +15 -0
- package/rcf/code-nodes/cn-019.json +16 -0
- package/rcf/code-nodes/cn-020.json +16 -0
- package/rcf/code-nodes/cn-021.json +16 -0
- package/rcf/code-nodes/cn-022.json +16 -0
- package/rcf/code-nodes/cn-023.json +16 -0
- package/rcf/code-nodes/cn-024.json +16 -0
- package/rcf/code-nodes/cn-025.json +16 -0
- package/rcf/code-nodes/cn-026.json +16 -0
- package/rcf/code-nodes/cn-027.json +16 -0
- package/rcf/code-nodes/cn-028.json +16 -0
- package/rcf/code-nodes/cn-029.json +19 -0
- package/rcf/code-nodes/cn-030.json +14 -0
- package/rcf/code-nodes/cn-031.json +14 -0
- package/rcf/code-nodes/cn-032.json +14 -0
- package/rcf/code-nodes/cn-033.json +14 -0
- package/rcf/code-nodes/cn-034.json +14 -0
- package/rcf/code-nodes/cn-035.json +14 -0
- package/rcf/code-nodes/cn-036.json +14 -0
- package/rcf/code-nodes/cn-037.json +14 -0
- package/rcf/code-nodes/cn-038.json +14 -0
- package/rcf/code-nodes/cn-039.json +14 -0
- package/rcf/code-nodes/cn-040.json +14 -0
- package/rcf/code-nodes/cn-041.json +14 -0
- package/rcf/code-nodes/cn-042.json +14 -0
- package/rcf/code-nodes/cn-043.json +14 -0
- package/rcf/code-nodes/cn-044.json +14 -0
- package/rcf/code-nodes/cn-045.json +14 -0
- package/rcf/code-nodes/cn-046.json +14 -0
- package/rcf/code-nodes/cn-047.json +14 -0
- package/rcf/code-nodes/cn-048.json +14 -0
- package/rcf/code-nodes/cn-049.json +14 -0
- package/rcf/code-nodes/cn-050.json +14 -0
- package/rcf/code-nodes/cn-051.json +14 -0
- package/rcf/code-nodes/cn-052.json +14 -0
- package/rcf/code-nodes/cn-053.json +14 -0
- package/rcf/code-nodes/cn-054.json +14 -0
- package/rcf/code-nodes/cn-055.json +14 -0
- package/rcf/code-nodes/cn-056.json +14 -0
- package/rcf/code-nodes/cn-057.json +14 -0
- package/rcf/fbs/fbs-001.json +49 -0
- package/rcf/fbs/fbs-002.json +42 -0
- package/rcf/fbs/fbs-003.json +37 -0
- package/rcf/fbs/fbs-004.json +39 -0
- package/rcf/fbs/fbs-005.json +38 -0
- package/rcf/fbs/fbs-006.json +48 -0
- package/rcf/fbs/fbs-007.json +39 -0
- package/rcf/fbs/fbs-008.json +40 -0
- package/rcf/fbs/fbs-009.json +36 -0
- package/rcf/fbs/fbs-010.json +41 -0
- package/rcf/fbs/fbs-011.json +36 -0
- package/rcf/fbs/fbs-012.json +46 -0
- package/rcf/fbs/fbs-013.json +42 -0
- package/rcf/fbs/fbs-014.json +49 -0
- package/rcf/fbs/fbs-015.json +32 -0
- package/rcf/manifest.json +17 -0
- package/rcf/prd.json +47 -0
- package/rcf/requirements/req-001.json +19 -0
- package/rcf/requirements/req-002.json +19 -0
- package/rcf/requirements/req-003.json +19 -0
- package/rcf/requirements/req-004.json +19 -0
- package/rcf/requirements/req-005.json +19 -0
- package/rcf/requirements/req-006.json +19 -0
- package/rcf/requirements/req-007.json +19 -0
- package/rcf/requirements/req-008.json +19 -0
- package/rcf/requirements/req-009.json +19 -0
- package/rcf/tacs/tac-001.json +45 -0
- package/rcf/tacs/tac-002.json +109 -0
- package/rcf/tacs/tac-003.json +40 -0
- package/rcf/tacs/tac-004.json +51 -0
- package/rcf/tacs/tac-005.json +52 -0
- package/rcf/tacs/tac-006.json +104 -0
- package/rcf/tacs/tac-007.json +38 -0
- package/rcf/tacs/tac-008.json +51 -0
- package/rcf/tad.json +51 -0
- package/rcf/test-suites/PENDING.md +23 -0
- package/rcf/test-suites/ts-001.json +38 -0
- package/rcf/test-suites/ts-002.json +38 -0
- package/rcf/test-suites/ts-003.json +43 -0
- package/rcf/test-suites/ts-004.json +44 -0
- package/rcf/test-suites/ts-005.json +30 -0
- package/rcf/test-suites/ts-006.json +36 -0
- package/rcf/test-suites/ts-007.json +43 -0
- package/rcf/test-suites/ts-008.json +37 -0
- package/rcf/test-suites/ts-009.json +38 -0
- package/rcf/test-suites/ts-010.json +38 -0
- package/rcf/test-suites/ts-011.json +44 -0
- package/rcf/test-suites/ts-012.json +36 -0
- package/rcf/test-suites/ts-013.json +38 -0
- package/rcf/test-suites/ts-014.json +38 -0
- package/rcf/test-suites/ts-015.json +38 -0
- package/rcf/test-suites/ts-016.json +37 -0
- package/rcf/test-suites/ts-017.json +38 -0
- package/rcf/test-suites/ts-018.json +38 -0
- package/rcf/test-suites/ts-019.json +37 -0
- package/rcf/test-suites/ts-020.json +46 -0
- package/rcf/test-suites/ts-021.json +46 -0
- package/rcf/test-suites/ts-022.json +46 -0
- package/rcf/test-suites/ts-023.json +46 -0
- package/rcf/test-suites/ts-024.json +46 -0
- package/rcf/test-suites/ts-025.json +52 -0
- package/rcf/user-stories/us-101.json +40 -0
- package/rcf/user-stories/us-102.json +40 -0
- package/rcf/user-stories/us-201.json +40 -0
- package/rcf/user-stories/us-202.json +40 -0
- package/rcf/user-stories/us-203.json +32 -0
- package/rcf/user-stories/us-301.json +40 -0
- package/rcf/user-stories/us-302.json +40 -0
- package/rcf/user-stories/us-303.json +40 -0
- package/rcf/user-stories/us-304.json +40 -0
- package/rcf/user-stories/us-401.json +40 -0
- package/rcf/user-stories/us-402.json +40 -0
- package/rcf/user-stories/us-403.json +40 -0
- package/rcf/user-stories/us-501.json +40 -0
- package/rcf/user-stories/us-502.json +40 -0
- package/rcf/user-stories/us-503.json +40 -0
- package/rcf/user-stories/us-601.json +40 -0
- package/rcf/user-stories/us-602.json +40 -0
- package/rcf/user-stories/us-701.json +40 -0
- package/rcf/user-stories/us-702.json +40 -0
- package/rcf/user-stories/us-801.json +49 -0
- package/rcf/user-stories/us-802.json +49 -0
- package/rcf/user-stories/us-803.json +49 -0
- package/rcf/user-stories/us-804.json +49 -0
- package/rcf/user-stories/us-805.json +49 -0
- package/rcf/user-stories/us-901.json +40 -0
- package/src/.gitkeep +0 -0
- package/src/admissibility/enforce.js +142 -0
- package/src/admissibility/index.js +8 -0
- package/src/admissibility/markers.js +104 -0
- package/src/admissibility/scope-lint.js +163 -0
- package/src/browser-verify/auth-smoke.js +109 -0
- package/src/browser-verify/index.js +29 -0
- package/src/browser-verify/invariants.js +336 -0
- package/src/browser-verify/manifest-writer.js +189 -0
- package/src/browser-verify/runner.js +171 -0
- package/src/build/bundle.js +198 -0
- package/src/build/formatters/json.js +18 -0
- package/src/build/formatters/markdown.js +406 -0
- package/src/build/index.js +14 -0
- package/src/build/mark.js +177 -0
- package/src/build/queue.js +285 -0
- package/src/cli/browser-verify.js +231 -0
- package/src/cli/build.js +584 -0
- package/src/cli/coverage.js +219 -0
- package/src/cli/create.js +358 -0
- package/src/cli/delete.js +127 -0
- package/src/cli/design.js +353 -0
- package/src/cli/doctor.js +548 -0
- package/src/cli/fbs.js +171 -0
- package/src/cli/finalise.js +377 -0
- package/src/cli/guidance.js +206 -0
- package/src/cli/help.js +156 -0
- package/src/cli/impact.js +119 -0
- package/src/cli/init.js +282 -0
- package/src/cli/intake.js +153 -0
- package/src/cli/link.js +128 -0
- package/src/cli/mcp.js +160 -0
- package/src/cli/preflight.js +220 -0
- package/src/cli/read.js +168 -0
- package/src/cli/req-baseline.js +269 -0
- package/src/cli/req-classify.js +135 -0
- package/src/cli/review.js +295 -0
- package/src/cli/test-suite.js +221 -0
- package/src/cli/trace.js +172 -0
- package/src/cli/ui-baseline.js +292 -0
- package/src/cli/ui-classify.js +108 -0
- package/src/cli/update.js +197 -0
- package/src/cli/validate.js +168 -0
- package/src/cli/view.js +452 -0
- package/src/core/baseline-catalog/data/auth.json +42 -0
- package/src/core/baseline-catalog/data/http-api.json +42 -0
- package/src/core/baseline-catalog/data/notifications.json +33 -0
- package/src/core/baseline-catalog/data/persistence.json +33 -0
- package/src/core/baseline-catalog/data/web-ui.json +60 -0
- package/src/core/baseline-catalog/index.js +121 -0
- package/src/core/errors/index.js +167 -0
- package/src/core/fixtures/register-canary/canary-prompt-01.json +18 -0
- package/src/core/fixtures/register-canary/canary-prompt-02.json +21 -0
- package/src/core/fixtures/register-canary/canary-prompt-03.json +17 -0
- package/src/core/isolation/index.js +60 -0
- package/src/core/mcp/framing.js +103 -0
- package/src/core/mcp/index.js +8 -0
- package/src/core/mcp/server.js +228 -0
- package/src/core/patterns/register-canary.js +209 -0
- package/src/core/patterns/req-shapes.js +158 -0
- package/src/core/patterns/services.js +358 -0
- package/src/core/patterns/ui-shapes.js +166 -0
- package/src/core/store/cn-resolve.js +134 -0
- package/src/core/store/derive-deps.js +93 -0
- package/src/core/store/ids.js +78 -0
- package/src/core/store/index.js +20 -0
- package/src/core/store/init.js +255 -0
- package/src/core/store/loader.js +214 -0
- package/src/core/store/tp-resolve.js +176 -0
- package/src/core/store/validator.js +191 -0
- package/src/core/store/walker.js +944 -0
- package/src/core/store/writer.js +1879 -0
- package/src/design/index.js +11 -0
- package/src/design/writer.js +271 -0
- package/src/finalise/detect.js +151 -0
- package/src/finalise/index.js +31 -0
- package/src/finalise/ingest.js +160 -0
- package/src/finalise/install.js +119 -0
- package/src/finalise/ship-without-verified.js +131 -0
- package/src/finalise/spawn.js +84 -0
- package/src/intake/fidelity.js +105 -0
- package/src/intake/index.js +6 -0
- package/src/intake/manifest-writer.js +100 -0
- package/src/intake/orchestrator.js +138 -0
- package/src/intake/validate.js +80 -0
- package/src/mcp/map-errors.js +131 -0
- package/src/mcp/prompts.js +49 -0
- package/src/mcp/resources.js +244 -0
- package/src/mcp/tools.js +1212 -0
- package/src/preflight/design-shapes.js +185 -0
- package/src/preflight/index.js +90 -0
- package/src/preflight/manifest-writer.js +264 -0
- package/src/preflight/scanner.js +206 -0
- package/src/preflight/secrets.js +134 -0
- package/src/preflight/session.js +246 -0
- package/src/query/attestation.js +285 -0
- package/src/query/coverage.js +308 -0
- package/src/query/formatters/json.js +21 -0
- package/src/query/formatters/mermaid.js +209 -0
- package/src/query/formatters/table.js +203 -0
- package/src/query/impact.js +173 -0
- package/src/query/index.js +13 -0
- package/src/query/refuse-on-admissibility.js +73 -0
- package/src/query/trace.js +345 -0
- package/src/register-canary/fixture-loader.js +87 -0
- package/src/register-canary/index.js +10 -0
- package/src/register-canary/record-writer.js +132 -0
- package/src/register-canary/runner.js +156 -0
- package/src/req-baseline/gate.js +86 -0
- package/src/req-baseline/index.js +27 -0
- package/src/req-baseline/open-candidates.js +143 -0
- package/src/req-baseline/opt-out.js +195 -0
- package/src/req-baseline/sweep.js +230 -0
- package/src/req-detection/classifier.js +181 -0
- package/src/req-detection/index.js +9 -0
- package/src/req-detection/persist.js +55 -0
- package/src/review/index.js +325 -0
- package/src/review/mutation.js +117 -0
- package/src/review/ui-baseline-drift.js +138 -0
- package/src/ruleset/index.js +140 -0
- package/src/ruleset/ruleset.json +146 -0
- package/src/server/index.js +178 -0
- package/src/server/routes.js +110 -0
- package/src/server/sse.js +118 -0
- package/src/setup/agent-setup.js +362 -0
- package/src/setup/identity-seed.js +104 -0
- package/src/setup/knowledge-seed.js +123 -0
- package/src/setup/managed-block.js +193 -0
- package/src/setup/managed-gitignore.js +166 -0
- package/src/setup/managed-markers.js +49 -0
- package/src/ui-baseline/defaults.js +119 -0
- package/src/ui-baseline/index.js +25 -0
- package/src/ui-baseline/manifest-writer.js +282 -0
- package/src/ui-baseline/session.js +178 -0
- package/src/ui-detection/classifier.js +192 -0
- package/src/verify/chain/index.js +221 -0
- package/src/verify/cli/cleanup.js +61 -0
- package/src/verify/cli/help.js +56 -0
- package/src/verify/cli/mcp.js +98 -0
- package/src/verify/cli/provision.js +71 -0
- package/src/verify/cli/report.js +71 -0
- package/src/verify/cli/run.js +155 -0
- package/src/verify/engine/brief.js +87 -0
- package/src/verify/engine/index.js +177 -0
- package/src/verify/engine/launcher.js +307 -0
- package/src/verify/mcp/tools.js +107 -0
- package/src/verify/profile/index.js +146 -0
- package/src/verify/provision/index.js +256 -0
- package/src/verify/report/index.js +139 -0
- package/src/verify/report/renderer.js +118 -0
- package/src/verify/verdict/index.js +313 -0
- package/src/view/doc-renderers/adr.js +44 -0
- package/src/view/doc-renderers/build-sequence.js +40 -0
- package/src/view/doc-renderers/fbs.js +128 -0
- package/src/view/doc-renderers/helpers.js +159 -0
- package/src/view/doc-renderers/index.js +12 -0
- package/src/view/doc-renderers/prd.js +45 -0
- package/src/view/doc-renderers/req.js +43 -0
- package/src/view/doc-renderers/tac.js +38 -0
- package/src/view/doc-renderers/tad.js +74 -0
- package/src/view/doc-renderers/test-suite.js +45 -0
- package/src/view/doc-renderers/user-story.js +63 -0
- package/src/view/html-page.js +462 -0
- package/src/view/index.js +63 -0
- package/src/view/live-client.js +338 -0
- package/src/view/mermaid-diagram.js +178 -0
- package/src/view/style.css +735 -0
- package/src/view/tree-model.js +152 -0
- package/src/view/vendored/mermaid.min.js +2607 -0
- package/src/view-supervisor/index.js +26 -0
- package/src/view-supervisor/logs.js +32 -0
- package/src/view-supervisor/manifest-writer.js +178 -0
- package/src/view-supervisor/persist-until.js +85 -0
- package/src/view-supervisor/supervisor.js +276 -0
- package/src/watch/index.js +152 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// `rcf delete <id>` subcommand handler. Refuse-by-default when the doc
|
|
2
|
+
// has dependents; --cascade opts in. Orphan-refuse pre-plan check on
|
|
3
|
+
// REQ / US / AC cascade paths (Phase 4 §D9 amendment). Phase 4 §D9.
|
|
4
|
+
|
|
5
|
+
import { parseArgs } from 'node:util';
|
|
6
|
+
|
|
7
|
+
import { isRcfError, writeUnexpectedFailure } from '#core/errors';
|
|
8
|
+
import { deleteDocument, walkTree } from '#core/store';
|
|
9
|
+
import { findProjectRoot } from '../view/index.js';
|
|
10
|
+
|
|
11
|
+
const OPTION_SPEC = {
|
|
12
|
+
cascade: { type: 'boolean' },
|
|
13
|
+
'dry-run': { type: 'boolean' },
|
|
14
|
+
quiet: { type: 'boolean' },
|
|
15
|
+
help: { type: 'boolean' },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const HELP = `Usage: rcf delete <id> [options]
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--cascade Also delete dependents and drop backrefs
|
|
22
|
+
(dependents discovered via computed maps)
|
|
23
|
+
--dry-run Print the plan without executing
|
|
24
|
+
--quiet Suppress non-error stdout
|
|
25
|
+
--help Print this help
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string[]} argv - argv slice after `delete`
|
|
30
|
+
* @param {object} [deps]
|
|
31
|
+
* @returns {Promise<number>}
|
|
32
|
+
*/
|
|
33
|
+
export async function main(argv, deps = {}) {
|
|
34
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
35
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
36
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
37
|
+
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: true, strict: true });
|
|
41
|
+
} catch (err) {
|
|
42
|
+
stderr.write(`[error] usage ${err.message}\n`);
|
|
43
|
+
stderr.write(HELP);
|
|
44
|
+
return 2;
|
|
45
|
+
}
|
|
46
|
+
const flags = parsed.values;
|
|
47
|
+
const positionals = parsed.positionals;
|
|
48
|
+
if (flags.help) { stdout.write(HELP); return 0; }
|
|
49
|
+
if (positionals.length !== 1) {
|
|
50
|
+
stderr.write('[error] usage delete: expected exactly one <id>\n');
|
|
51
|
+
stderr.write(HELP);
|
|
52
|
+
return 2;
|
|
53
|
+
}
|
|
54
|
+
const id = positionals[0];
|
|
55
|
+
|
|
56
|
+
const projectRoot = await findProjectRoot(cwd);
|
|
57
|
+
if (!projectRoot) {
|
|
58
|
+
stderr.write('[error] usage no project root found (no rcf/manifest.json in this directory or any ancestor). Run `npx rcf init` to create and wire a project.\n');
|
|
59
|
+
return 2;
|
|
60
|
+
}
|
|
61
|
+
const walkResult = await walkTree({ projectRoot });
|
|
62
|
+
// B5 (amends the §D9 broken-tree-before-dependents precedence): a
|
|
63
|
+
// broken tree no longer blocks delete - deleting the offending doc is
|
|
64
|
+
// the canonical repair. The writer gates on the POST-write tree state;
|
|
65
|
+
// only net-new breakage refuses.
|
|
66
|
+
if (walkResult.errors.length > 0) {
|
|
67
|
+
stderr.write(`[warn] tree has ${walkResult.errors.length} pre-existing issue(s); proceeding - writes are validated against the post-write state (run 'rcf validate' for details)\n`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const result = await deleteDocument({
|
|
71
|
+
projectRoot, tree: walkResult.tree, id,
|
|
72
|
+
options: {
|
|
73
|
+
cascade: Boolean(flags.cascade),
|
|
74
|
+
dryRun: Boolean(flags['dry-run']),
|
|
75
|
+
},
|
|
76
|
+
walkErrors: walkResult.errors,
|
|
77
|
+
});
|
|
78
|
+
if (isRcfError(result)) return handleDeleteError(result, stderr);
|
|
79
|
+
// BUG-005 fix: split the header text between dry-run (future tense,
|
|
80
|
+
// "Would delete …") and executed (past tense, "Deleted …"). Previously
|
|
81
|
+
// both paths shared the future-tense header, making an executed delete
|
|
82
|
+
// visually indistinguishable from a plan at the summary line.
|
|
83
|
+
const dryRun = Boolean(flags['dry-run']);
|
|
84
|
+
const label = dryRun ? '[dry-run] would ' : '';
|
|
85
|
+
if (!flags.quiet) {
|
|
86
|
+
if (dryRun) {
|
|
87
|
+
stdout.write(
|
|
88
|
+
`Would delete ${result.deleted.length} file(s) and mutate ${result.mutated.length} doc(s). (dry-run)\n`,
|
|
89
|
+
);
|
|
90
|
+
} else {
|
|
91
|
+
stdout.write(
|
|
92
|
+
`Deleted ${result.deleted.length} file(s), mutated ${result.mutated.length} doc(s).\n`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
for (const line of result.plan) stdout.write(` ${label}${line}\n`);
|
|
96
|
+
}
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* `rcfError.kind === 'usage'` from writer.deleteDocument overloads two
|
|
102
|
+
* exit codes: the plain "unknown id / already-clean" cases stay exit 2,
|
|
103
|
+
* while a `rule` of `dependents` / `wouldOrphan` maps to exit 4.
|
|
104
|
+
*
|
|
105
|
+
* BUG-007 fix: `ioFailure` emits the spec §D15 `[rcf] unexpected failure`
|
|
106
|
+
* block (message + stack), not the structured `[error] ioFailure` line.
|
|
107
|
+
*
|
|
108
|
+
* BUG-008 fix: exit-4 refusals are labelled `[error] refused …` so the
|
|
109
|
+
* prefix matches the exit-code semantics, instead of the misleading
|
|
110
|
+
* `[error] usage …` prefix used when the underlying `rcfError.kind`
|
|
111
|
+
* happens to be `usage` with a `dependents` / `wouldOrphan` rule.
|
|
112
|
+
*/
|
|
113
|
+
function handleDeleteError(err, stderr) {
|
|
114
|
+
const kind = err.kind;
|
|
115
|
+
if (kind === 'ioFailure') {
|
|
116
|
+
writeUnexpectedFailure(err, stderr);
|
|
117
|
+
return 1;
|
|
118
|
+
}
|
|
119
|
+
if (kind === 'usage' && (err.rule === 'dependents' || err.rule === 'wouldOrphan')) {
|
|
120
|
+
stderr.write(`[error] refused ${err.message}\n`);
|
|
121
|
+
return 4;
|
|
122
|
+
}
|
|
123
|
+
stderr.write(`[error] ${kind} ${err.message}\n`);
|
|
124
|
+
if (kind === 'usage') return 2;
|
|
125
|
+
if (kind === 'validation' || kind === 'brokenReference') return 3;
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
// `rcf design <fbs-id> [<sub-verb> ...]` handler
|
|
2
|
+
// (ui-design-gate-0.7.0-spec §5.5).
|
|
3
|
+
//
|
|
4
|
+
// Verb shapes:
|
|
5
|
+
// rcf design <fbs-id> Dispatch the Design worker (no-op stub in v1; injection point).
|
|
6
|
+
// rcf design <fbs-id> journeys add ... Append a journey.
|
|
7
|
+
// rcf design <fbs-id> nav set ... Overwrite navModel.
|
|
8
|
+
// rcf design <fbs-id> theme-a11y set ... Overwrite themeAndA11y.
|
|
9
|
+
// rcf design <fbs-id> --mark-complete Set designStageComplete: true.
|
|
10
|
+
//
|
|
11
|
+
// Sub-verb positional-grammar constraint (spec §5.5): FBS ids match
|
|
12
|
+
// `/^FBS-\d+$/`, so no operator-typed slug can collide with a sub-verb
|
|
13
|
+
// noun. The parser enforces the pattern on the FBS positional: any
|
|
14
|
+
// positional in slot 0 that does not match /^FBS-\d+$/ is a usage
|
|
15
|
+
// error, not an FBS lookup miss. This closes the door on future
|
|
16
|
+
// refactors that accept slugs whose namespace could overlap with the
|
|
17
|
+
// sub-verb set.
|
|
18
|
+
//
|
|
19
|
+
// Design-worker dispatch: v1 does not spawn a subagent in-process (the
|
|
20
|
+
// dispatch pattern is deferred to the CLI harness or the operator);
|
|
21
|
+
// `rcf design <fbs-id>` with no sub-verb prints the current state and
|
|
22
|
+
// the operator's next-move options. This matches the Track A pattern
|
|
23
|
+
// where `rcf review` runs a deterministic audit and dispatches the
|
|
24
|
+
// mutation runner only via an injected dependency; wiring the Design
|
|
25
|
+
// worker's live dispatcher would enlarge scope beyond spec §5.3.
|
|
26
|
+
|
|
27
|
+
import { parseArgs } from 'node:util';
|
|
28
|
+
import process from 'node:process';
|
|
29
|
+
|
|
30
|
+
import { walkTree } from '#core/store';
|
|
31
|
+
import { writeUnexpectedFailure, rcfError } from '#core/errors';
|
|
32
|
+
|
|
33
|
+
import { findProjectRoot } from '../view/index.js';
|
|
34
|
+
import {
|
|
35
|
+
missingDesignStageArtefacts,
|
|
36
|
+
writeJourneyAdd,
|
|
37
|
+
writeMarkComplete,
|
|
38
|
+
writeNavSet,
|
|
39
|
+
writeThemeA11ySet,
|
|
40
|
+
} from '../design/index.js';
|
|
41
|
+
|
|
42
|
+
const KNOWN_SUB_VERBS = new Set(['journeys', 'nav', 'theme-a11y']);
|
|
43
|
+
const FBS_ID_PATTERN = /^FBS-\d+$/;
|
|
44
|
+
|
|
45
|
+
const OPTION_SPEC = {
|
|
46
|
+
// journeys add
|
|
47
|
+
id: { type: 'string' },
|
|
48
|
+
actor: { type: 'string' },
|
|
49
|
+
goal: { type: 'string' },
|
|
50
|
+
step: { type: 'string', multiple: true },
|
|
51
|
+
// nav set
|
|
52
|
+
shape: { type: 'string' },
|
|
53
|
+
route: { type: 'string', multiple: true },
|
|
54
|
+
'signed-in-as-affordance': { type: 'string' },
|
|
55
|
+
notes: { type: 'string' },
|
|
56
|
+
// theme-a11y set
|
|
57
|
+
mode: { type: 'string' },
|
|
58
|
+
tokens: { type: 'string' },
|
|
59
|
+
'contrast-targets': { type: 'string' },
|
|
60
|
+
'contrast-test': { type: 'string' },
|
|
61
|
+
'contrast-before-palette': { type: 'string' },
|
|
62
|
+
// mark-complete
|
|
63
|
+
'mark-complete': { type: 'boolean' },
|
|
64
|
+
// shared
|
|
65
|
+
'dry-run': { type: 'boolean' },
|
|
66
|
+
json: { type: 'boolean' },
|
|
67
|
+
quiet: { type: 'boolean' },
|
|
68
|
+
help: { type: 'boolean' },
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const HELP = `Usage: rcf design <fbs-id> [<sub-verb> ...] [options]
|
|
72
|
+
|
|
73
|
+
Design substage verbs for a UI-bearing FBS. FBS positional is required
|
|
74
|
+
and must match /^FBS-\\d+$/ (any other positional in that slot is a
|
|
75
|
+
usage error, not an FBS lookup miss).
|
|
76
|
+
|
|
77
|
+
Verb shapes:
|
|
78
|
+
rcf design <fbs-id> Print the current design
|
|
79
|
+
state and next-move options.
|
|
80
|
+
rcf design <fbs-id> journeys add ... Append a journey.
|
|
81
|
+
rcf design <fbs-id> nav set ... Overwrite navModel.
|
|
82
|
+
rcf design <fbs-id> theme-a11y set ... Overwrite themeAndA11y.
|
|
83
|
+
rcf design <fbs-id> --mark-complete Set designStageComplete: true.
|
|
84
|
+
|
|
85
|
+
journeys add options:
|
|
86
|
+
--id <slug> Lowercase slug (a-z0-9-), unique within the FBS.
|
|
87
|
+
--actor "..." Short actor label.
|
|
88
|
+
--goal "..." One-line goal statement.
|
|
89
|
+
--step "..." (2-8 times) Walk-through steps.
|
|
90
|
+
|
|
91
|
+
nav set options:
|
|
92
|
+
--shape <shape> shared-persistent | shared-per-section | none-single-page | operator-declared-other
|
|
93
|
+
--route <path=label:auth> One entry per route (auth is true|false); repeatable.
|
|
94
|
+
--signed-in-as-affordance true|false Whether authenticated routes show signed-in-as.
|
|
95
|
+
--notes "..." Free text; required in practice for operator-declared-other.
|
|
96
|
+
|
|
97
|
+
theme-a11y set options:
|
|
98
|
+
--mode <themeMode> light-default-with-toggle | dark-default-with-toggle | single-theme-declared
|
|
99
|
+
--tokens <path> Project-relative path to the design tokens module.
|
|
100
|
+
--contrast-targets "..." Short target statement (default: WCAG AA).
|
|
101
|
+
--contrast-test <path> Project-relative path to the contrast test.
|
|
102
|
+
--contrast-before-palette true|false Operator attestation on ordering.
|
|
103
|
+
|
|
104
|
+
--mark-complete options:
|
|
105
|
+
--dry-run Print the intended change; do not write.
|
|
106
|
+
|
|
107
|
+
Common options:
|
|
108
|
+
--json Emit the FBS's designStage block as JSON on success.
|
|
109
|
+
--quiet Suppress non-error confirmations.
|
|
110
|
+
--help Print this help.
|
|
111
|
+
|
|
112
|
+
Refusal shapes (spec §6.2):
|
|
113
|
+
* FBS.uiBearing is true but no uiBaseline record on the manifest.
|
|
114
|
+
* designStage baseline-vs-designStage disagreement without an opt-out.
|
|
115
|
+
* --mark-complete when any of the three artefacts is missing or empty.
|
|
116
|
+
|
|
117
|
+
Exit codes:
|
|
118
|
+
0 success
|
|
119
|
+
1 IO / unexpected runtime failure
|
|
120
|
+
2 usage error
|
|
121
|
+
3 schema or tree validation failure
|
|
122
|
+
4 refused (mark-complete or baseline disagreement)
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {string[]} argv
|
|
127
|
+
* @param {object} [deps]
|
|
128
|
+
* @returns {Promise<number>}
|
|
129
|
+
*/
|
|
130
|
+
export async function main(argv, deps = {}) {
|
|
131
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
132
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
133
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
134
|
+
|
|
135
|
+
if (argv.length === 0) {
|
|
136
|
+
stderr.write('[error] usage design: expected an <fbs-id> positional (e.g. FBS-016)\n');
|
|
137
|
+
return 2;
|
|
138
|
+
}
|
|
139
|
+
if (argv[0] === '--help' || argv[0] === '-h') { stdout.write(HELP); return 0; }
|
|
140
|
+
|
|
141
|
+
// Sub-verb positional grammar (§5.5): slot 0 MUST be an FBS id or
|
|
142
|
+
// an error. Sub-verb collision is guarded by the FBS-id pattern
|
|
143
|
+
// never matching a lowercase sub-verb noun.
|
|
144
|
+
const fbsId = argv[0];
|
|
145
|
+
if (!FBS_ID_PATTERN.test(fbsId)) {
|
|
146
|
+
stderr.write(`[error] usage design: expected an FBS id like FBS-011 as the first positional, got '${fbsId}' (sub-verbs like ${[...KNOWN_SUB_VERBS].join(' | ')} follow the FBS id).\n`);
|
|
147
|
+
return 2;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Parse remainder. `parseArgs` handles the sub-verb + sub-sub-verb
|
|
151
|
+
// positionals uniformly.
|
|
152
|
+
let parsed;
|
|
153
|
+
try {
|
|
154
|
+
parsed = parseArgs({ args: argv.slice(1), options: OPTION_SPEC, allowPositionals: true, strict: true });
|
|
155
|
+
} catch (err) {
|
|
156
|
+
stderr.write(`[error] usage ${err.message}\n`);
|
|
157
|
+
stderr.write(HELP);
|
|
158
|
+
return 2;
|
|
159
|
+
}
|
|
160
|
+
const flags = parsed.values;
|
|
161
|
+
if (flags.help) { stdout.write(HELP); return 0; }
|
|
162
|
+
const subs = parsed.positionals;
|
|
163
|
+
|
|
164
|
+
const projectRoot = await findProjectRoot(cwd);
|
|
165
|
+
if (!projectRoot) {
|
|
166
|
+
stderr.write('[error] usage no project root found (no rcf/manifest.json in this directory or any ancestor). Run `rcf init` first.\n');
|
|
167
|
+
return 2;
|
|
168
|
+
}
|
|
169
|
+
const walkResult = await walkTree({ projectRoot });
|
|
170
|
+
if (walkResult.errors.length > 0) {
|
|
171
|
+
stderr.write(`[warn] tree has ${walkResult.errors.length} pre-existing issue(s); proceeding\n`);
|
|
172
|
+
}
|
|
173
|
+
const { tree } = walkResult;
|
|
174
|
+
const fbs = tree.byId.get(fbsId);
|
|
175
|
+
if (!fbs || tree.kindById.get(fbsId) !== 'fbs') {
|
|
176
|
+
stderr.write(`[error] usage design: ${fbsId} not found or not an FBS\n`);
|
|
177
|
+
return 2;
|
|
178
|
+
}
|
|
179
|
+
if (fbs.uiBearing !== true) {
|
|
180
|
+
stderr.write(`[warn] design: ${fbsId} is not uiBearing (fbs.uiBearing is ${JSON.stringify(fbs.uiBearing)}); the Design substage is a no-op for non-UI FBS. Set uiBearing=true first (see 'rcf ui-classify ${fbsId}').\n`);
|
|
181
|
+
}
|
|
182
|
+
// Refuse Design substage writes when the baseline is missing (§6.2).
|
|
183
|
+
if (fbs.uiBearing === true && !tree.manifest?.uiBaseline && subs.length > 0) {
|
|
184
|
+
stderr.write(`[error] refused: ${fbsId} is uiBearing but no uiBaseline record exists on the manifest. Run: rcf ui-baseline init\n`);
|
|
185
|
+
return 4;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const now = deps.now ? new Date(deps.now()) : new Date();
|
|
189
|
+
|
|
190
|
+
if (flags['mark-complete']) {
|
|
191
|
+
return await runMarkComplete({ tree, projectRoot, fbs, fbsId, stdout, stderr, flags, now });
|
|
192
|
+
}
|
|
193
|
+
if (subs.length === 0) return runShow({ tree, fbs, fbsId, stdout, flags });
|
|
194
|
+
|
|
195
|
+
const first = subs[0];
|
|
196
|
+
const second = subs[1];
|
|
197
|
+
if (!KNOWN_SUB_VERBS.has(first)) {
|
|
198
|
+
stderr.write(`[error] usage design: unknown sub-verb '${first}' (expected ${[...KNOWN_SUB_VERBS].join(' | ')}, or --mark-complete)\n`);
|
|
199
|
+
return 2;
|
|
200
|
+
}
|
|
201
|
+
if (first === 'journeys' && second === 'add') {
|
|
202
|
+
return await runJourneysAdd({ tree, projectRoot, fbsId, stdout, stderr, flags, now });
|
|
203
|
+
}
|
|
204
|
+
if (first === 'nav' && second === 'set') {
|
|
205
|
+
return await runNavSet({ tree, projectRoot, fbsId, stdout, stderr, flags, now });
|
|
206
|
+
}
|
|
207
|
+
if (first === 'theme-a11y' && second === 'set') {
|
|
208
|
+
return await runThemeA11ySet({ tree, projectRoot, fbsId, stdout, stderr, flags, now });
|
|
209
|
+
}
|
|
210
|
+
stderr.write(`[error] usage design: unknown sub-verb combination '${[first, second].filter(Boolean).join(' ')}'\n`);
|
|
211
|
+
return 2;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function runShow({ tree, fbs, fbsId, stdout, flags }) {
|
|
215
|
+
const stage = fbs.designStage ?? null;
|
|
216
|
+
if (flags.json) {
|
|
217
|
+
stdout.write(`${JSON.stringify(stage, null, 2)}\n`);
|
|
218
|
+
return 0;
|
|
219
|
+
}
|
|
220
|
+
stdout.write(`design ${fbsId}: uiBearing=${JSON.stringify(fbs.uiBearing)} designStageComplete=${JSON.stringify(fbs.designStageComplete)}\n`);
|
|
221
|
+
const missing = missingDesignStageArtefacts(fbs);
|
|
222
|
+
if (!stage) {
|
|
223
|
+
stdout.write(' designStage: (none)\n');
|
|
224
|
+
} else {
|
|
225
|
+
stdout.write(` journeys: ${(stage.journeys ?? []).length}\n`);
|
|
226
|
+
stdout.write(` navModel: ${stage.navModel ? stage.navModel.shape + ' (' + (stage.navModel.routes ?? []).length + ' routes)' : '(none)'}\n`);
|
|
227
|
+
stdout.write(` themeAndA11y: ${stage.themeAndA11y ? stage.themeAndA11y.themeMode : '(none)'}\n`);
|
|
228
|
+
}
|
|
229
|
+
if (missing.length > 0) {
|
|
230
|
+
stdout.write(` next: author the missing artefact(s): ${missing.join(', ')}\n`);
|
|
231
|
+
} else if (fbs.designStageComplete !== true) {
|
|
232
|
+
stdout.write(' next: rcf design ' + fbsId + ' --mark-complete\n');
|
|
233
|
+
}
|
|
234
|
+
const baseline = tree.manifest?.uiBaseline;
|
|
235
|
+
if (fbs.uiBearing === true && !baseline) {
|
|
236
|
+
stdout.write(' refused (once you begin authoring): rcf ui-baseline init must run first.\n');
|
|
237
|
+
}
|
|
238
|
+
return 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function runJourneysAdd({ tree, projectRoot, fbsId, stdout, stderr, flags, now }) {
|
|
242
|
+
const journey = {
|
|
243
|
+
id: flags.id,
|
|
244
|
+
actor: flags.actor,
|
|
245
|
+
goal: flags.goal,
|
|
246
|
+
steps: Array.isArray(flags.step) ? flags.step : [],
|
|
247
|
+
};
|
|
248
|
+
const result = await writeJourneyAdd({ projectRoot, tree, fbsId, journey, now });
|
|
249
|
+
return handleWriteResult({ result, stdout, stderr, quiet: Boolean(flags.quiet), verb: 'journeys add', ok: `journey '${journey.id}' added to ${fbsId}` });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function runNavSet({ tree, projectRoot, fbsId, stdout, stderr, flags, now }) {
|
|
253
|
+
const routes = parseRouteEntries(flags.route ?? []);
|
|
254
|
+
if ('error' in routes) {
|
|
255
|
+
stderr.write(`[error] usage design nav set: ${routes.error}\n`);
|
|
256
|
+
return 2;
|
|
257
|
+
}
|
|
258
|
+
let signedInAsAffordance;
|
|
259
|
+
if (typeof flags['signed-in-as-affordance'] === 'string') {
|
|
260
|
+
if (flags['signed-in-as-affordance'] === 'true') signedInAsAffordance = true;
|
|
261
|
+
else if (flags['signed-in-as-affordance'] === 'false') signedInAsAffordance = false;
|
|
262
|
+
else {
|
|
263
|
+
stderr.write(`[error] usage design nav set: --signed-in-as-affordance must be true or false, got '${flags['signed-in-as-affordance']}'\n`);
|
|
264
|
+
return 2;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const result = await writeNavSet({
|
|
268
|
+
projectRoot, tree, fbsId,
|
|
269
|
+
shape: flags.shape,
|
|
270
|
+
routes: routes.routes,
|
|
271
|
+
signedInAsAffordance,
|
|
272
|
+
notes: flags.notes,
|
|
273
|
+
now,
|
|
274
|
+
});
|
|
275
|
+
return handleWriteResult({ result, stdout, stderr, quiet: Boolean(flags.quiet), verb: 'nav set', ok: `navModel written on ${fbsId} (${routes.routes.length} route(s))` });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function runThemeA11ySet({ tree, projectRoot, fbsId, stdout, stderr, flags, now }) {
|
|
279
|
+
let cbp;
|
|
280
|
+
if (typeof flags['contrast-before-palette'] === 'string') {
|
|
281
|
+
if (flags['contrast-before-palette'] === 'true') cbp = true;
|
|
282
|
+
else if (flags['contrast-before-palette'] === 'false') cbp = false;
|
|
283
|
+
else {
|
|
284
|
+
stderr.write(`[error] usage design theme-a11y set: --contrast-before-palette must be true or false, got '${flags['contrast-before-palette']}'\n`);
|
|
285
|
+
return 2;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const result = await writeThemeA11ySet({
|
|
289
|
+
projectRoot, tree, fbsId,
|
|
290
|
+
themeMode: flags.mode,
|
|
291
|
+
themeTokensModule: flags.tokens,
|
|
292
|
+
contrastTargets: flags['contrast-targets'],
|
|
293
|
+
contrastTestPath: flags['contrast-test'],
|
|
294
|
+
contrastTestAuthoredBeforePalette: cbp,
|
|
295
|
+
now,
|
|
296
|
+
});
|
|
297
|
+
return handleWriteResult({ result, stdout, stderr, quiet: Boolean(flags.quiet), verb: 'theme-a11y set', ok: `themeAndA11y written on ${fbsId} (mode=${flags.mode})` });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function runMarkComplete({ tree, projectRoot, fbs, fbsId, stdout, stderr, flags, now }) {
|
|
301
|
+
if (flags['dry-run']) {
|
|
302
|
+
const missing = missingDesignStageArtefacts(fbs);
|
|
303
|
+
if (missing.length > 0) {
|
|
304
|
+
stdout.write(`[dry-run] design --mark-complete: would refuse (${fbsId} designStage missing: ${missing.join(', ')})\n`);
|
|
305
|
+
return 0;
|
|
306
|
+
}
|
|
307
|
+
stdout.write(`[dry-run] design --mark-complete: would set designStageComplete=true on ${fbsId}\n`);
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
const result = await writeMarkComplete({ projectRoot, tree, fbsId, now });
|
|
311
|
+
if (result && 'kind' in result && 'message' in result) {
|
|
312
|
+
if (result.kind === 'ioFailure') { writeUnexpectedFailure(result, stderr); return 1; }
|
|
313
|
+
stderr.write(`[error] ${result.kind} ${result.message}\n`);
|
|
314
|
+
if (result.kind === 'usage') return 4; // refused per §6.2
|
|
315
|
+
if (result.kind === 'validation') return 3;
|
|
316
|
+
return 1;
|
|
317
|
+
}
|
|
318
|
+
if (!flags.quiet) stdout.write(`design --mark-complete: ${fbsId} designStageComplete=true.\n`);
|
|
319
|
+
return 0;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function handleWriteResult({ result, stdout, stderr, quiet, verb, ok }) {
|
|
323
|
+
if (result && 'kind' in result && 'message' in result) {
|
|
324
|
+
if (result.kind === 'ioFailure') { writeUnexpectedFailure(result, stderr); return 1; }
|
|
325
|
+
stderr.write(`[error] ${result.kind} ${result.message}\n`);
|
|
326
|
+
if (result.kind === 'usage') return 2;
|
|
327
|
+
if (result.kind === 'validation' || result.kind === 'brokenReference') return 3;
|
|
328
|
+
return 1;
|
|
329
|
+
}
|
|
330
|
+
if (!quiet) stdout.write(`design ${verb}: ${ok}.\n`);
|
|
331
|
+
return 0;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function parseRouteEntries(entries) {
|
|
335
|
+
const routes = [];
|
|
336
|
+
for (const raw of entries) {
|
|
337
|
+
// Shape: path=label:authRequired. `path` can contain `/`; `label`
|
|
338
|
+
// MUST NOT contain `:`, and `authRequired` is the last colon-slice.
|
|
339
|
+
const eq = raw.indexOf('=');
|
|
340
|
+
if (eq < 0) return { error: `bad --route '${raw}'; expected path=label:authRequired` };
|
|
341
|
+
const path = raw.slice(0, eq);
|
|
342
|
+
const rhs = raw.slice(eq + 1);
|
|
343
|
+
const lastColon = rhs.lastIndexOf(':');
|
|
344
|
+
if (lastColon < 0) return { error: `bad --route '${raw}'; expected path=label:authRequired` };
|
|
345
|
+
const label = rhs.slice(0, lastColon);
|
|
346
|
+
const authString = rhs.slice(lastColon + 1);
|
|
347
|
+
if (authString !== 'true' && authString !== 'false') {
|
|
348
|
+
return { error: `bad --route '${raw}'; authRequired must be true or false` };
|
|
349
|
+
}
|
|
350
|
+
routes.push({ path, label, authRequired: authString === 'true' });
|
|
351
|
+
}
|
|
352
|
+
return { routes };
|
|
353
|
+
}
|