@shoalrun/cli 0.1.0-alpha.12 → 0.1.0-alpha.14
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 +55 -0
- package/README.md +45 -5
- package/agent-docs/.well-known/shoalrun/navigation/project-updates-50f26fe22670.txt +3 -1
- package/agent-docs/SNAPSHOT.json +5 -5
- package/agent-docs/api-docs/support.md +11 -10
- package/agent-docs/author/components.md +13 -1
- package/agent-docs/author/frontmatter-reference.md +28 -25
- package/agent-docs/author/frontmatter-reference.sections/choose-a-publication-and-visibility.md +3 -1
- package/agent-docs/author/frontmatter-reference.sections/control-output-details.md +3 -1
- package/agent-docs/author/frontmatter-reference.sections/global-defaults-and-enforced-values.md +3 -1
- package/agent-docs/author/frontmatter-reference.sections/html-markdown-and-agents.md +3 -1
- package/agent-docs/author/frontmatter-reference.sections/identify-and-route-a-page.md +3 -1
- package/agent-docs/author/frontmatter-reference.sections/point-agents-to-it.md +3 -1
- package/agent-docs/author/frontmatter-reference.sections/project-defined-fields.md +3 -1
- package/agent-docs/author/frontmatter-reference.sections/related-reading.md +3 -1
- package/agent-docs/author/frontmatter-reference.sections/use-the-local-files-while-authoring.md +3 -1
- package/agent-docs/author/frontmatter-reference.sections/when-the-files-update.md +3 -1
- package/agent-docs/author/frontmatter.md +5 -2
- package/agent-docs/author/frontmatter.sections/audience-badges.md +1 -1
- package/agent-docs/author/frontmatter.sections/give-deprecation-guidance.md +1 -1
- package/agent-docs/author/frontmatter.sections/global-defaults-and-enforced-values.md +1 -1
- package/agent-docs/author/frontmatter.sections/optional-native-fields.md +5 -2
- package/agent-docs/author/frontmatter.sections/publication-fields.md +1 -1
- package/agent-docs/author/frontmatter.sections/publishing-policy.md +1 -1
- package/agent-docs/author/frontmatter.sections/related-metadata-tasks.md +1 -1
- package/agent-docs/author/frontmatter.sections/related-reading.md +1 -1
- package/agent-docs/author/frontmatter.sections/required-after-inheritance.md +1 -1
- package/agent-docs/author/frontmatter.sections/seo-fields.md +1 -1
- package/agent-docs/author/frontmatter.sections/validation-rules.md +1 -1
- package/agent-docs/author/markdown-metadata.md +13 -5
- package/agent-docs/author/navigation.md +12 -1
- package/agent-docs/author/navigation.sections/arrange-authored-pages.md +1 -1
- package/agent-docs/author/navigation.sections/fill-an-api-tag-group.md +1 -1
- package/agent-docs/author/navigation.sections/hide-group-page-counts.md +12 -1
- package/agent-docs/author/navigation.sections/html-markdown-and-agents.md +1 -1
- package/agent-docs/author/navigation.sections/local-api-preview.md +1 -1
- package/agent-docs/author/navigation.sections/mix-guides-and-api-references.md +1 -1
- package/agent-docs/author/navigation.sections/optionally-split-a-large-menu-into-files.md +1 -1
- package/agent-docs/author/navigation.sections/reach-navigation-while-reading.md +1 -1
- package/agent-docs/author/navigation.sections/related-reading.md +1 -1
- package/agent-docs/author/navigation.sections/split-navigation-into-tabs.md +1 -1
- package/agent-docs/author/navigation.sections/subject-lifecycle-labels.md +1 -1
- package/agent-docs/author/templates.md +10 -2
- package/agent-docs/author/templates.sections/built-in-feature-templates.md +1 -1
- package/agent-docs/author/templates.sections/built-in-templates.md +1 -1
- package/agent-docs/author/templates.sections/choose-formatting-enforcement-for-your-project.md +1 -1
- package/agent-docs/author/templates.sections/create-or-edit-a-project-template.md +10 -2
- package/agent-docs/author/templates.sections/keep-the-four-shapes-distinct.md +1 -1
- package/agent-docs/author/templates.sections/project-added-templates.md +1 -1
- package/agent-docs/author/templates.sections/related-reading.md +1 -1
- package/agent-docs/author/templates.sections/scaffold-a-page.md +1 -1
- package/agent-docs/author/templates.sections/short-cli-and-derived-values.md +1 -1
- package/agent-docs/author/templates.sections/what-every-template-gives-you.md +1 -1
- package/agent-docs/author/workspace.md +7 -1
- package/agent-docs/author/writing-style.md +13 -1
- package/agent-docs/author/writing-style.sections/adapt-this-example.md +1 -1
- package/agent-docs/author/writing-style.sections/choose-components-by-reader-need.md +1 -1
- package/agent-docs/author/writing-style.sections/evidence-and-uncertainty.md +1 -1
- package/agent-docs/author/writing-style.sections/example-audience-and-scope.md +1 -1
- package/agent-docs/author/writing-style.sections/feature-availability.md +36 -0
- package/agent-docs/author/writing-style.sections/ingredients-recipes-and-cookbooks.md +1 -1
- package/agent-docs/author/writing-style.sections/page-purpose-and-structure.md +1 -1
- package/agent-docs/author/writing-style.sections/procedures-and-examples.md +1 -1
- package/agent-docs/author/writing-style.sections/related-reading.md +1 -1
- package/agent-docs/author/writing-style.sections/review-a-draft.md +1 -1
- package/agent-docs/author/writing-style.sections/reviewed-writing-examples.md +1 -1
- package/agent-docs/author/writing-style.sections/voice-and-explanations.md +1 -1
- package/agent-docs/changelog/2026-10-28.md +89 -0
- package/agent-docs/changelog.md +9 -1
- package/agent-docs/config/outputs.md +15 -11
- package/agent-docs/config/outputs.sections/exports.md +14 -11
- package/agent-docs/config/outputs.sections/metadata.md +1 -1
- package/agent-docs/config/outputs.sections/related-reading.md +1 -1
- package/agent-docs/content-delivery/agents.md +86 -0
- package/agent-docs/content-delivery/api.md +130 -0
- package/agent-docs/content-delivery/client.md +79 -0
- package/agent-docs/content-delivery/http-api/operations/get-content-representation-fa1d2e3f.md +394 -0
- package/agent-docs/content-delivery/http-api/operations/get-content-representation-head-4052375c.md +256 -0
- package/agent-docs/content-delivery/http-api/operations/get-content-resource-849f1c49.md +911 -0
- package/agent-docs/content-delivery/http-api/operations/get-content-resource-head-0a352acd.md +246 -0
- package/agent-docs/content-delivery/http-api/operations/list-content-resources-8e977ec7.md +913 -0
- package/agent-docs/content-delivery/http-api/operations/list-content-resources-head-b67cbad9.md +236 -0
- package/agent-docs/content-delivery/http-api.md +78 -0
- package/agent-docs/content-delivery/manifest.md +79 -0
- package/agent-docs/content-delivery/publish.md +72 -0
- package/agent-docs/content-delivery/render.md +73 -0
- package/agent-docs/content-delivery/rest.md +108 -0
- package/agent-docs/content-delivery/sections.md +110 -0
- package/agent-docs/content-delivery/select.md +100 -0
- package/agent-docs/content-delivery.md +79 -0
- package/agent-docs/documents.json +6748 -5073
- package/agent-docs/provenance.json +4 -4
- package/agent-docs/publish/content-export.md +32 -16
- package/agent-docs/publish/content-export.sections/assets-downloads-and-notices.md +3 -3
- package/agent-docs/publish/content-export.sections/check-the-result.md +2 -2
- package/agent-docs/publish/content-export.sections/export-selected-pages.md +12 -9
- package/agent-docs/publish/content-export.sections/inspect-the-manifest.md +5 -3
- package/agent-docs/publish/content-export.sections/prerequisites.md +3 -3
- package/agent-docs/publish/content-export.sections/related-reading.md +2 -2
- package/agent-docs/publish/content-export.sections/review-exported-api-material.md +3 -3
- package/agent-docs/publish/content-export.sections/try-the-help-drawer-example.md +3 -3
- package/agent-docs/publish/content-export.sections/upgrade-an-existing-integration.md +34 -0
- package/agent-docs/publish/content-export.sections/what-the-representations-retain.md +4 -4
- package/agent-docs/publish/mcp.md +3 -1
- package/agent-docs/publish/mcp.sections/check-budgets-and-errors.md +1 -1
- package/agent-docs/publish/mcp.sections/choose-the-documentation-source.md +1 -1
- package/agent-docs/publish/mcp.sections/configure-the-client.md +1 -1
- package/agent-docs/publish/mcp.sections/generate-a-vercel-endpoint.md +1 -1
- package/agent-docs/publish/mcp.sections/related-reading.md +3 -1
- package/agent-docs/publish/mcp.sections/search-then-read.md +1 -1
- package/agent-docs/publish/mcp.sections/serve-a-public-endpoint-on-node.md +1 -1
- package/agent-docs/publish/mcp.sections/verify-the-connection.md +1 -1
- package/agent-docs/publish/mcp.sections/why-offer-mcp-for-public-docs.md +1 -1
- package/agent-docs/publish/seo.md +16 -2
- package/agent-docs/publish/seo.sections/related-reading.md +28 -0
- package/agent-docs/publish/seo.sections/set-the-public-address.md +36 -0
- package/agent-docs/publish/seo.sections/what-agents-receive.md +28 -0
- package/agent-docs/publish/seo.sections/what-the-build-adds.md +42 -0
- package/agent-docs/publish/seo.sections/write-the-page-title-and-summary.md +43 -0
- package/agent-docs/search-index.json +1 -1
- package/agent-docs/styling/layouts.md +21 -3
- package/agent-docs/styling/layouts.sections/build-a-landing-page.md +6 -3
- package/agent-docs/styling/layouts.sections/build-a-splash-page.md +6 -3
- package/agent-docs/styling/layouts.sections/control-navigation-tabs.md +6 -3
- package/agent-docs/styling/layouts.sections/customize-the-hero.md +6 -3
- package/agent-docs/styling/layouts.sections/hero.md +6 -3
- package/agent-docs/styling/layouts.sections/hide-the-table-of-contents-or-give-content-more-room.md +54 -0
- package/agent-docs/styling/layouts.sections/html-markdown-and-agents.md +6 -3
- package/agent-docs/styling/layouts.sections/related-reading.md +6 -3
- package/dist/api.js +70 -3
- package/dist/authoring-hints.d.ts +3 -0
- package/dist/authoring-hints.js +35 -0
- package/dist/authoring-project.d.ts +1 -1
- package/dist/authoring-project.js +2 -0
- package/dist/authoring-validation.js +2 -0
- package/dist/code-samples.js +22 -11
- package/dist/compiler-config.d.ts +1 -0
- package/dist/compiler-config.js +13 -0
- package/dist/compiler.js +2 -1
- package/dist/component-names.js +1 -1
- package/dist/component-transform.js +7 -1
- package/dist/content-delivery-contract.d.ts +108 -0
- package/dist/content-delivery-contract.js +15 -0
- package/dist/content-delivery-transport.d.ts +3 -0
- package/dist/content-delivery-transport.js +47 -0
- package/dist/content-delivery-validation.d.ts +7 -0
- package/dist/content-delivery-validation.js +122 -0
- package/dist/content-delivery.d.ts +21 -0
- package/dist/content-delivery.js +119 -0
- package/dist/content-export-anchors.d.ts +11 -0
- package/dist/content-export-anchors.js +79 -0
- package/dist/content-export-command.d.ts +4 -2
- package/dist/content-export-command.js +15 -2
- package/dist/content-export-comparison.d.ts +16 -0
- package/dist/content-export-comparison.js +48 -0
- package/dist/content-export-config.d.ts +17 -0
- package/dist/content-export-config.js +47 -3
- package/dist/content-export-render.js +20 -1
- package/dist/content-export.d.ts +5 -1
- package/dist/content-export.js +193 -40
- package/dist/content-mount.d.ts +11 -0
- package/dist/content-mount.js +73 -0
- package/dist/content-rest-client.d.ts +12 -0
- package/dist/content-rest-client.js +162 -0
- package/dist/content-rest-contract.d.ts +27 -0
- package/dist/content-rest-contract.js +9 -0
- package/dist/content-rest-server.d.ts +7 -0
- package/dist/content-rest-server.js +99 -0
- package/dist/content-source.d.ts +3 -1
- package/dist/frontmatter.js +8 -7
- package/dist/html-renderer.js +25 -0
- package/dist/markdown-sections.d.ts +2 -1
- package/dist/markdown-sections.js +8 -18
- package/dist/page-renderer.js +5 -3
- package/dist/publication-plan.js +2 -1
- package/dist/scaffold-placeholders.d.ts +2 -0
- package/dist/scaffold-placeholders.js +19 -0
- package/dist/section-identity.d.ts +24 -0
- package/dist/section-identity.js +137 -0
- package/examples/content-delivery/README.md +87 -0
- package/examples/content-delivery/app.js +91 -0
- package/examples/content-delivery/content/billing.mdx +22 -0
- package/examples/content-delivery/docs.config.yaml +36 -0
- package/examples/content-delivery/index.html +25 -0
- package/examples/content-delivery/preview.mjs +86 -0
- package/examples/content-delivery/snippets/refund-window.mdx +3 -0
- package/examples/help-drawer/README.md +2 -2
- package/examples/help-drawer/app.js +3 -3
- package/package.json +6 -3
- package/schemas/content-export.schema.json +469 -33
- package/schemas/content-rest.openapi.json +1627 -0
- package/skills/shoalrun-author/SKILL.md +2 -0
- package/styles/site.css +9 -4
|
@@ -2,10 +2,14 @@ import { exportContent } from './content-export.js';
|
|
|
2
2
|
export const exportHelp = `Usage: shoalrun export [config] --out DIRECTORY [--target NAME]
|
|
3
3
|
[--format markdown|html|both] [--document ID]...
|
|
4
4
|
[--channel C --version V --language L] [--public-base /help/] [--review]
|
|
5
|
+
[--previous-manifest FILE] [--fail-on-removed]
|
|
5
6
|
|
|
6
|
-
Export
|
|
7
|
+
Export selected pages, sections, and named snippets as Markdown and passive HTML.
|
|
8
|
+
Configure typed resources in an export target; --document selects whole pages.
|
|
7
9
|
Navigation, facets, and a prior website build are not required.
|
|
8
10
|
--review writes an API-review candidate; status needs-review exits 2 until pinned.
|
|
11
|
+
--previous-manifest compares public references with an earlier reviewed export.
|
|
12
|
+
--fail-on-removed requires that baseline and refuses to replace output if references disappear.
|
|
9
13
|
`;
|
|
10
14
|
export async function runExportCommand(args) {
|
|
11
15
|
const options = {};
|
|
@@ -19,8 +23,14 @@ export async function runExportCommand(args) {
|
|
|
19
23
|
options.review = true;
|
|
20
24
|
continue;
|
|
21
25
|
}
|
|
26
|
+
if (arg === '--fail-on-removed') {
|
|
27
|
+
if (options.failOnRemoved)
|
|
28
|
+
throw new Error(exportHelp);
|
|
29
|
+
options.failOnRemoved = true;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
22
32
|
if (arg.startsWith('--')) {
|
|
23
|
-
if (!['--out', '--target', '--format', '--document', '--channel', '--version', '--language', '--public-base'].includes(arg) || !args[index + 1] || args[index + 1].startsWith('--'))
|
|
33
|
+
if (!['--out', '--target', '--format', '--document', '--channel', '--version', '--language', '--public-base', '--previous-manifest'].includes(arg) || !args[index + 1] || args[index + 1].startsWith('--'))
|
|
24
34
|
throw new Error(exportHelp);
|
|
25
35
|
const value = args[++index];
|
|
26
36
|
if (arg === '--document') {
|
|
@@ -53,5 +63,8 @@ export async function runExportCommand(args) {
|
|
|
53
63
|
}
|
|
54
64
|
options.target = values.get('--target');
|
|
55
65
|
options.publicBase = values.get('--public-base');
|
|
66
|
+
options.previousManifest = values.get('--previous-manifest');
|
|
67
|
+
if (options.failOnRemoved && !options.previousManifest)
|
|
68
|
+
throw new Error('--fail-on-removed requires --previous-manifest');
|
|
56
69
|
return exportContent(config ?? 'docs.config.yaml', output, options);
|
|
57
70
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ContentPublication, type ContentRef } from './content-delivery-contract.js';
|
|
2
|
+
export type ContentReference = {
|
|
3
|
+
publication: ContentPublication;
|
|
4
|
+
ref: ContentRef;
|
|
5
|
+
};
|
|
6
|
+
export type ContentExportComparison = {
|
|
7
|
+
status: 'no-baseline' | 'compared';
|
|
8
|
+
added: ContentReference[];
|
|
9
|
+
removed: ContentReference[];
|
|
10
|
+
diagnostics: Array<{
|
|
11
|
+
code: 'no-reference-baseline' | 'removed-content-reference';
|
|
12
|
+
message: string;
|
|
13
|
+
}>;
|
|
14
|
+
};
|
|
15
|
+
/** Titles, ordering and content edits do not change a public reference. */
|
|
16
|
+
export declare function compareContentManifests(previous: unknown | undefined, current: unknown): ContentExportComparison;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { contentRefKey } from './content-delivery-contract.js';
|
|
2
|
+
const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
const nonempty = (value) => typeof value === 'string' && value.length > 0 && value.trim() === value;
|
|
4
|
+
const keyFor = ({ publication, ref }) => JSON.stringify([publication.channel, publication.version, publication.language, contentRefKey(ref)]);
|
|
5
|
+
/** Validate the public identity surface, without reading files or executing source. */
|
|
6
|
+
function references(manifest, label, requireReviewed) {
|
|
7
|
+
const fail = () => { throw new Error(`${label} must be a content export manifest with valid, unique public resource references`); };
|
|
8
|
+
if (!object(manifest) || manifest.schemaVersion !== 1 || manifest.kind !== 'shoalrun-content-export' || !Array.isArray(manifest.resources) || !manifest.resources.length)
|
|
9
|
+
return fail();
|
|
10
|
+
const publication = manifest.publication;
|
|
11
|
+
if (!object(publication) || Object.keys(publication).length !== 3 || !['channel', 'version', 'language'].every(key => nonempty(publication[key])))
|
|
12
|
+
return fail();
|
|
13
|
+
if (!object(manifest.review) || !['ok', 'required'].includes(String(manifest.review.status)))
|
|
14
|
+
return fail();
|
|
15
|
+
if (requireReviewed && manifest.review.status !== 'ok')
|
|
16
|
+
throw new Error(`${label} must be a reviewed export, not a review candidate`);
|
|
17
|
+
const result = new Map();
|
|
18
|
+
for (const resource of manifest.resources) {
|
|
19
|
+
if (!object(resource) || !object(resource.ref) || !object(resource.publication))
|
|
20
|
+
return fail();
|
|
21
|
+
if (Object.keys(resource.publication).length !== 3 || !['channel', 'version', 'language'].every(key => resource.publication && resource.publication[key] === publication[key]))
|
|
22
|
+
return fail();
|
|
23
|
+
const ref = resource.ref;
|
|
24
|
+
const keys = ref.kind === 'page' ? ['kind', 'documentId'] : ref.kind === 'section' ? ['kind', 'documentId', 'sectionId'] : ref.kind === 'snippet' ? ['kind', 'id'] : [];
|
|
25
|
+
if (!keys.length || Object.keys(ref).length !== keys.length || !keys.every(key => nonempty(ref[key])))
|
|
26
|
+
return fail();
|
|
27
|
+
const entry = { publication: { channel: publication.channel, version: publication.version, language: publication.language }, ref: { ...ref } };
|
|
28
|
+
const key = keyFor(entry);
|
|
29
|
+
if (result.has(key))
|
|
30
|
+
return fail();
|
|
31
|
+
result.set(key, entry);
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
/** Titles, ordering and content edits do not change a public reference. */
|
|
36
|
+
export function compareContentManifests(previous, current) {
|
|
37
|
+
const next = references(current, 'Current manifest', false);
|
|
38
|
+
const sorted = (values) => [...values].sort((a, b) => keyFor(a) < keyFor(b) ? -1 : keyFor(a) > keyFor(b) ? 1 : 0);
|
|
39
|
+
if (previous === undefined)
|
|
40
|
+
return {
|
|
41
|
+
status: 'no-baseline', added: sorted(next.values()), removed: [],
|
|
42
|
+
diagnostics: [{ code: 'no-reference-baseline', message: 'No previous manifest supplied; removed public references were not checked.' }],
|
|
43
|
+
};
|
|
44
|
+
const prior = references(previous, 'Previous manifest', true);
|
|
45
|
+
const removed = sorted([...prior].filter(([key]) => !next.has(key)).map(([, value]) => value));
|
|
46
|
+
const added = sorted([...next].filter(([key]) => !prior.has(key)).map(([, value]) => value));
|
|
47
|
+
return { status: 'compared', added, removed, diagnostics: removed.length ? [{ code: 'removed-content-reference', message: `${removed.length} previously exported public reference(s) are missing. Review removed references before publishing.` }] : [] };
|
|
48
|
+
}
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { type ReaderSelection } from './reader-filters.js';
|
|
2
2
|
import type { Variant } from './content.js';
|
|
3
3
|
export type ExportFormat = 'markdown' | 'html';
|
|
4
|
+
export type ExportResourceSelector = {
|
|
5
|
+
kind: 'page';
|
|
6
|
+
documentId: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'section';
|
|
9
|
+
documentId: string;
|
|
10
|
+
sectionId: string;
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'snippet';
|
|
13
|
+
id: string;
|
|
14
|
+
snippetId: string;
|
|
15
|
+
title: string;
|
|
16
|
+
summary: string;
|
|
17
|
+
values?: Record<string, string | number | boolean>;
|
|
18
|
+
};
|
|
4
19
|
export type ExportTarget = {
|
|
5
20
|
formats: ExportFormat[];
|
|
6
21
|
documents?: string[];
|
|
22
|
+
resources?: ExportResourceSelector[];
|
|
7
23
|
publication?: Variant;
|
|
8
24
|
contentFilter?: ReaderSelection;
|
|
9
25
|
publicBase: string;
|
|
@@ -11,6 +27,7 @@ export type ExportTarget = {
|
|
|
11
27
|
apiReview: Record<string, string>;
|
|
12
28
|
notices: string[];
|
|
13
29
|
};
|
|
30
|
+
export declare function validateExportResources(value: unknown): ExportResourceSelector[];
|
|
14
31
|
export declare function exportPublicBase(value: unknown): string;
|
|
15
32
|
export declare function validateExportTarget(value: unknown, readerFilters?: unknown): ExportTarget;
|
|
16
33
|
export declare function validateExports(value: unknown, readerFilters?: unknown): void;
|
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
import { isConfigMapping } from './compiler-config.js';
|
|
2
2
|
import { validateContentFilter } from './content-filter.js';
|
|
3
3
|
import { validateReaderFilters } from './reader-filters.js';
|
|
4
|
+
import { validSectionId } from './section-identity.js';
|
|
5
|
+
const nonempty = (value) => typeof value === 'string' && value.trim() === value && value.length > 0;
|
|
6
|
+
const snippetId = (value) => nonempty(value) && /^[A-Za-z_][A-Za-z0-9_.-]*$/.test(value)
|
|
7
|
+
&& value.split('.').every(part => /^[A-Za-z_][A-Za-z0-9_-]*$/.test(part) && !['__proto__', 'prototype', 'constructor'].includes(part));
|
|
8
|
+
export function validateExportResources(value) {
|
|
9
|
+
if (!Array.isArray(value) || !value.length)
|
|
10
|
+
throw new Error('Export resources must be a nonempty list');
|
|
11
|
+
const seen = new Set();
|
|
12
|
+
return value.map((item, index) => {
|
|
13
|
+
const fail = (message) => { throw new Error(`Export resources[${index}]: ${message}`); };
|
|
14
|
+
if (!isConfigMapping(item))
|
|
15
|
+
fail('resource must be a mapping');
|
|
16
|
+
const resource = item;
|
|
17
|
+
const keys = resource.kind === 'page' ? ['kind', 'documentId'] : resource.kind === 'section' ? ['kind', 'documentId', 'sectionId'] : resource.kind === 'snippet' ? ['kind', 'id', 'snippetId', 'title', 'summary', 'values'] : undefined;
|
|
18
|
+
if (!keys || Object.keys(resource).some(key => !keys.includes(key)))
|
|
19
|
+
fail('expected a page, section, or snippet selector with only its supported fields');
|
|
20
|
+
let identity;
|
|
21
|
+
if (resource.kind === 'snippet') {
|
|
22
|
+
if (!snippetId(resource.id) || !snippetId(resource.snippetId))
|
|
23
|
+
fail('snippet id and snippetId must be valid stable snippet identifiers');
|
|
24
|
+
if (!nonempty(resource.title) || !nonempty(resource.summary))
|
|
25
|
+
fail('snippet needs a nonempty title and summary');
|
|
26
|
+
if (resource.values !== undefined) {
|
|
27
|
+
if (!isConfigMapping(resource.values) || Object.entries(resource.values).some(([key, value]) => !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(key) || ['__proto__', 'prototype', 'constructor'].includes(key)
|
|
28
|
+
|| !['string', 'number', 'boolean'].includes(typeof value) || typeof value === 'number' && !Number.isFinite(value)))
|
|
29
|
+
fail('snippet values must map safe variable names to finite scalar literals');
|
|
30
|
+
}
|
|
31
|
+
identity = JSON.stringify(['snippet', resource.id]);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
if (!nonempty(resource.documentId))
|
|
35
|
+
fail('documentId must be a nonempty trimmed string');
|
|
36
|
+
if (resource.kind === 'section' && !validSectionId(resource.sectionId))
|
|
37
|
+
fail('sectionId must be a stable lowercase anchor identifier');
|
|
38
|
+
identity = JSON.stringify([resource.kind, resource.documentId, ...(resource.kind === 'section' ? [resource.sectionId] : [])]);
|
|
39
|
+
}
|
|
40
|
+
if (seen.has(identity))
|
|
41
|
+
fail('duplicate resource reference');
|
|
42
|
+
seen.add(identity);
|
|
43
|
+
return { ...resource, ...(resource.values !== undefined ? { values: { ...resource.values } } : {}) };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
4
46
|
export function exportPublicBase(value) {
|
|
5
47
|
if (typeof value !== 'string' || !/^\/(?:[A-Za-z0-9_~.-]+\/)*$/.test(value) || value.split('/').some(part => part === '.' || part === '..')) {
|
|
6
48
|
throw new Error('Export publicBase must be a root-relative path with a trailing slash, such as /help/');
|
|
@@ -8,8 +50,10 @@ export function exportPublicBase(value) {
|
|
|
8
50
|
return value;
|
|
9
51
|
}
|
|
10
52
|
export function validateExportTarget(value, readerFilters) {
|
|
11
|
-
if (!isConfigMapping(value) || Object.keys(value).some(key => !['formats', 'documents', 'publication', 'contentFilter', 'publicBase', 'assetRoots', 'apiReview', 'notices'].includes(key)))
|
|
12
|
-
throw new Error('Export target accepts formats, documents, publication, contentFilter, publicBase, and
|
|
53
|
+
if (!isConfigMapping(value) || Object.keys(value).some(key => !['formats', 'documents', 'resources', 'publication', 'contentFilter', 'publicBase', 'assetRoots', 'apiReview', 'notices'].includes(key)))
|
|
54
|
+
throw new Error('Export target accepts formats, documents or resources, publication, contentFilter, publicBase, assetRoots, apiReview, and notices');
|
|
55
|
+
if (value.documents !== undefined && value.resources !== undefined)
|
|
56
|
+
throw new Error('Export documents and resources are mutually exclusive');
|
|
13
57
|
const formats = value.formats ?? ['markdown', 'html'];
|
|
14
58
|
if (!Array.isArray(formats) || !formats.length || formats.some(format => !['markdown', 'html'].includes(format)) || new Set(formats).size !== formats.length)
|
|
15
59
|
throw new Error('Export formats must be a nonempty unique list of markdown and/or html');
|
|
@@ -28,7 +72,7 @@ export function validateExportTarget(value, readerFilters) {
|
|
|
28
72
|
}
|
|
29
73
|
if (value.apiReview !== undefined && (!isConfigMapping(value.apiReview) || Object.values(value.apiReview).some(digest => typeof digest !== 'string' || !/^[a-f0-9]{64}$/.test(digest))))
|
|
30
74
|
throw new Error('Export apiReview must map API IDs to lowercase SHA-256 digests');
|
|
31
|
-
return { formats: [...formats].sort(), ...(value.documents !== undefined ? { documents: strings(value.documents, 'documents') } : {}), ...(publication ? { publication } : {}),
|
|
75
|
+
return { formats: [...formats].sort(), ...(value.documents !== undefined ? { documents: strings(value.documents, 'documents') } : {}), ...(value.resources !== undefined ? { resources: validateExportResources(value.resources) } : {}), ...(publication ? { publication } : {}),
|
|
32
76
|
...(value.contentFilter !== undefined ? { contentFilter: validateContentFilter(value.contentFilter, validateReaderFilters(readerFilters)) } : {}),
|
|
33
77
|
publicBase: exportPublicBase(value.publicBase ?? '/'), assetRoots: strings(value.assetRoots, 'assetRoots') ?? [], apiReview: (value.apiReview ?? {}), notices: strings(value.notices, 'notices') ?? [] };
|
|
34
78
|
}
|
|
@@ -5,7 +5,22 @@ import { heading, link, paragraph, root, text, renderMarkdown } from './content.
|
|
|
5
5
|
import { walkthroughMarkdown } from './walkthrough.js';
|
|
6
6
|
/** Lower interactions while representation-specific source blocks still exist. */
|
|
7
7
|
export function portableTree(tree, format, routes, basePath) {
|
|
8
|
+
// Provenance is internal export metadata. Keep it when component lowering
|
|
9
|
+
// synthesizes headings so a slice can retain its full-source identities.
|
|
10
|
+
const inherit = (node, key) => {
|
|
11
|
+
if (!node || typeof node !== 'object')
|
|
12
|
+
return;
|
|
13
|
+
node.data = { ...node.data, exportSourceKey: node.data?.exportSourceKey ?? key };
|
|
14
|
+
for (const [index, child] of (node.children ?? []).entries())
|
|
15
|
+
inherit(child, `${node.data.exportSourceKey}/${index}`);
|
|
16
|
+
};
|
|
8
17
|
const walk = (node) => {
|
|
18
|
+
const result = lower(node);
|
|
19
|
+
if (node.data?.exportSourceKey)
|
|
20
|
+
result.forEach((child, index) => inherit(child, `${node.data.exportSourceKey}:${index}`));
|
|
21
|
+
return result;
|
|
22
|
+
};
|
|
23
|
+
const lower = (node) => {
|
|
9
24
|
if (node.type === 'docRepresentation')
|
|
10
25
|
return node.representation === format ? node.children.flatMap(walk) : [];
|
|
11
26
|
if (node.type === 'docComponent')
|
|
@@ -39,8 +54,12 @@ export function portableTree(tree, format, routes, basePath) {
|
|
|
39
54
|
const lowered = publicTree(root((tree.children ?? []).flatMap(walk)), format, routes, 'light', basePath);
|
|
40
55
|
const definitions = new Map();
|
|
41
56
|
const collectDefinitions = (node) => {
|
|
42
|
-
if (node.type === 'definition')
|
|
57
|
+
if (node.type === 'definition') {
|
|
58
|
+
const previous = definitions.get(node.identifier);
|
|
59
|
+
if (previous && (previous.url !== node.url || previous.title !== node.title))
|
|
60
|
+
throw new Error(`Ambiguous export definition ${node.identifier}`);
|
|
43
61
|
definitions.set(node.identifier, node);
|
|
62
|
+
}
|
|
44
63
|
for (const child of node.children ?? [])
|
|
45
64
|
collectDefinitions(child);
|
|
46
65
|
};
|
package/dist/content-export.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export type ContentExportOptions = {
|
|
|
7
7
|
publication?: Variant;
|
|
8
8
|
publicBase?: string;
|
|
9
9
|
review?: boolean;
|
|
10
|
+
previousManifest?: string;
|
|
11
|
+
failOnRemoved?: boolean;
|
|
10
12
|
};
|
|
11
13
|
/** Export selected source content; never invoke the website shell/compiler. */
|
|
12
14
|
export declare function exportContent(configFile: string, destination: string, options?: ContentExportOptions): Promise<{
|
|
@@ -14,8 +16,10 @@ export declare function exportContent(configFile: string, destination: string, o
|
|
|
14
16
|
output: string;
|
|
15
17
|
publication: Variant;
|
|
16
18
|
formats: ExportFormat[];
|
|
19
|
+
resources: number;
|
|
17
20
|
documents: number;
|
|
18
21
|
documentIds: string[];
|
|
22
|
+
comparison: import("./content-export-comparison.js").ContentExportComparison;
|
|
19
23
|
assets: number;
|
|
20
24
|
digest: string;
|
|
21
25
|
apiReview: {
|
|
@@ -24,7 +28,7 @@ export declare function exportContent(configFile: string, destination: string, o
|
|
|
24
28
|
}[];
|
|
25
29
|
diagnostics: {
|
|
26
30
|
code: string;
|
|
27
|
-
documentId
|
|
31
|
+
documentId?: string;
|
|
28
32
|
message: string;
|
|
29
33
|
}[];
|
|
30
34
|
}>;
|