@shoalrun/cli 0.1.0-alpha.14 → 0.1.0-alpha.16
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 +49 -0
- package/DEPLOYMENT-PROFILES.md +287 -0
- package/README.md +43 -2
- package/agent-docs/.well-known/shoalrun/navigation/project-updates-50f26fe22670.txt +5 -1
- package/agent-docs/SNAPSHOT.json +5 -5
- package/agent-docs/changelog/2026-10-30.md +43 -0
- package/agent-docs/changelog/2026-11-03.md +56 -0
- package/agent-docs/changelog.md +17 -1
- package/agent-docs/documents.json +419 -61
- package/agent-docs/index.md +73 -103
- package/agent-docs/provenance.json +4 -4
- package/agent-docs/publish/content-export.md +1 -1
- package/agent-docs/publish/content-export.sections/prerequisites.md +11 -0
- package/agent-docs/publish/deployment-profiles.md +273 -0
- package/agent-docs/publish/deployment-profiles.sections/before-you-begin.md +44 -0
- package/agent-docs/publish/deployment-profiles.sections/build-and-inspect-the-edition.md +69 -0
- package/agent-docs/publish/deployment-profiles.sections/check-the-result.md +46 -0
- package/agent-docs/publish/deployment-profiles.sections/choose-navigation-labels.md +53 -0
- package/agent-docs/publish/deployment-profiles.sections/current-limits.md +46 -0
- package/agent-docs/publish/deployment-profiles.sections/define-the-shared-sku-manifest.md +70 -0
- package/agent-docs/publish/deployment-profiles.sections/html-markdown-and-agents.md +44 -0
- package/agent-docs/publish/deployment-profiles.sections/mark-pages-and-shared-sections.md +135 -0
- package/agent-docs/publish/deployment-profiles.sections/related-reading.md +42 -0
- package/agent-docs/publish/deployment-profiles.sections/supply-the-profile-from-the-deployment.md +68 -0
- package/agent-docs/search-index.json +1 -1
- package/agent-docs/start.md +2 -2
- package/agent-docs/start.sections/add-the-page-to-navigation.md +1 -1
- package/agent-docs/start.sections/build-and-check-both-outputs.md +1 -1
- package/agent-docs/start.sections/build-this-site.md +1 -1
- package/agent-docs/start.sections/check-writing-style.md +1 -1
- package/agent-docs/start.sections/create-a-page.md +1 -1
- package/agent-docs/start.sections/make-it-yours.md +1 -1
- package/agent-docs/start.sections/migrating-from-github-packages.md +1 -1
- package/agent-docs/start.sections/prerequisites.md +1 -1
- package/agent-docs/start.sections/related-reading.md +1 -1
- package/agent-docs/start.sections/start-your-own-project.md +2 -2
- package/assets/search-semantic.js +34 -0
- package/dist/api-publication-plan.d.ts +3 -0
- package/dist/api-publication-plan.js +30 -3
- package/dist/artifact-bundle.d.ts +2 -1
- package/dist/artifact-bundle.js +2 -2
- package/dist/authoring-api.d.ts +1 -1
- package/dist/authoring-api.js +1 -1
- package/dist/authoring-project-command.js +4 -2
- package/dist/authoring-project.d.ts +2 -0
- package/dist/authoring-project.js +4 -4
- package/dist/authoring-snapshot.d.ts +23 -4
- package/dist/authoring-snapshot.js +84 -12
- package/dist/authoring-validation-command.js +5 -3
- package/dist/authoring-validation.d.ts +2 -0
- package/dist/authoring-validation.js +38 -11
- package/dist/cli-discovery.d.ts +2 -2
- package/dist/cli-discovery.js +9 -9
- package/dist/cli.js +14 -8
- package/dist/compiler.d.ts +13 -0
- package/dist/compiler.js +27 -74
- package/dist/component-names.js +1 -1
- package/dist/component-transform.js +7 -1
- package/dist/config.d.ts +4 -1
- package/dist/config.js +19 -5
- package/dist/content-export-command.d.ts +13 -1
- package/dist/content-export-command.js +3 -2
- package/dist/content-export.d.ts +13 -0
- package/dist/content-export.js +5 -4
- package/dist/content-source.d.ts +1 -0
- package/dist/content-workspace.d.ts +13 -0
- package/dist/content-workspace.js +18 -1
- package/dist/deployment-profile.d.ts +52 -0
- package/dist/deployment-profile.js +277 -0
- package/dist/frontmatter.js +11 -4
- package/dist/index-build.js +7 -3
- package/dist/page-materialization.js +2 -0
- package/dist/page-renderer.d.ts +1 -0
- package/dist/page-renderer.js +3 -1
- package/dist/palettes.d.ts +1 -1
- package/dist/project-navigation.d.ts +5 -0
- package/dist/project-navigation.js +19 -0
- package/dist/publication-plan.d.ts +3 -1
- package/dist/publication-plan.js +23 -4
- package/dist/publication-render.d.ts +3 -2
- package/dist/publication-render.js +2 -2
- package/dist/retrieval.d.ts +4 -0
- package/dist/retrieval.js +28 -8
- package/dist/search-handler.js +11 -11
- package/dist/search-identifier.d.ts +2 -1
- package/dist/search-identifier.js +10 -4
- package/dist/search-results.d.ts +1 -1
- package/dist/search-results.js +39 -25
- package/dist/site-identity.d.ts +3 -0
- package/dist/site-identity.js +69 -0
- package/dist/vercel.d.ts +1 -0
- package/dist/vercel.js +3 -1
- package/package.json +2 -1
- package/skills/shoalrun-author/SKILL.md +5 -1
- package/skills/shoalrun-author/references/customer-editions.md +164 -0
- package/skills/shoalrun-recipes/SKILL.md +2 -0
- package/skills/shoalrun-setup/SKILL.md +4 -0
- package/styles/site.css +2 -0
- package/agent-docs/publish/content-export.sections/upgrade-an-existing-integration.md +0 -34
|
@@ -21,11 +21,13 @@ export async function runValidationCommand(command, args, discoverConfig) {
|
|
|
21
21
|
options.runConfiguredChecks = true;
|
|
22
22
|
continue;
|
|
23
23
|
}
|
|
24
|
-
const allowed = command === 'baseline' ? ['--config', '--out'] : command === 'validate' ? ['--config', '--file', '--id', '--baseline', '--artifacts'] : [];
|
|
24
|
+
const allowed = command === 'baseline' ? ['--config', '--out', '--profile'] : command === 'validate' ? ['--config', '--file', '--id', '--baseline', '--artifacts', '--profile'] : [];
|
|
25
25
|
const value = args[++index];
|
|
26
26
|
if (!allowed.includes(flag) || !value || value.startsWith('--'))
|
|
27
27
|
throw new SnapshotError('invalid_input', `Unknown option or missing value for ${flag}`);
|
|
28
|
-
if (flag === '--
|
|
28
|
+
if (flag === '--profile')
|
|
29
|
+
options.profile = value;
|
|
30
|
+
else if (flag === '--config')
|
|
29
31
|
config = value;
|
|
30
32
|
else if (flag === '--out')
|
|
31
33
|
output = value;
|
|
@@ -42,7 +44,7 @@ export async function runValidationCommand(command, args, discoverConfig) {
|
|
|
42
44
|
throw new SnapshotError('invalid_input', `Unexpected arguments for docs ${command}`);
|
|
43
45
|
if (command === 'baseline' && !output)
|
|
44
46
|
throw new SnapshotError('invalid_input', 'Usage: shoalrun docs baseline --out FILE [--config FILE]');
|
|
45
|
-
const result = command === 'validation-status' ? await authoringValidationStatus(positional[0]) : command === 'baseline' ? await writeAuthoringBaseline(config ?? await discoverConfig(), output) : await validateAuthoringChanges(config ?? await discoverConfig(), options);
|
|
47
|
+
const result = command === 'validation-status' ? await authoringValidationStatus(positional[0]) : command === 'baseline' ? await writeAuthoringBaseline(config ?? await discoverConfig(), output, { profile: options.profile }) : await validateAuthoringChanges(config ?? await discoverConfig(), options);
|
|
46
48
|
process.stdout.write(JSON.stringify(result) + '\n');
|
|
47
49
|
if (result.status !== 'ok')
|
|
48
50
|
process.exitCode = 1;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ValidationFinding } from './authoring-findings.js';
|
|
2
2
|
import { type SnapshotCapture, type SnapshotInput } from './authoring-snapshot.js';
|
|
3
3
|
export type ValidationOptions = {
|
|
4
|
+
profile?: string;
|
|
4
5
|
files?: string[];
|
|
5
6
|
ids?: string[];
|
|
6
7
|
baselineFile?: string;
|
|
@@ -15,6 +16,7 @@ export type ValidationCheck = {
|
|
|
15
16
|
findings?: number;
|
|
16
17
|
};
|
|
17
18
|
export type ValidationReport = {
|
|
19
|
+
profile?: SnapshotCapture['profile'];
|
|
18
20
|
schemaVersion: 1;
|
|
19
21
|
kind: 'shoalrun-authoring-validation';
|
|
20
22
|
status: 'ok' | 'failed' | 'incomplete' | 'unavailable' | 'stale';
|
|
@@ -13,7 +13,7 @@ import { auditReference } from './reference-audit.js';
|
|
|
13
13
|
import { iconLicenseFile } from './icons.js';
|
|
14
14
|
import { parseAuditSettings, runAuditTest } from './audit.js';
|
|
15
15
|
import { configFindingLocation, validationFinding } from './authoring-findings.js';
|
|
16
|
-
import { authoringArtifactPath, captureSnapshotInputs, collectSnapshotInputs, confirmSnapshotInputs, excludedNames, isWithin, parseAuthoringBaseline, relativeInput, sha256, SnapshotError, validatorInputs } from './authoring-snapshot.js';
|
|
16
|
+
import { authoringArtifactPath, captureSnapshotInputs, collectSnapshotInputs, confirmSnapshotInputs, excludedNames, isWithin, parseAuthoringBaseline, relativeInput, sha256, SnapshotError, validatorInputs, validateProfileCapture, readProfileInput } from './authoring-snapshot.js';
|
|
17
17
|
const framework = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
18
|
// Include ordinary hoisted dependency locations in installed npm packages too.
|
|
19
19
|
const checkModulePaths = (createRequire(import.meta.url).resolve.paths('yaml') ?? []).filter(directory => path.basename(directory) === 'node_modules').join(path.delimiter);
|
|
@@ -59,7 +59,7 @@ export async function validateAuthoringChanges(configFile, options) {
|
|
|
59
59
|
// Internal injection boundary for deterministic capture/check race fixtures.
|
|
60
60
|
export async function validateAuthoringChangesWithHooks(configFile, options, hooks = {}) {
|
|
61
61
|
checkedOptions(options);
|
|
62
|
-
const capture = await captureSnapshotInputs(configFile);
|
|
62
|
+
const capture = await captureSnapshotInputs(configFile, { profile: options.profile });
|
|
63
63
|
const runtime = await validatorInputs();
|
|
64
64
|
let baseline = { kind: 'explicit-declaration' };
|
|
65
65
|
const previous = new Map();
|
|
@@ -67,20 +67,41 @@ export async function validateAuthoringChangesWithHooks(configFile, options, hoo
|
|
|
67
67
|
const raw = await readFile(options.baselineFile);
|
|
68
68
|
const value = parseAuthoringBaseline(JSON.parse(raw.toString('utf8')), capture.root, capture.config);
|
|
69
69
|
baseline = { kind: 'task-baseline', file: await realpath(options.baselineFile), digest: sha256(raw) };
|
|
70
|
+
if (value.profile?.file !== capture.profile?.file)
|
|
71
|
+
throw new SnapshotError('invalid_input', 'Baseline and validation must use the same deployment profile path');
|
|
72
|
+
// Retain previously declared profile assets just like other baseline
|
|
73
|
+
// dependencies, even when a profile no longer references an extant file.
|
|
74
|
+
for (const [file, source] of Object.entries(value.profile?.sources ?? {})) {
|
|
75
|
+
if (!capture.profile || Object.hasOwn(capture.profile.sources, file))
|
|
76
|
+
continue;
|
|
77
|
+
const input = await readProfileInput(source).catch(error => { if (error.code === 'ENOENT')
|
|
78
|
+
return undefined; throw error; });
|
|
79
|
+
if (!input)
|
|
80
|
+
continue;
|
|
81
|
+
capture.profile.sources[file] = source;
|
|
82
|
+
capture.inputs.push({ file, digest: input.digest, mode: input.mode });
|
|
83
|
+
capture.buffers.set(file, input.bytes);
|
|
84
|
+
}
|
|
85
|
+
capture.inputs.sort((a, b) => a.file.localeCompare(b.file));
|
|
70
86
|
for (const input of value.inputs)
|
|
71
87
|
previous.set(input.file, input);
|
|
72
88
|
// A dependency removed from config is not necessarily a deleted file. Retain
|
|
73
89
|
// baseline scope for extant sibling inputs so change classification is honest.
|
|
74
90
|
const oldExternal = [];
|
|
75
91
|
for (const input of value.inputs)
|
|
76
|
-
if ((!isWithin(capture.root, path.resolve(capture.root, input.file)) || input.file.split(path.sep).includes('node_modules')) && await stat(relativeInput(capture.root, input.file)).then(() => true, () => false))
|
|
92
|
+
if (!Object.hasOwn(capture.profile?.sources ?? {}, input.file) && (!isWithin(capture.root, path.resolve(capture.root, input.file)) || input.file.split(path.sep).includes('node_modules')) && await stat(relativeInput(capture.root, input.file)).then(() => true, () => false))
|
|
77
93
|
oldExternal.push(input.file);
|
|
78
94
|
const oldDirectories = [];
|
|
79
95
|
for (const directory of value.externalDirectories)
|
|
80
96
|
if (await stat(relativeInput(capture.root, directory)).then(() => true, () => false))
|
|
81
97
|
oldDirectories.push(directory);
|
|
82
98
|
const combinedDirectories = [...new Set([...capture.externalDirectories, ...oldDirectories])].sort();
|
|
83
|
-
const combined = await collectSnapshotInputs(capture.root, [...new Set([...oldExternal, ...capture.inputs.filter(input => (!isWithin(capture.root, path.resolve(capture.root, input.file)) || input.file.split(path.sep).includes('node_modules'))).map(input => input.file)])], combinedDirectories);
|
|
99
|
+
const combined = await collectSnapshotInputs(capture.root, [...new Set([...oldExternal, ...capture.inputs.filter(input => !Object.hasOwn(capture.profile?.sources ?? {}, input.file) && (!isWithin(capture.root, path.resolve(capture.root, input.file)) || input.file.split(path.sep).includes('node_modules'))).map(input => input.file)])], combinedDirectories);
|
|
100
|
+
for (const input of capture.inputs.filter(input => Object.hasOwn(capture.profile?.sources ?? {}, input.file))) {
|
|
101
|
+
combined.inputs.push(input);
|
|
102
|
+
combined.buffers.set(input.file, capture.buffers.get(input.file));
|
|
103
|
+
}
|
|
104
|
+
combined.inputs.sort((a, b) => a.file.localeCompare(b.file));
|
|
84
105
|
for (const input of capture.inputs)
|
|
85
106
|
if (combined.inputs.find(current => current.file === input.file)?.digest !== input.digest)
|
|
86
107
|
throw new SnapshotError('stale_input', `Input changed while comparing the task baseline: ${input.file}`);
|
|
@@ -90,7 +111,7 @@ export async function validateAuthoringChangesWithHooks(configFile, options, hoo
|
|
|
90
111
|
// Loading configuration is read-only. Compilation and executable extensions
|
|
91
112
|
// are never run on the original project.
|
|
92
113
|
try {
|
|
93
|
-
loaded = await loadConfig(path.join(capture.root, capture.config));
|
|
114
|
+
loaded = await loadConfig(path.join(capture.root, capture.config), { profile: options.profile });
|
|
94
115
|
}
|
|
95
116
|
catch { /* Snapshot compiler diagnoses invalid YAML/config. */ }
|
|
96
117
|
const contentDir = path.resolve(capture.root, String(loaded?.value.site?.contentDir ?? 'content'));
|
|
@@ -147,7 +168,7 @@ export async function validateAuthoringChangesWithHooks(configFile, options, hoo
|
|
|
147
168
|
await mkdir(directory);
|
|
148
169
|
}
|
|
149
170
|
const tree = path.join(directory, 'tree'), shadow = path.join(tree, path.basename(capture.root)), site = path.join(directory, 'site');
|
|
150
|
-
const report = { schemaVersion: 1, kind: 'shoalrun-authoring-validation', status: 'incomplete', snapshotId, project: capture.root, config: capture.config, baseline, changed, inputs: capture.inputs, excluded: capture.excluded, externalDirectories: capture.externalDirectories, contentDigest, configDigest: capture.configDigest, validatorDigest: runtime.digest, runtimeDependencies: [], dependencies: [], checks: [], findings: [],
|
|
171
|
+
const report = { profile: capture.profile, schemaVersion: 1, kind: 'shoalrun-authoring-validation', status: 'incomplete', snapshotId, project: capture.root, config: capture.config, baseline, changed, inputs: capture.inputs, excluded: capture.excluded, externalDirectories: capture.externalDirectories, contentDigest, configDigest: capture.configDigest, validatorDigest: runtime.digest, runtimeDependencies: [], dependencies: [], checks: [], findings: [],
|
|
151
172
|
coverage: { scope: 'complete-captured-project', authored, affected, rendered: [], notRendered: [...affected] },
|
|
152
173
|
artifacts: { directory, project: shadow, site, report: path.join(directory, 'report.json'), digest: null, lifecycle: 'Retained local validation artifacts. Remove this artifact directory when finished; it is separate from working-tree source. No preview service is started.' },
|
|
153
174
|
freshness: { current: true, checkedAt: new Date().toISOString() } };
|
|
@@ -175,7 +196,8 @@ export async function validateAuthoringChangesWithHooks(configFile, options, hoo
|
|
|
175
196
|
if (!await confirmSnapshotInputs(capture) || !await absentInputsUnchanged())
|
|
176
197
|
throw new SnapshotError('stale_input', 'Working tree changed during snapshot capture; retry validation.');
|
|
177
198
|
const stagedConfig = path.join(shadow, capture.config);
|
|
178
|
-
|
|
199
|
+
const stagedProfile = capture.profile ? path.resolve(shadow, capture.profile.file) : undefined;
|
|
200
|
+
loaded = await loadConfig(stagedConfig, { profile: stagedProfile });
|
|
179
201
|
if (capture.configDigest && loaded.digest !== capture.configDigest)
|
|
180
202
|
throw new SnapshotError('stale_input', 'Captured configuration does not match the inspected configuration; retry.');
|
|
181
203
|
// Local executable metadata/highlighting modules are not a provider-free
|
|
@@ -198,7 +220,7 @@ export async function validateAuthoringChangesWithHooks(configFile, options, hoo
|
|
|
198
220
|
}
|
|
199
221
|
}
|
|
200
222
|
try {
|
|
201
|
-
const manifest = await compile(stagedConfig, site, {}, { authoringFiles: new Set(affected.map(file => path.join(shadow, file))) });
|
|
223
|
+
const manifest = await compile(stagedConfig, site, {}, { profile: stagedProfile, authoringFiles: new Set(affected.map(file => path.join(shadow, file))) });
|
|
202
224
|
report.dependencies = manifest.dependencies.map(file => isWithin(tree, file) ? path.relative(shadow, file) : file);
|
|
203
225
|
for (const dependency of manifest.dependencies) {
|
|
204
226
|
if (isWithin(tree, dependency)) {
|
|
@@ -212,8 +234,12 @@ export async function validateAuthoringChangesWithHooks(configFile, options, hoo
|
|
|
212
234
|
}
|
|
213
235
|
report.coverage.rendered = (manifest.authoringSources ?? []).map(doc => ({ id: doc.id, file: path.relative(shadow, doc.file), route: doc.url, markdown: doc.markdownUrl }));
|
|
214
236
|
report.coverage.notRendered = affected.filter(file => !report.coverage.rendered.some(doc => doc.file === file));
|
|
215
|
-
for (const file of report.coverage.notRendered)
|
|
216
|
-
|
|
237
|
+
for (const file of report.coverage.notRendered) {
|
|
238
|
+
const profileExcluded = manifest.profileSelection?.decisions.some(decision => !decision.included && decision.id.startsWith(path.join(shadow, file) + ': '));
|
|
239
|
+
report.findings.push(profileExcluded
|
|
240
|
+
? { ...finding('validation.profile-excluded', 'This affected page is excluded from the selected deployment edition; its rendered content was not checked.', 'Validate a profile/publication that includes this page before accepting changes to its content.', 'warning'), file }
|
|
241
|
+
: { ...finding('validation.excluded-page', 'This affected authored page did not render in any enabled publication.', 'Correct its publication/locale selection or enable an isolated review publication and validate again.'), file });
|
|
242
|
+
}
|
|
217
243
|
report.checks.push({ id: 'compile', status: 'passed', documents: manifest.documents.length });
|
|
218
244
|
const settings = parseAuditSettings(config.audit);
|
|
219
245
|
if (settings.links) {
|
|
@@ -324,6 +350,7 @@ export async function readValidationReport(reportFile) {
|
|
|
324
350
|
throw new SnapshotError('invalid_input', 'Expected a version-1 authoring validation report');
|
|
325
351
|
if (!report.baseline || !report.coverage || !Array.isArray(report.coverage.rendered) || !Array.isArray(report.coverage.affected) || !Array.isArray(report.coverage.notRendered) || !Array.isArray(report.checks) || !Array.isArray(report.findings) || !report.artifacts || !report.freshness || typeof report.project !== 'string' || !path.isAbsolute(report.project) || typeof report.snapshotId !== 'string' || !/^[a-f0-9]{64}$/.test(report.snapshotId))
|
|
326
352
|
throw new SnapshotError('invalid_input', 'Malformed authoring validation report');
|
|
353
|
+
validateProfileCapture(report.project, report.profile);
|
|
327
354
|
for (const input of report.inputs)
|
|
328
355
|
relativeInput(report.project, input.file);
|
|
329
356
|
return report;
|
|
@@ -335,7 +362,7 @@ export async function authoringValidationStatus(reportFile) {
|
|
|
335
362
|
for (const input of report.changed)
|
|
336
363
|
if (input.digest === null && await stat(relativeInput(report.project, input.file)).then(() => true, () => false))
|
|
337
364
|
missingInputCreated = true;
|
|
338
|
-
const changed = missingInputCreated || sha256(JSON.stringify(report.inputs)) !== report.contentDigest || !await confirmSnapshotInputs({ root: report.project, inputs: report.inputs, externalDirectories: report.externalDirectories });
|
|
365
|
+
const changed = missingInputCreated || sha256(JSON.stringify(report.inputs)) !== report.contentDigest || !await confirmSnapshotInputs({ root: report.project, inputs: report.inputs, externalDirectories: report.externalDirectories, profile: report.profile });
|
|
339
366
|
const runtime = await validatorInputs();
|
|
340
367
|
let baselineChanged = false, dependencyChanged = false;
|
|
341
368
|
if (report.baseline.file)
|
package/dist/cli-discovery.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const docsHelp = "Usage: shoalrun docs <command>\n\nRead bundled references (no project or provider key required):\n search <query> [--json]\n read <document-id> [--json]\n\nInspect a project:\n project [--config FILE] [--section pages|guidance|configuration|navigation|relationships|templates|findings]\n source FILE [--config FILE]\n context <document-id> [--config FILE]\n\nValidate and preview edits:\n baseline --out FILE [--config FILE]\n validate (--baseline FILE | --file FILE | --id ID) [--config FILE]\n validation-status REPORT\n review REPORT [--format json|markdown]\n preview REPORT [--port PORT]\n\nOther authoring tools: changes, plan, apply, mcp.\nDiscover packaged agent guidance with shoalrun skills list.\n";
|
|
1
|
+
export declare const docsHelp = "Usage: shoalrun docs <command>\n\nRead bundled references (no project or provider key required):\n search <query> [--json]\n read <document-id> [--json]\n\nInspect a project:\n project [--config FILE] [--profile FILE] [--section pages|guidance|configuration|navigation|relationships|templates|findings]\n source FILE [--config FILE] [--profile FILE]\n context <document-id> [--config FILE]\n\nValidate and preview edits:\n baseline --out FILE [--config FILE] [--profile FILE]\n validate (--baseline FILE | --file FILE | --id ID) [--config FILE] [--profile FILE]\n validation-status REPORT\n review REPORT [--format json|markdown]\n preview REPORT [--port PORT]\n\nOther authoring tools: changes, plan, apply, mcp.\nDiscover packaged agent guidance with shoalrun skills list.\n";
|
|
2
2
|
export declare const skillsHelp = "Usage: shoalrun skills list [--json]\n shoalrun skills read <name> [--json]\n\nList the names, descriptions and local paths of packaged skills, or read a skill.\nThese commands do not install skills or require a project or provider key.\n";
|
|
3
|
-
export declare const cliHelp = "Usage: shoalrun <command>\n\nStart and build:\n init [directory] [--starter minimal|api-docs|showcase]\n build [config] [output]\n export [config] --out DIRECTORY [--target NAME]\n dev [config] [output] [--port PORT]\n serve <site-directory> [--host HOST] [--port PORT]\n check [config] [--json]\n audit [config]\n scaffold <template> -n <target> [--config FILE]\n\nAgent guidance and offline documentation:\n skills list [--json]\n skills read <name> [--json]\n docs search <query> [--json]\n docs read <document-id> [--json]\n docs project [--config FILE]\n docs baseline --out FILE [--config FILE]\n docs validate (--baseline FILE | --file FILE | --id ID) [--config FILE]\n docs review REPORT [--format json|markdown]\n docs preview REPORT [--port PORT]\n\nOther commands: config, api, operation, ingredient, recipe, search, index,\nask, answers, ask-log, chat, eval, vercel.\nRun shoalrun docs --help or shoalrun skills --help for discovery commands.\n";
|
|
3
|
+
export declare const cliHelp = "Usage: shoalrun <command>\n\nStart and build:\n init [directory] [--starter minimal|api-docs|showcase]\n build [config] [output] [--profile FILE]\n export [config] --out DIRECTORY [--target NAME] [--profile FILE]\n dev [config] [output] [--port PORT]\n serve <site-directory> [--host HOST] [--port PORT]\n check [config] [--json]\n audit [config]\n scaffold <template> -n <target> [--config FILE]\n\nAgent guidance and offline documentation:\n skills list [--json]\n skills read <name> [--json]\n docs search <query> [--json]\n docs read <document-id> [--json]\n docs project [--config FILE] [--profile FILE]\n docs baseline --out FILE [--config FILE] [--profile FILE]\n docs validate (--baseline FILE | --file FILE | --id ID) [--config FILE] [--profile FILE]\n docs review REPORT [--format json|markdown]\n docs preview REPORT [--port PORT]\n\nOther commands: config, api, operation, ingredient, recipe, search, index,\nask, answers, ask-log, chat, eval, vercel.\nRun shoalrun docs --help or shoalrun skills --help for discovery commands.\n";
|
|
4
4
|
export declare function runSkillsCommand(args: string[]): Promise<string>;
|
package/dist/cli-discovery.js
CHANGED
|
@@ -9,13 +9,13 @@ Read bundled references (no project or provider key required):
|
|
|
9
9
|
read <document-id> [--json]
|
|
10
10
|
|
|
11
11
|
Inspect a project:
|
|
12
|
-
project [--config FILE] [--section pages|guidance|configuration|navigation|relationships|templates|findings]
|
|
13
|
-
source FILE [--config FILE]
|
|
12
|
+
project [--config FILE] [--profile FILE] [--section pages|guidance|configuration|navigation|relationships|templates|findings]
|
|
13
|
+
source FILE [--config FILE] [--profile FILE]
|
|
14
14
|
context <document-id> [--config FILE]
|
|
15
15
|
|
|
16
16
|
Validate and preview edits:
|
|
17
|
-
baseline --out FILE [--config FILE]
|
|
18
|
-
validate (--baseline FILE | --file FILE | --id ID) [--config FILE]
|
|
17
|
+
baseline --out FILE [--config FILE] [--profile FILE]
|
|
18
|
+
validate (--baseline FILE | --file FILE | --id ID) [--config FILE] [--profile FILE]
|
|
19
19
|
validation-status REPORT
|
|
20
20
|
review REPORT [--format json|markdown]
|
|
21
21
|
preview REPORT [--port PORT]
|
|
@@ -33,8 +33,8 @@ export const cliHelp = `Usage: shoalrun <command>
|
|
|
33
33
|
|
|
34
34
|
Start and build:
|
|
35
35
|
init [directory] [--starter minimal|api-docs|showcase]
|
|
36
|
-
build [config] [output]
|
|
37
|
-
export [config] --out DIRECTORY [--target NAME]
|
|
36
|
+
build [config] [output] [--profile FILE]
|
|
37
|
+
export [config] --out DIRECTORY [--target NAME] [--profile FILE]
|
|
38
38
|
dev [config] [output] [--port PORT]
|
|
39
39
|
serve <site-directory> [--host HOST] [--port PORT]
|
|
40
40
|
check [config] [--json]
|
|
@@ -46,9 +46,9 @@ Agent guidance and offline documentation:
|
|
|
46
46
|
skills read <name> [--json]
|
|
47
47
|
docs search <query> [--json]
|
|
48
48
|
docs read <document-id> [--json]
|
|
49
|
-
docs project [--config FILE]
|
|
50
|
-
docs baseline --out FILE [--config FILE]
|
|
51
|
-
docs validate (--baseline FILE | --file FILE | --id ID) [--config FILE]
|
|
49
|
+
docs project [--config FILE] [--profile FILE]
|
|
50
|
+
docs baseline --out FILE [--config FILE] [--profile FILE]
|
|
51
|
+
docs validate (--baseline FILE | --file FILE | --id ID) [--config FILE] [--profile FILE]
|
|
52
52
|
docs review REPORT [--format json|markdown]
|
|
53
53
|
docs preview REPORT [--port PORT]
|
|
54
54
|
|
package/dist/cli.js
CHANGED
|
@@ -73,7 +73,7 @@ function buildArguments(args, defaultOutput) {
|
|
|
73
73
|
positional.push(value);
|
|
74
74
|
continue;
|
|
75
75
|
}
|
|
76
|
-
if (!['--source-commit', '--release-label'].includes(value))
|
|
76
|
+
if (!['--source-commit', '--release-label', '--profile'].includes(value))
|
|
77
77
|
throw new Error(`Unknown build option ${value}`);
|
|
78
78
|
if (flags[value] !== undefined)
|
|
79
79
|
throw new Error(`Duplicate build option ${value}`);
|
|
@@ -84,7 +84,7 @@ function buildArguments(args, defaultOutput) {
|
|
|
84
84
|
}
|
|
85
85
|
if (positional.length > 2)
|
|
86
86
|
throw new Error('Build accepts at most a config and output directory');
|
|
87
|
-
return { config: positional[0] ?? 'docs.config.yaml', output: positional[1] ?? defaultOutput, provenance: { sourceCommit: flags['--source-commit'], releaseLabel: flags['--release-label'] } };
|
|
87
|
+
return { profile: flags['--profile'], config: positional[0] ?? 'docs.config.yaml', output: positional[1] ?? defaultOutput, provenance: { sourceCommit: flags['--source-commit'], releaseLabel: flags['--release-label'] } };
|
|
88
88
|
}
|
|
89
89
|
// An unset or empty PORT means the default; serveSite rejects anything else that is not a port number.
|
|
90
90
|
const defaultPort = () => process.env.PORT?.trim() ? Number(process.env.PORT) : 8765;
|
|
@@ -134,8 +134,8 @@ try {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
else if (command === 'build') {
|
|
137
|
-
const { config, output, provenance } = buildArguments([subcommand, ...rest].filter((item) => item !== undefined), 'site-dist');
|
|
138
|
-
const manifest = await compile(config, output, provenance);
|
|
137
|
+
const { config, output, provenance, profile } = buildArguments([subcommand, ...rest].filter((item) => item !== undefined), 'site-dist');
|
|
138
|
+
const manifest = await compile(config, output, provenance, { profile });
|
|
139
139
|
process.stdout.write(JSON.stringify({ status: 'ok', documents: manifest.documents.length, operations: Object.keys(manifest.operations).length }, null, 2) + '\n');
|
|
140
140
|
}
|
|
141
141
|
else if (command === 'dev') {
|
|
@@ -160,8 +160,10 @@ try {
|
|
|
160
160
|
else if (command === 'vercel' && subcommand === 'build') {
|
|
161
161
|
// --chat adds the optional answer function: it needs chat and retrieval settings and the embedding key at build time.
|
|
162
162
|
const chat = rest.includes('--chat');
|
|
163
|
-
const { config, output, provenance } = buildArguments(rest.filter(item => item !== '--chat'), '.vercel/output');
|
|
164
|
-
|
|
163
|
+
const { config, output, provenance, profile } = buildArguments(rest.filter(item => item !== '--chat'), '.vercel/output');
|
|
164
|
+
if (profile && chat)
|
|
165
|
+
throw new Error('Profiled Vercel builds do not support --chat yet; build the selected static edition without --chat');
|
|
166
|
+
const result = await buildVercel(config, output, provenance, { chat, profile });
|
|
165
167
|
// Asking for the chat and not getting it must fail the deploy. Otherwise a site would look ready and have no chat.
|
|
166
168
|
if (chat && result.chat && !result.chat.added)
|
|
167
169
|
throw new Error(`The chat was requested but not added: ${result.chat.reason}`);
|
|
@@ -236,7 +238,7 @@ try {
|
|
|
236
238
|
embedQuery = async (query, signal) => (await provider.embed([query], 'query', signal))[0];
|
|
237
239
|
}
|
|
238
240
|
extras = { search: createSearchHandler({ retriever, embedQuery, hosts, readerFilters: validateReaderFilters(settings.readerFilters), pageSummaries }) };
|
|
239
|
-
banner = `Search is on: ${embeddings ? '
|
|
241
|
+
banner = `Search is on: ${embeddings ? 'semantic retrieval' : 'lexical retrieval; run shoalrun index --embed for semantic results'}.\n`;
|
|
240
242
|
}
|
|
241
243
|
if (ask) {
|
|
242
244
|
// The preview spends your API credits on every question, so it answers only on this machine.
|
|
@@ -423,7 +425,11 @@ try {
|
|
|
423
425
|
const provider = createProvider(embeddingProviderConfig(retrieval, { provider: values['--provider'], model: values['--model'], dimension: values['--dimension'] === undefined ? undefined : Number(values['--dimension']), baseUrl: values['--base-url'], keyEnv: values['--key-env'] }));
|
|
424
426
|
embeddings = await embedChunkIndex(subcommand, result.index, provider, embeddingInput);
|
|
425
427
|
}
|
|
426
|
-
|
|
428
|
+
const counts = new Map();
|
|
429
|
+
for (const chunk of result.index.chunks)
|
|
430
|
+
counts.set(chunk.variant, (counts.get(chunk.variant) ?? 0) + 1);
|
|
431
|
+
const publications = [...counts].sort(([a], [b]) => a.localeCompare(b)).map(([variant, passages]) => ({ variant, passages }));
|
|
432
|
+
process.stdout.write(JSON.stringify({ status: 'ok', documents: result.documents, chunks: result.index.chunks.length, sourceDigest: result.index.sourceDigest, publications, output: result.output, ...(embeddings ? { embeddings } : {}) }, null, 2) + '\n');
|
|
427
433
|
}
|
|
428
434
|
else if (command === 'ask') {
|
|
429
435
|
const usage = 'Usage: shoalrun ask <site-directory> "<question>" [--config FILE] [--model MODEL] [--passages N] [--reader-view FACET=CHOICE[,CHOICE]]... [--channel C --version V --language L] [--no-rerank] [--no-direct] [--no-escalate] [--no-cards] [--json]';
|
package/dist/compiler.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare function compile(configFile: string, destination?: string, proven
|
|
|
10
10
|
apiReview?: 'report';
|
|
11
11
|
sourceOverrides?: ReadonlyMap<string, string>;
|
|
12
12
|
authoringFiles?: ReadonlySet<string>;
|
|
13
|
+
profile?: string;
|
|
13
14
|
}): Promise<{
|
|
14
15
|
authoringSources?: {
|
|
15
16
|
id: string;
|
|
@@ -36,6 +37,18 @@ export declare function compile(configFile: string, destination?: string, proven
|
|
|
36
37
|
}>;
|
|
37
38
|
}>;
|
|
38
39
|
}[];
|
|
40
|
+
profileSelection: {
|
|
41
|
+
schemaVersion: number;
|
|
42
|
+
profileDigest: string;
|
|
43
|
+
features: string[];
|
|
44
|
+
provenance: Record<string, string[]>;
|
|
45
|
+
decisions: {
|
|
46
|
+
kind: "page" | "section";
|
|
47
|
+
id: string;
|
|
48
|
+
included: boolean;
|
|
49
|
+
requires?: import("./deployment-profile.js").FeatureRequirements;
|
|
50
|
+
}[];
|
|
51
|
+
} | undefined;
|
|
39
52
|
documents: ({
|
|
40
53
|
digest: string;
|
|
41
54
|
markdownBytes: number;
|
package/dist/compiler.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { validateHeader, validateFooter } from './site-identity.js';
|
|
2
|
+
import { profileSelection } from './deployment-profile.js';
|
|
3
|
+
import { profileNavigation } from './project-navigation.js';
|
|
1
4
|
import { canonical, checkOutputDirectory } from './output-location.js';
|
|
2
5
|
import { reviewApiPublication } from './api-publication-review.js';
|
|
3
6
|
import { prepareContentWorkspace } from './content-workspace.js';
|
|
@@ -63,6 +66,8 @@ function validateSearch(value, declaringFile) {
|
|
|
63
66
|
return { provider, index: false };
|
|
64
67
|
if (provider === 'lexical')
|
|
65
68
|
return { provider, module: builtInSearchProvider, index: true };
|
|
69
|
+
if (provider === 'semantic')
|
|
70
|
+
return { provider, module: path.resolve(path.dirname(builtInSearchProvider), 'search-semantic.js'), index: true };
|
|
66
71
|
if (provider !== 'custom')
|
|
67
72
|
throw new Error(`Unknown search provider ${provider}`);
|
|
68
73
|
if (typeof config.module !== 'string' || !/\.m?js$/.test(config.module))
|
|
@@ -74,75 +79,6 @@ function validateSearch(value, declaringFile) {
|
|
|
74
79
|
const builtInSearchProvider = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../assets/search-lexical.js');
|
|
75
80
|
// Each shape preset has a default palette; palettes live in styles/themes/palettes.
|
|
76
81
|
export const themePresets = Object.fromEntries(presetCatalog.map(preset => [preset.name, preset.palette]));
|
|
77
|
-
function validateHeader(value) {
|
|
78
|
-
if (value === undefined)
|
|
79
|
-
return { links: [] };
|
|
80
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
81
|
-
throw new Error('site.header must be a mapping');
|
|
82
|
-
const { links = [], ...extra } = value;
|
|
83
|
-
if (Object.keys(extra).length)
|
|
84
|
-
throw new Error(`site.header does not accept ${Object.keys(extra).join(', ')}`);
|
|
85
|
-
if (!Array.isArray(links))
|
|
86
|
-
throw new Error('site.header.links must be a list');
|
|
87
|
-
return {
|
|
88
|
-
links: links.map((link, index) => {
|
|
89
|
-
const where = `site.header.links[${index}]`;
|
|
90
|
-
const { style = 'link', ...rest } = (link && typeof link === 'object' && !Array.isArray(link) ? link : {});
|
|
91
|
-
const checked = validateSiteLink(link, where, rest);
|
|
92
|
-
if (style !== 'link' && style !== 'button' && style !== 'primary')
|
|
93
|
-
throw new Error(`${where}.style must be link, button, or primary`);
|
|
94
|
-
return { ...checked, style };
|
|
95
|
-
})
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
// Header and footer links share one rule: a label and a site path, http(s) URL, or mailto link.
|
|
99
|
-
function validateSiteLink(link, where, fields = link) {
|
|
100
|
-
if (!link || typeof link !== 'object' || Array.isArray(link))
|
|
101
|
-
throw new Error(`${where} must be a mapping`);
|
|
102
|
-
const { label, href, ...rest } = fields;
|
|
103
|
-
if (Object.keys(rest).length)
|
|
104
|
-
throw new Error(`${where} does not accept ${Object.keys(rest).join(', ')}`);
|
|
105
|
-
if (typeof label !== 'string' || !label.trim())
|
|
106
|
-
throw new Error(`${where}.label is required`);
|
|
107
|
-
if (typeof href !== 'string' || !/^(\/|https?:\/\/|mailto:)/.test(href))
|
|
108
|
-
throw new Error(`${where}.href must be a site path, http(s) URL, or mailto link`);
|
|
109
|
-
return { label, href };
|
|
110
|
-
}
|
|
111
|
-
function validateFooter(value) {
|
|
112
|
-
if (value === undefined)
|
|
113
|
-
return undefined;
|
|
114
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
115
|
-
throw new Error('site.footer must be a mapping');
|
|
116
|
-
const { columns = [], links = [], text, ...extra } = value;
|
|
117
|
-
if (Object.keys(extra).length)
|
|
118
|
-
throw new Error(`site.footer does not accept ${Object.keys(extra).join(', ')}`);
|
|
119
|
-
if (!Array.isArray(columns))
|
|
120
|
-
throw new Error('site.footer.columns must be a list');
|
|
121
|
-
if (!Array.isArray(links))
|
|
122
|
-
throw new Error('site.footer.links must be a list');
|
|
123
|
-
if (text !== undefined && (typeof text !== 'string' || !text.trim()))
|
|
124
|
-
throw new Error('site.footer.text must be non-empty text');
|
|
125
|
-
const footer = {
|
|
126
|
-
columns: columns.map((column, index) => {
|
|
127
|
-
const where = `site.footer.columns[${index}]`;
|
|
128
|
-
if (!column || typeof column !== 'object' || Array.isArray(column))
|
|
129
|
-
throw new Error(`${where} must be a mapping`);
|
|
130
|
-
const { title, links: columnLinks, ...rest } = column;
|
|
131
|
-
if (Object.keys(rest).length)
|
|
132
|
-
throw new Error(`${where} does not accept ${Object.keys(rest).join(', ')}`);
|
|
133
|
-
if (typeof title !== 'string' || !title.trim())
|
|
134
|
-
throw new Error(`${where}.title is required`);
|
|
135
|
-
if (!Array.isArray(columnLinks) || !columnLinks.length)
|
|
136
|
-
throw new Error(`${where}.links must list at least one link`);
|
|
137
|
-
return { title, links: columnLinks.map((link, linkIndex) => validateSiteLink(link, `${where}.links[${linkIndex}]`)) };
|
|
138
|
-
}),
|
|
139
|
-
links: links.map((link, index) => validateSiteLink(link, `site.footer.links[${index}]`)),
|
|
140
|
-
...(typeof text === 'string' ? { text } : {})
|
|
141
|
-
};
|
|
142
|
-
if (!footer.columns.length && !footer.links.length && !footer.text)
|
|
143
|
-
throw new Error('site.footer needs columns, links, or text');
|
|
144
|
-
return footer;
|
|
145
|
-
}
|
|
146
82
|
const contrastFailuresFor = (palette, modes, calloutTypes, badgeVariants) => {
|
|
147
83
|
const customTones = new Map([...calloutTypes.values()].flatMap(type => type.colors ? [[type.tone, type.colors]] : []));
|
|
148
84
|
return [...contrastProblems(palette, modes), ...calloutToneProblems(customTones, palette, modes), ...badgeContrastProblems(badgeVariants, palette, modes)];
|
|
@@ -165,7 +101,7 @@ export async function checkThemeContrast(configFile) {
|
|
|
165
101
|
return { modes, palette: palette.name };
|
|
166
102
|
}
|
|
167
103
|
export async function compile(configFile, destination, provenance = {}, options = {}) {
|
|
168
|
-
const loaded = await loadConfig(configFile);
|
|
104
|
+
const loaded = await loadConfig(configFile, { profile: options.profile });
|
|
169
105
|
const componentPlugins = await loadComponentPlugins(loaded);
|
|
170
106
|
const provenanceRecord = buildInfo(loaded.digest, provenance);
|
|
171
107
|
const resolvedConfig = resolvedCompilerConfig(loaded.value);
|
|
@@ -246,6 +182,10 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
246
182
|
if (faviconFile && !existsSync(faviconFile))
|
|
247
183
|
throw new Error(`site.favicon: file not found: ${faviconFile}`);
|
|
248
184
|
const faviconRoute = publicPath(faviconFile?.toLowerCase().endsWith('.png') ? '/favicon.png' : '/favicon.svg');
|
|
185
|
+
if (config.site.logo !== undefined && (typeof config.site.logo !== 'string' || !/\.(?:svg|png)$/i.test(config.site.logo)))
|
|
186
|
+
throw new Error('site.logo must be a local .svg or .png file');
|
|
187
|
+
const logoFile = config.site.logo === undefined ? undefined : path.resolve(path.dirname(explainSegments(loaded, ['site', 'logo']).source.file), config.site.logo);
|
|
188
|
+
const logoRoute = logoFile ? publicPath(`/assets/brand-logo${path.extname(logoFile).toLowerCase()}`) : undefined;
|
|
249
189
|
const shareFontFiles = [...bundledShareFonts(), ...shareImages.fonts, ...fonts.faces.map(face => face.file).filter(file => /\.(?:ttf|otf)$/i.test(file))];
|
|
250
190
|
const shareCache = path.join(base, '.shoalrun/cache/share-images');
|
|
251
191
|
const fontDirectory = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../styles/fonts');
|
|
@@ -353,7 +293,7 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
353
293
|
throw new Error('site.contentFilter matched no published pages; check page facets and enabled publications');
|
|
354
294
|
let navigationSpec;
|
|
355
295
|
try {
|
|
356
|
-
navigationSpec = validateProjectNavigation(config.navigation ?? [], docs, knownOperations, new Set(Object.keys(config.apis ?? {})));
|
|
296
|
+
navigationSpec = validateProjectNavigation(loaded.deployment?.context.grants && loaded.deployment.context.pruneNavigation ? profileNavigation(config.navigation ?? [], docs, knownDocumentIds, knownOperations, new Set(Object.keys(config.apis ?? {}))) : config.navigation ?? [], docs, knownOperations, new Set(Object.keys(config.apis ?? {})));
|
|
357
297
|
}
|
|
358
298
|
catch (error) {
|
|
359
299
|
const source = config.navigation === undefined ? configFile : explain(loaded, 'navigation').source.file;
|
|
@@ -390,10 +330,20 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
390
330
|
};
|
|
391
331
|
// Local component and Markdown images publish beside their page under /assets/pages.
|
|
392
332
|
const heroImages = new Map();
|
|
333
|
+
if (logoFile && logoRoute)
|
|
334
|
+
heroImages.set(logoRoute, logoFile);
|
|
393
335
|
const walkthroughSources = new Set();
|
|
336
|
+
const usedComponentAssets = new Set();
|
|
394
337
|
const collectPageAssets = (value) => {
|
|
395
338
|
if (!isConfigMapping(value))
|
|
396
339
|
return;
|
|
340
|
+
if (value.type === 'docComponent' && isConfigMapping(value.browser)) {
|
|
341
|
+
if (typeof value.browser.moduleUrl === 'string')
|
|
342
|
+
usedComponentAssets.add(value.browser.moduleUrl);
|
|
343
|
+
for (const route of Array.isArray(value.browser.styleUrls) ? value.browser.styleUrls : [])
|
|
344
|
+
if (typeof route === 'string')
|
|
345
|
+
usedComponentAssets.add(route);
|
|
346
|
+
}
|
|
397
347
|
const image = value.image;
|
|
398
348
|
if (value.type === 'docHero' && isConfigMapping(image) && typeof image.url === 'string' && typeof image.file === 'string') {
|
|
399
349
|
const route = publicPath(image.url);
|
|
@@ -428,7 +378,7 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
428
378
|
rendered, docs, relationships, knownOperations, cardUses, publishedRelationships, cookbooks, documentRoutes,
|
|
429
379
|
basePath, siteOrigin, colorMode, config, highlighter, editionLabel, customStyleFile, themePreset, palette,
|
|
430
380
|
themeStyleFile, generatedPaletteCss, generatedBadgeCss, themeSwitcherData, generatedFontsCss, fontsPreload: fonts.preload,
|
|
431
|
-
shareFor, faviconRoute, modeToggle, header, footer, search, readerFilters, variants, externalDocuments,
|
|
381
|
+
shareFor, faviconRoute, logoRoute, modeToggle, header, footer, search, readerFilters, variants, externalDocuments,
|
|
432
382
|
externalSiteLinks, navigationSpec, navigationEntries, navigationTagOrder
|
|
433
383
|
});
|
|
434
384
|
// The theme previewer and switcher re-theme pages in the browser, so every preset and palette ships only when one is used.
|
|
@@ -501,11 +451,12 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
501
451
|
const artifactConfig = options.authoringFiles ? { ...config, mcp: false } : config;
|
|
502
452
|
const { artifacts, buildId } = await assembleArtifacts({
|
|
503
453
|
base, basePath, publicPath, canonical, siteOrigin, manifest, provenanceRecord, rendered, sitemaps,
|
|
454
|
+
privateDependencies: new Set([...(loaded.deployment?.file ? [loaded.deployment.file] : []), ...(loaded.deployment?.assetFiles ?? [])]),
|
|
504
455
|
diagramAssets, heroImages, focusedContracts, styleFiles, themeStyleFile, generatedPaletteCss,
|
|
505
456
|
generatedBadgeCss, search, synonyms, config: artifactConfig, builtInSearchProvider, customReaderModule, analyticsModule, mcpAnalyticsModule,
|
|
506
457
|
previewFiles, assetDirectory, themeSwitcher, themeDirectory, palette, colorMode, generatedFontsCss,
|
|
507
458
|
fontFiles, fonts, shareImages, shareFor, themePreset, shareFontFiles, shareCache, faviconFile,
|
|
508
|
-
faviconRoute, customStyleFile, scriptFiles, componentAssets: componentPlugins.assets, redirects, apiPublications, api, catalog, headers,
|
|
459
|
+
faviconRoute, customStyleFile, scriptFiles, componentAssets: loaded.deployment?.context.grants ? componentPlugins.assets.filter(asset => usedComponentAssets.has(asset.route)) : componentPlugins.assets, redirects, apiPublications, api, catalog, headers,
|
|
509
460
|
documentRoutes, readerFilters, apiSources
|
|
510
461
|
});
|
|
511
462
|
if (destination) {
|
|
@@ -516,10 +467,12 @@ export async function compile(configFile, destination, provenance = {}, options
|
|
|
516
467
|
await mkdir(localGuide, { recursive: true });
|
|
517
468
|
await writeFile(path.join(localGuide, 'frontmatter-reference.md'), frontmatterReference(config.frontmatter));
|
|
518
469
|
await writeFile(path.join(localGuide, 'frontmatter.schema.json'), JSON.stringify(frontmatterSchema(config.frontmatter), null, 2) + '\n');
|
|
470
|
+
if (loaded.deployment?.context.grants && !options.authoringFiles)
|
|
471
|
+
await writeFile(path.join(localGuide, 'profile-selection.json'), JSON.stringify({ ...profileSelection(loaded.deployment), buildId, configDigest: loaded.digest }, null, 2) + '\n');
|
|
519
472
|
});
|
|
520
473
|
// Authoring captures resolve revision dates without changing captured inputs.
|
|
521
474
|
if (!options.authoringFiles)
|
|
522
475
|
await freshness?.commit();
|
|
523
476
|
}
|
|
524
|
-
return { ...manifest, buildId, apiPublicationReview, ...(options.authoringFiles ? { authoringSources: rendered.map(({ doc }) => ({ id: doc.frontmatter.id, file: doc.file, url: doc.route, markdownUrl: doc.markdownRoute })) } : {}) };
|
|
477
|
+
return { ...manifest, buildId, apiPublicationReview, profileSelection: profileSelection(loaded.deployment), ...(options.authoringFiles ? { authoringSources: rendered.map(({ doc }) => ({ id: doc.frontmatter.id, file: doc.file, url: doc.route, markdownUrl: doc.markdownRoute })) } : {}) };
|
|
525
478
|
}
|
package/dist/component-names.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const builtinComponentNames = new Set('Anchor FrontmatterReference ForPublication ReaderSection ReaderChoice ReaderFilter Only Badge Kbd Tooltip InfoHint Progress Math ActionLink Button DefinitionList Term Definition Steps Timeline Milestone FileTree Tree Folder File Table CollapsibleGroup Collapsible Section Hero ThemePreview Diagram Image Tabs Walkthrough Step Tab Fields Field CookbookChooser DocCard DocCards DocCollection RelatedReading ApiExample ApiSchema Snippet Var DocLink Callout'.split(' '));
|
|
1
|
+
export const builtinComponentNames = new Set('Anchor FrontmatterReference ForFeature ForPublication ReaderSection ReaderChoice ReaderFilter Only Badge Kbd Tooltip InfoHint Progress Math ActionLink Button DefinitionList Term Definition Steps Timeline Milestone FileTree Tree Folder File Table CollapsibleGroup Collapsible Section Hero ThemePreview Diagram Image Tabs Walkthrough Step Tab Fields Field CookbookChooser DocCard DocCards DocCollection RelatedReading ApiExample ApiSchema Snippet Var DocLink Callout'.split(' '));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pruneFeatureTree } from './deployment-profile.js';
|
|
1
2
|
import { lowerComponent } from './component-plugins.js';
|
|
2
3
|
import { cardComponent } from './cards.js';
|
|
3
4
|
import { cookbookChooserComponent } from './cookbook-chooser.js';
|
|
@@ -59,7 +60,8 @@ function literalAttributes(attributes) {
|
|
|
59
60
|
export function transformMdx(tree, current, registry, snippets = new Map(), projectValues = {}, uses = [], calloutTypes = resolveCalloutTypes(undefined, current.file), projectRoot, readerConfig, frontmatterConfig = {}, publicationConfig, badgeVariants = resolveBadgeVariants(undefined, current.file), componentPlugins = new Map()) {
|
|
60
61
|
if (!isMdxRoot(tree))
|
|
61
62
|
throw new Error(`${current.file}: parsed content must be a root with children`);
|
|
62
|
-
tree = lowerSectionMarkers(tree, current.file);
|
|
63
|
+
tree = lowerSectionMarkers(pruneFeatureTree(tree, current.featureContext, current.file), current.file);
|
|
64
|
+
snippets = new Map([...snippets].map(([id, definition]) => [id, { ...definition, tree: pruneFeatureTree(definition.tree, current.featureContext, definition.file) }]));
|
|
63
65
|
let pluginDepth = 0;
|
|
64
66
|
let tabsIndex = 0;
|
|
65
67
|
let walkthroughIndex = 0;
|
|
@@ -130,6 +132,10 @@ export function transformMdx(tree, current, registry, snippets = new Map(), proj
|
|
|
130
132
|
throw new Error(`${sourceFile}: <FrontmatterReference> must be an empty block`);
|
|
131
133
|
return childrenOf(parsePlainMarkdown(frontmatterReference(frontmatterConfig, true)), scope, stack, sourceFile);
|
|
132
134
|
}
|
|
135
|
+
if (node.name === 'ForFeature') {
|
|
136
|
+
const selected = pruneFeatureTree({ type: 'root', children: [node] }, current.featureContext, sourceFile);
|
|
137
|
+
return childrenOf(selected.children, scope, stack, sourceFile);
|
|
138
|
+
}
|
|
133
139
|
if (node.name === 'ForPublication') {
|
|
134
140
|
const attributes = node.attributes ?? [];
|
|
135
141
|
const allowed = ['channel', 'version', 'language'];
|
package/dist/config.d.ts
CHANGED
|
@@ -8,10 +8,13 @@ type Tree = {
|
|
|
8
8
|
};
|
|
9
9
|
export declare class ConfigError extends Error {
|
|
10
10
|
}
|
|
11
|
-
export declare function loadConfig(file: string
|
|
11
|
+
export declare function loadConfig(file: string, options?: {
|
|
12
|
+
profile?: string;
|
|
13
|
+
}): Promise<{
|
|
12
14
|
value: Record<string, unknown>;
|
|
13
15
|
tree: Tree;
|
|
14
16
|
dependencies: string[];
|
|
17
|
+
deployment: import("./deployment-profile.js").ProfileResolution | undefined;
|
|
15
18
|
digest: string;
|
|
16
19
|
}>;
|
|
17
20
|
export declare function explain(config: Awaited<ReturnType<typeof loadConfig>>, key: string): {
|
package/dist/config.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveDeploymentProfile } from './deployment-profile.js';
|
|
1
2
|
import { validateExports } from './content-export-config.js';
|
|
2
3
|
import { validateContentFilter } from './content-filter.js';
|
|
3
4
|
import { validateReaderFilters } from './reader-filters.js';
|
|
@@ -8,7 +9,7 @@ import { parseYaml } from './yaml-value.js';
|
|
|
8
9
|
import { validateSearch } from './search-config.js';
|
|
9
10
|
import { resolveTaxonomy } from './taxonomy.js';
|
|
10
11
|
import { validateCookbooks } from './cookbook-chooser.js';
|
|
11
|
-
const allowed = new Set(['exports', 'schemaVersion', 'site', 'navigation', 'redirects', 'apis', 'relationships', 'variables', 'snippets', 'callouts', 'badges', 'publications', 'outputs', 'templates', 'theme', 'frontmatter', 'metadata', 'audit', 'readerFilters', 'retrieval', 'chat', 'authoring', 'search', 'cookbooks', 'analytics', 'mcp', 'components']);
|
|
12
|
+
const allowed = new Set(['profiles', 'exports', 'schemaVersion', 'site', 'navigation', 'redirects', 'apis', 'relationships', 'variables', 'snippets', 'callouts', 'badges', 'publications', 'outputs', 'templates', 'theme', 'frontmatter', 'metadata', 'audit', 'readerFilters', 'retrieval', 'chat', 'authoring', 'search', 'cookbooks', 'analytics', 'mcp', 'components']);
|
|
12
13
|
const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
13
14
|
const treeObject = (v) => object(v) && !('kind' in v && 'items' in v);
|
|
14
15
|
export class ConfigError extends Error {
|
|
@@ -59,7 +60,7 @@ function merge(base, overlay) {
|
|
|
59
60
|
return overlay;
|
|
60
61
|
return { value: overlay.value, source: { file: overlay.source.file, history: [base.source.file, ...base.source.history] } };
|
|
61
62
|
}
|
|
62
|
-
export async function loadConfig(file) {
|
|
63
|
+
export async function loadConfig(file, options = {}) {
|
|
63
64
|
const dependencies = new Set();
|
|
64
65
|
const root = path.resolve(file);
|
|
65
66
|
async function read(current, stack, rootFile = false) {
|
|
@@ -113,8 +114,21 @@ export async function loadConfig(file) {
|
|
|
113
114
|
delete local.extends;
|
|
114
115
|
return merge(result, await resolve(local));
|
|
115
116
|
}
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
let tree = finalize(await read(root, [], true));
|
|
118
|
+
let value = valueOf(tree);
|
|
119
|
+
if (!object(value) || value.schemaVersion !== 1)
|
|
120
|
+
throw new ConfigError(`${root}: schemaVersion must be 1`);
|
|
121
|
+
const raw = value;
|
|
122
|
+
const profileSettings = treeObject(tree.value) ? tree.value.profiles : undefined;
|
|
123
|
+
const manifestNode = profileSettings && treeObject(profileSettings.value) ? profileSettings.value.manifest : undefined;
|
|
124
|
+
const deployment = await resolveDeploymentProfile(raw.profiles, raw, manifestNode?.source.file ?? root, options.profile);
|
|
125
|
+
if (deployment) {
|
|
126
|
+
for (const file of deployment.dependencies)
|
|
127
|
+
dependencies.add(file);
|
|
128
|
+
const node = (value) => ({ source: { file: deployment.file ?? root, history: [] }, value: Array.isArray(value) ? value.map(node) : object(value) ? Object.fromEntries(Object.entries(value).map(([key, child]) => [key, node(child)])) : value });
|
|
129
|
+
tree = finalize(merge(tree, node(deployment.overlay)));
|
|
130
|
+
value = valueOf(tree);
|
|
131
|
+
}
|
|
118
132
|
if (!object(value) || value.schemaVersion !== 1)
|
|
119
133
|
throw new ConfigError(`${root}: schemaVersion must be 1`);
|
|
120
134
|
for (const key of Object.keys(value))
|
|
@@ -128,7 +142,7 @@ export async function loadConfig(file) {
|
|
|
128
142
|
resolveTaxonomy(object(value.frontmatter) ? value.frontmatter.taxonomy : undefined);
|
|
129
143
|
if (object(value.publications) && ((object(value.publications.defaults) && Object.hasOwn(value.publications.defaults, 'locale')) || (Array.isArray(value.publications.enabled) && value.publications.enabled.some(item => object(item) && Object.hasOwn(item, 'locale')))))
|
|
130
144
|
throw new ConfigError(`${root}: publications.locale is now publications.language; page locale is reserved for formatting conventions`);
|
|
131
|
-
return { value, tree, dependencies: [...dependencies].sort(), digest: createHash('sha256').update(JSON.stringify(value)).digest('hex') };
|
|
145
|
+
return { value, tree, dependencies: [...dependencies].sort(), deployment, digest: createHash('sha256').update(JSON.stringify(deployment ? [value, deployment.digest] : value)).digest('hex') };
|
|
132
146
|
}
|
|
133
147
|
export function explain(config, key) {
|
|
134
148
|
return explainSegments(config, key.split('.'));
|