@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
package/agent-docs/start.md
CHANGED
|
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
|
|
|
16
16
|
|
|
17
17
|
Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
|
|
18
18
|
|
|
19
|
-
**Updated: 2026-10-
|
|
19
|
+
**Updated: 2026-10-30.**
|
|
20
20
|
|
|
21
21
|
This quickstart takes you from installing Shoalrun to your own working documentation site. You will scaffold a page from a template, complete its draft, put it in the sidebar, and read its HTML and Markdown output.
|
|
22
22
|
|
|
@@ -36,7 +36,7 @@ Install an exact version of the alpha and initialize the minimal starter:
|
|
|
36
36
|
mkdir my-docs
|
|
37
37
|
cd my-docs
|
|
38
38
|
npm init -y
|
|
39
|
-
npm install --save-exact @shoalrun/cli@0.1.0-alpha.
|
|
39
|
+
npm install --save-exact @shoalrun/cli@0.1.0-alpha.14
|
|
40
40
|
npx --no-install shoalrun init .
|
|
41
41
|
```
|
|
42
42
|
|
|
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
|
|
|
16
16
|
|
|
17
17
|
Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
|
|
18
18
|
|
|
19
|
-
**Updated: 2026-10-
|
|
19
|
+
**Updated: 2026-10-30.**
|
|
20
20
|
|
|
21
21
|
Parent page: [Full Markdown page](/start.md)
|
|
22
22
|
|
|
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
|
|
|
16
16
|
|
|
17
17
|
Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
|
|
18
18
|
|
|
19
|
-
**Updated: 2026-10-
|
|
19
|
+
**Updated: 2026-10-30.**
|
|
20
20
|
|
|
21
21
|
Parent page: [Full Markdown page](/start.md)
|
|
22
22
|
|
|
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
|
|
|
16
16
|
|
|
17
17
|
Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
|
|
18
18
|
|
|
19
|
-
**Updated: 2026-10-
|
|
19
|
+
**Updated: 2026-10-30.**
|
|
20
20
|
|
|
21
21
|
Parent page: [Full Markdown page](/start.md)
|
|
22
22
|
|
|
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
|
|
|
16
16
|
|
|
17
17
|
Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
|
|
18
18
|
|
|
19
|
-
**Updated: 2026-10-
|
|
19
|
+
**Updated: 2026-10-30.**
|
|
20
20
|
|
|
21
21
|
Parent page: [Full Markdown page](/start.md)
|
|
22
22
|
|
|
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
|
|
|
16
16
|
|
|
17
17
|
Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
|
|
18
18
|
|
|
19
|
-
**Updated: 2026-10-
|
|
19
|
+
**Updated: 2026-10-30.**
|
|
20
20
|
|
|
21
21
|
Parent page: [Full Markdown page](/start.md)
|
|
22
22
|
|
|
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
|
|
|
16
16
|
|
|
17
17
|
Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
|
|
18
18
|
|
|
19
|
-
**Updated: 2026-10-
|
|
19
|
+
**Updated: 2026-10-30.**
|
|
20
20
|
|
|
21
21
|
Parent page: [Full Markdown page](/start.md)
|
|
22
22
|
|
|
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
|
|
|
16
16
|
|
|
17
17
|
Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
|
|
18
18
|
|
|
19
|
-
**Updated: 2026-10-
|
|
19
|
+
**Updated: 2026-10-30.**
|
|
20
20
|
|
|
21
21
|
Parent page: [Full Markdown page](/start.md)
|
|
22
22
|
|
|
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
|
|
|
16
16
|
|
|
17
17
|
Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
|
|
18
18
|
|
|
19
|
-
**Updated: 2026-10-
|
|
19
|
+
**Updated: 2026-10-30.**
|
|
20
20
|
|
|
21
21
|
Parent page: [Full Markdown page](/start.md)
|
|
22
22
|
|
|
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
|
|
|
16
16
|
|
|
17
17
|
Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
|
|
18
18
|
|
|
19
|
-
**Updated: 2026-10-
|
|
19
|
+
**Updated: 2026-10-30.**
|
|
20
20
|
|
|
21
21
|
Parent page: [Full Markdown page](/start.md)
|
|
22
22
|
|
|
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
|
|
|
16
16
|
|
|
17
17
|
Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
|
|
18
18
|
|
|
19
|
-
**Updated: 2026-10-
|
|
19
|
+
**Updated: 2026-10-30.**
|
|
20
20
|
|
|
21
21
|
Parent page: [Full Markdown page](/start.md)
|
|
22
22
|
|
|
@@ -38,7 +38,7 @@ Install an exact version of the alpha and initialize the minimal starter:
|
|
|
38
38
|
mkdir my-docs
|
|
39
39
|
cd my-docs
|
|
40
40
|
npm init -y
|
|
41
|
-
npm install --save-exact @shoalrun/cli@0.1.0-alpha.
|
|
41
|
+
npm install --save-exact @shoalrun/cli@0.1.0-alpha.14
|
|
42
42
|
npx --no-install shoalrun init .
|
|
43
43
|
```
|
|
44
44
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Built-in semantic connection. Credentials and query embeddings stay server-side.
|
|
2
|
+
import * as lexical from './search-lexical.js';
|
|
3
|
+
const endpoint = new URL('../__shoalrun/search', import.meta.url).pathname;
|
|
4
|
+
let endpointMissing = false;
|
|
5
|
+
export const prepare = lexical.prepare;
|
|
6
|
+
|
|
7
|
+
export async function search(query, context) {
|
|
8
|
+
const fallback = () => lexical.search(query, context);
|
|
9
|
+
context.signal?.throwIfAborted();
|
|
10
|
+
if (endpointMissing || query.trim().length < 3) return fallback();
|
|
11
|
+
// Debounce requests before spending query-embedding credits. Every listener
|
|
12
|
+
// is removed on completion, including when a keystroke cancels this request.
|
|
13
|
+
await new Promise((resolve, reject) => {
|
|
14
|
+
const abort = () => { clearTimeout(timer); context.signal?.removeEventListener('abort', abort); reject(context.signal.reason); };
|
|
15
|
+
const timer = setTimeout(() => { context.signal?.removeEventListener('abort', abort); resolve(); }, 200);
|
|
16
|
+
context.signal?.addEventListener('abort', abort, { once: true });
|
|
17
|
+
});
|
|
18
|
+
context.signal?.throwIfAborted();
|
|
19
|
+
try {
|
|
20
|
+
const signal = AbortSignal.any([AbortSignal.timeout(4000), ...(context.signal ? [context.signal] : [])]);
|
|
21
|
+
const response = await fetch(endpoint, {
|
|
22
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
23
|
+
body: JSON.stringify({ query, variant: context.variant, limit: context.limit, readerSelection: context.readerSelection }), signal
|
|
24
|
+
});
|
|
25
|
+
if (response.status === 404) endpointMissing = true;
|
|
26
|
+
if (!response.ok) return fallback();
|
|
27
|
+
const body = await response.json();
|
|
28
|
+
context.signal?.throwIfAborted();
|
|
29
|
+
return Array.isArray(body.results) ? body.results : fallback();
|
|
30
|
+
} catch {
|
|
31
|
+
context.signal?.throwIfAborted();
|
|
32
|
+
return fallback();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -30,6 +30,8 @@ type ApiDefinition = {
|
|
|
30
30
|
id: string;
|
|
31
31
|
reference: string;
|
|
32
32
|
sourceOperationId?: string;
|
|
33
|
+
shared?: unknown;
|
|
34
|
+
requires?: unknown;
|
|
33
35
|
}>;
|
|
34
36
|
generateReferences?: boolean | {
|
|
35
37
|
pathPrefix?: string;
|
|
@@ -60,6 +62,7 @@ export declare function materializeApiReferences({ config, loaded, variants, doc
|
|
|
60
62
|
allowRouteCollisions?: boolean;
|
|
61
63
|
}): Promise<{
|
|
62
64
|
api: ApiReader;
|
|
65
|
+
operationRequirements: Map<string, unknown>;
|
|
63
66
|
queryRules: Map<string, import("./api.js").QuerySyntaxRule[]>;
|
|
64
67
|
lifecycleMappings: Map<string, {
|
|
65
68
|
from: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { matchesFeatures, validateFeatureClassification } from './deployment-profile.js';
|
|
1
2
|
import { matchesContentFilter } from './content-filter.js';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { ApiReader, querySyntaxRules } from './api.js';
|
|
@@ -37,6 +38,14 @@ function validatedApiDefinitions(value) {
|
|
|
37
38
|
}
|
|
38
39
|
export async function materializeApiReferences({ config, loaded, variants, docs, knownDocumentIds, knownDocumentTypes, authoredOperationIds, routeOwners, basePath, readerFilters, contentFilter, allowRouteCollisions }) {
|
|
39
40
|
const definitions = validatedApiDefinitions(config.apis);
|
|
41
|
+
for (const [id, definition] of Object.entries(definitions)) {
|
|
42
|
+
validateFeatureClassification(definition, loaded.deployment ? { ...loaded.deployment.context, requireClassification: false } : undefined, `API ${id}`);
|
|
43
|
+
for (const operation of definition.operations ?? [])
|
|
44
|
+
validateFeatureClassification(operation, loaded.deployment ? { ...loaded.deployment.context, requireClassification: false } : undefined, `Operation ${operation.id}`);
|
|
45
|
+
if (loaded.deployment?.context.grants && [definition.source, ...(definition.publications ?? []).map(entry => entry.source)].some(source => source?.publish === true))
|
|
46
|
+
throw new Error(`API ${id}: profiled builds cannot publish a full OpenAPI source; use focused contracts (source.publish: false, source.focused: true)`);
|
|
47
|
+
}
|
|
48
|
+
const operationRequirements = new Map();
|
|
40
49
|
const api = new ApiReader();
|
|
41
50
|
const queryRules = new Map(Object.entries(definitions).map(([id, definition]) => [id, querySyntaxRules(definition.querySyntax)]));
|
|
42
51
|
const lifecycleMappings = new Map();
|
|
@@ -108,7 +117,7 @@ export async function materializeApiReferences({ config, loaded, variants, docs,
|
|
|
108
117
|
explicit.add(operation.sourceOperationId);
|
|
109
118
|
}
|
|
110
119
|
definition.operations ??= [];
|
|
111
|
-
for (const variant of variants) {
|
|
120
|
+
for (const variant of loaded.deployment?.context.classificationPublications ?? variants) {
|
|
112
121
|
const binding = sourceForVariant(apiId, definition, variant);
|
|
113
122
|
if (!binding)
|
|
114
123
|
continue;
|
|
@@ -147,7 +156,21 @@ export async function materializeApiReferences({ config, loaded, variants, docs,
|
|
|
147
156
|
knownOperations.set(id, { apiId, reference });
|
|
148
157
|
definition.operations.push({ id, sourceOperationId, reference });
|
|
149
158
|
}
|
|
150
|
-
if (
|
|
159
|
+
if (selected) {
|
|
160
|
+
knownDocumentIds.add(reference);
|
|
161
|
+
knownDocumentTypes.set(reference, 'api-reference');
|
|
162
|
+
}
|
|
163
|
+
const requirements = isConfigMapping(operation) ? operation['x-shoalrun-requires'] : undefined;
|
|
164
|
+
const shared = isConfigMapping(operation) ? operation['x-shoalrun-shared'] : undefined;
|
|
165
|
+
if (selected) {
|
|
166
|
+
const classification = requirements !== undefined || shared !== undefined ? { requires: requirements, shared } : definition;
|
|
167
|
+
validateFeatureClassification(classification, loaded.deployment?.context, `${binding.file}: ${sourceOperationId} (generated reference)`);
|
|
168
|
+
}
|
|
169
|
+
if (!variants.some(item => item.channel === variant.channel && item.version === variant.version && item.language === variant.language))
|
|
170
|
+
continue;
|
|
171
|
+
operationRequirements.set(keyFor(id, variant), requirements);
|
|
172
|
+
const featureSelected = matchesFeatures(requirements, loaded.deployment?.context, `${binding.file}: ${sourceOperationId}`) && matchesFeatures(definition.requires, loaded.deployment?.context, `API ${apiId}`);
|
|
173
|
+
if (!selected || !featureSelected)
|
|
151
174
|
continue;
|
|
152
175
|
const page = resolveFrontmatter({
|
|
153
176
|
id: reference, title: String(isConfigMapping(operation) && operation.summary || `${webhook ? 'Webhook' : method.toUpperCase()} ${route}`),
|
|
@@ -158,6 +181,8 @@ export async function materializeApiReferences({ config, loaded, variants, docs,
|
|
|
158
181
|
validateFrontmatter(page, config.frontmatter, binding.file);
|
|
159
182
|
knownDocumentIds.add(reference);
|
|
160
183
|
knownDocumentTypes.set(reference, page.type);
|
|
184
|
+
if (!matchesFeatures(page.requires, loaded.deployment?.context, `${binding.file}: ${reference}`))
|
|
185
|
+
continue;
|
|
161
186
|
if (page.access?.policy !== 'public' || page.channel !== variant.channel || page.version !== variant.version || page.language !== variant.language)
|
|
162
187
|
continue;
|
|
163
188
|
const key = keyFor(reference, variant);
|
|
@@ -167,6 +192,8 @@ export async function materializeApiReferences({ config, loaded, variants, docs,
|
|
|
167
192
|
throw new Error(`Generated API reference ${reference} conflicts with an authored page`);
|
|
168
193
|
continue;
|
|
169
194
|
}
|
|
195
|
+
if (loaded.deployment?.context.grants && authoredOperationIds.has(id))
|
|
196
|
+
continue;
|
|
170
197
|
const marks = pageReaderFilters(page, readerFilters, binding.file);
|
|
171
198
|
if (!matchesContentFilter(marks, contentFilter))
|
|
172
199
|
continue;
|
|
@@ -183,5 +210,5 @@ export async function materializeApiReferences({ config, loaded, variants, docs,
|
|
|
183
210
|
if (!available.has(id))
|
|
184
211
|
throw new Error(`apis.${apiId}.generateReferences names unknown source operationId ${id}`);
|
|
185
212
|
}
|
|
186
|
-
return { api, queryRules, lifecycleMappings, extensionValue, sourceForVariant, knownOperations, apiDefinitions: definitions };
|
|
213
|
+
return { api, operationRequirements, queryRules, lifecycleMappings, extensionValue, sourceForVariant, knownOperations, apiDefinitions: definitions };
|
|
187
214
|
}
|
|
@@ -32,7 +32,7 @@ export type ArtifactAssemblyInput = {
|
|
|
32
32
|
generatedPaletteCss?: string;
|
|
33
33
|
generatedBadgeCss?: string;
|
|
34
34
|
search: {
|
|
35
|
-
provider: 'lexical' | 'custom' | 'none';
|
|
35
|
+
provider: 'lexical' | 'semantic' | 'custom' | 'none';
|
|
36
36
|
index: boolean;
|
|
37
37
|
module?: string;
|
|
38
38
|
};
|
|
@@ -51,6 +51,7 @@ export type ArtifactAssemblyInput = {
|
|
|
51
51
|
mcpAnalyticsModule?: string;
|
|
52
52
|
previewFiles: string[];
|
|
53
53
|
assetDirectory: string;
|
|
54
|
+
privateDependencies?: ReadonlySet<string>;
|
|
54
55
|
themeSwitcher: boolean;
|
|
55
56
|
themeDirectory: string;
|
|
56
57
|
palette: Palette;
|
package/dist/artifact-bundle.js
CHANGED
|
@@ -98,7 +98,7 @@ export async function assembleArtifacts(input) {
|
|
|
98
98
|
return `shoalrun:${framework}`;
|
|
99
99
|
return `external:${path.basename(resolved)}`;
|
|
100
100
|
};
|
|
101
|
-
addMetadataArtifact('/documents.json', JSON.stringify({ ...manifest, dependencies: [...new Set(manifest.dependencies.map(publicDependency))].sort() }, null, 2) + '\n');
|
|
101
|
+
addMetadataArtifact('/documents.json', JSON.stringify({ ...manifest, dependencies: [...new Set(manifest.dependencies.filter(file => !input.privateDependencies?.has(file)).map(publicDependency))].sort() }, null, 2) + '\n');
|
|
102
102
|
addMetadataArtifact('/build-info.json', JSON.stringify(provenanceRecord, null, 2) + '\n');
|
|
103
103
|
for (const styleFile of styleFiles)
|
|
104
104
|
addPublicArtifact(`/assets/${path.basename(styleFile)}`, await readFile(styleFile, 'utf8'));
|
|
@@ -122,7 +122,7 @@ export async function assembleArtifacts(input) {
|
|
|
122
122
|
const compact = { schemaVersion: 1, documents: index.documents.map(({ id, demo, url, markdownUrl, title, summary, type, variant, headings, sections, navigationTab }) => ({ id, demo, url, markdownUrl, title, summary, type, variant, headings, ...(navigationTab ? { navigationTab } : {}), ...(sections ? { sections } : {}), ...sizes.get(markdownUrl) })) };
|
|
123
123
|
addMetadataArtifact('/search-index.min.json', JSON.stringify(compact) + '\n');
|
|
124
124
|
}
|
|
125
|
-
if (search.provider === 'custom' && search.index)
|
|
125
|
+
if ((search.provider === 'custom' || search.provider === 'semantic') && search.index)
|
|
126
126
|
addPublicArtifact('/assets/search-lexical.js', await readFile(builtInSearchProvider, 'utf8'));
|
|
127
127
|
if (search.module)
|
|
128
128
|
addPublicArtifact('/assets/search-provider.js', await readFile(search.module, 'utf8'));
|
package/dist/authoring-api.d.ts
CHANGED
|
@@ -15,4 +15,4 @@ export { authoringReview, authoringReviewMarkdown } from './authoring-review.js'
|
|
|
15
15
|
export type { AuthoringReview } from './authoring-review.js';
|
|
16
16
|
export { startAuthoringPreview } from './authoring-preview.js';
|
|
17
17
|
export type { AuthoringPreview, AuthoringPreviewOptions } from './authoring-preview.js';
|
|
18
|
-
export declare const authoringCapabilities: readonly ["create-page", "complete-bundle-validation", "project-discovery", "project-source-read", "documentation-search-read", "working-tree-validation", "task-baseline", "validation-freshness", "snapshot-preview", "authoring-review-report"];
|
|
18
|
+
export declare const authoringCapabilities: readonly ["create-page", "complete-bundle-validation", "project-discovery", "project-source-read", "documentation-search-read", "working-tree-validation", "task-baseline", "validation-freshness", "snapshot-preview", "authoring-review-report", "deployment-profiles"];
|
package/dist/authoring-api.js
CHANGED
|
@@ -12,4 +12,4 @@ export { authoringReview, authoringReviewMarkdown } from './authoring-review.js'
|
|
|
12
12
|
export { startAuthoringPreview } from './authoring-preview.js';
|
|
13
13
|
// Optional capabilities let private authoring clients reject an older package
|
|
14
14
|
// before spending provider budget. Existing version-1 update clients still work.
|
|
15
|
-
export const authoringCapabilities = ['create-page', 'complete-bundle-validation', 'project-discovery', 'project-source-read', 'documentation-search-read', 'working-tree-validation', 'task-baseline', 'validation-freshness', 'snapshot-preview', 'authoring-review-report'];
|
|
15
|
+
export const authoringCapabilities = ['create-page', 'complete-bundle-validation', 'project-discovery', 'project-source-read', 'documentation-search-read', 'working-tree-validation', 'task-baseline', 'validation-freshness', 'snapshot-preview', 'authoring-review-report', 'deployment-profiles'];
|
|
@@ -15,11 +15,13 @@ export function parseProjectOptions(command, args) {
|
|
|
15
15
|
seen.add(flag);
|
|
16
16
|
if (flag === '--json')
|
|
17
17
|
continue;
|
|
18
|
-
const allowed = command === 'project' ? ['--config', '--section', '--id', '--file', '--query', '--offset', '--limit', '--max-tokens'] : ['--config', '--offset', '--max-tokens'];
|
|
18
|
+
const allowed = command === 'project' ? ['--profile', '--config', '--section', '--id', '--file', '--query', '--offset', '--limit', '--max-tokens'] : ['--profile', '--config', '--offset', '--max-tokens'];
|
|
19
19
|
const value = args[++i];
|
|
20
20
|
if (!allowed.includes(flag) || !value || value.startsWith('--'))
|
|
21
21
|
throw new ProjectToolError('invalid_input', `Unknown option or missing value for ${flag}`);
|
|
22
|
-
if (flag === '--
|
|
22
|
+
if (flag === '--profile')
|
|
23
|
+
options.profile = value;
|
|
24
|
+
else if (flag === '--config')
|
|
23
25
|
config = value;
|
|
24
26
|
else if (['--offset', '--limit', '--max-tokens'].includes(flag)) {
|
|
25
27
|
if (!/^\d+$/.test(value))
|
|
@@ -2,6 +2,7 @@ export declare const projectToolSchemaVersion = 1;
|
|
|
2
2
|
export declare const projectSections: readonly ["pages", "guidance", "configuration", "navigation", "relationships", "templates", "findings"];
|
|
3
3
|
export type ProjectSection = typeof projectSections[number];
|
|
4
4
|
export type ProjectOptions = {
|
|
5
|
+
profile?: string;
|
|
5
6
|
section?: ProjectSection;
|
|
6
7
|
id?: string;
|
|
7
8
|
file?: string;
|
|
@@ -78,6 +79,7 @@ export declare function inspectAuthoringProject(configFile: string, options?: Pr
|
|
|
78
79
|
}>;
|
|
79
80
|
/** Exact project-relative passage; role is context, not evidence approval. */
|
|
80
81
|
export declare function readAuthoringSource(configFile: string, file: string, options?: {
|
|
82
|
+
profile?: string;
|
|
81
83
|
offset?: number;
|
|
82
84
|
maxTokens?: number;
|
|
83
85
|
}): Promise<{
|
|
@@ -61,11 +61,11 @@ async function safeFile(root, file) {
|
|
|
61
61
|
throw new ProjectToolError('unsafe_path', `Source escapes project: ${file}`);
|
|
62
62
|
return actual;
|
|
63
63
|
}
|
|
64
|
-
async function openProject(configFile) {
|
|
64
|
+
async function openProject(configFile, profile) {
|
|
65
65
|
try {
|
|
66
66
|
const config = await realpath(path.resolve(configFile));
|
|
67
67
|
const root = path.dirname(config);
|
|
68
|
-
const loaded = await loadConfig(config);
|
|
68
|
+
const loaded = await loadConfig(config, { profile });
|
|
69
69
|
const guidance = validateAuthoringGuidance(loaded.value.authoring);
|
|
70
70
|
return { config, root, loaded, guidance };
|
|
71
71
|
}
|
|
@@ -88,7 +88,7 @@ export async function inspectAuthoringProject(configFile, options = {}) {
|
|
|
88
88
|
const offset = integer(options.offset, 0, 0, Number.MAX_SAFE_INTEGER, 'offset');
|
|
89
89
|
const limit = integer(options.limit, 20, 1, 100, 'limit');
|
|
90
90
|
const maxTokens = integer(options.maxTokens, 8192, 1024, 100000, 'maxTokens');
|
|
91
|
-
const { root, config, loaded, guidance } = await openProject(configFile);
|
|
91
|
+
const { root, config, loaded, guidance } = await openProject(configFile, options.profile);
|
|
92
92
|
const value = loaded.value;
|
|
93
93
|
const relative = (file) => path.relative(root, file);
|
|
94
94
|
const digests = new Map();
|
|
@@ -263,7 +263,7 @@ export async function inspectAuthoringProject(configFile, options = {}) {
|
|
|
263
263
|
export async function readAuthoringSource(configFile, file, options = {}) {
|
|
264
264
|
const offset = integer(options.offset, 0, 0, Number.MAX_SAFE_INTEGER, 'offset');
|
|
265
265
|
const maxTokens = integer(options.maxTokens, 4096, 1024, 100000, 'maxTokens');
|
|
266
|
-
const { root, loaded, guidance } = await openProject(configFile);
|
|
266
|
+
const { root, loaded, guidance } = await openProject(configFile, options.profile);
|
|
267
267
|
const actual = await safeFile(root, file);
|
|
268
268
|
const raw = await readFile(actual, 'utf8');
|
|
269
269
|
const chars = [...raw];
|
|
@@ -11,7 +11,12 @@ export type SnapshotInput = {
|
|
|
11
11
|
digest: string;
|
|
12
12
|
mode: number;
|
|
13
13
|
};
|
|
14
|
+
export type ProfileCapture = {
|
|
15
|
+
file: string;
|
|
16
|
+
sources: Record<string, string>;
|
|
17
|
+
};
|
|
14
18
|
export type SnapshotCapture = {
|
|
19
|
+
profile?: ProfileCapture;
|
|
15
20
|
root: string;
|
|
16
21
|
config: string;
|
|
17
22
|
inputs: SnapshotInput[];
|
|
@@ -24,6 +29,7 @@ export type SnapshotCapture = {
|
|
|
24
29
|
configDigest?: string;
|
|
25
30
|
};
|
|
26
31
|
export type AuthoringBaseline = {
|
|
32
|
+
profile?: ProfileCapture;
|
|
27
33
|
schemaVersion: 1;
|
|
28
34
|
kind: 'shoalrun-authoring-baseline';
|
|
29
35
|
project: string;
|
|
@@ -33,7 +39,14 @@ export type AuthoringBaseline = {
|
|
|
33
39
|
externalDirectories: string[];
|
|
34
40
|
excluded: SnapshotCapture['excluded'];
|
|
35
41
|
};
|
|
42
|
+
export declare function validateProfileCapture(root: string, profile?: ProfileCapture): void;
|
|
36
43
|
export declare function relativeInput(root: string, file: string): string;
|
|
44
|
+
/** Read only the named mounted file, including assets retained by a baseline. */
|
|
45
|
+
export declare function readProfileInput(source: string): Promise<{
|
|
46
|
+
bytes: NonSharedBuffer;
|
|
47
|
+
digest: string;
|
|
48
|
+
mode: number;
|
|
49
|
+
}>;
|
|
37
50
|
export declare function collectSnapshotInputs(root: string, externalFiles?: string[], externalDirectories?: string[]): Promise<{
|
|
38
51
|
inputs: SnapshotInput[];
|
|
39
52
|
buffers: Map<string, Buffer<ArrayBufferLike>>;
|
|
@@ -43,9 +56,13 @@ export declare function collectSnapshotInputs(root: string, externalFiles?: stri
|
|
|
43
56
|
}[];
|
|
44
57
|
}>;
|
|
45
58
|
/** Capture static, project-owned inputs without running compiler/plugins/checks. */
|
|
46
|
-
export declare function captureSnapshotInputs(configFile: string
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
export declare function captureSnapshotInputs(configFile: string, options?: {
|
|
60
|
+
profile?: string;
|
|
61
|
+
}): Promise<SnapshotCapture>;
|
|
62
|
+
export declare function confirmSnapshotInputs(capture: Pick<SnapshotCapture, 'root' | 'inputs' | 'externalDirectories' | 'profile'>): Promise<boolean>;
|
|
63
|
+
export declare function captureAuthoringBaseline(configFile: string, options?: {
|
|
64
|
+
profile?: string;
|
|
65
|
+
}): Promise<AuthoringBaseline>;
|
|
49
66
|
export declare function parseAuthoringBaseline(value: unknown, root: string, config: string): AuthoringBaseline;
|
|
50
67
|
export declare function validatorInputs(): Promise<{
|
|
51
68
|
digest: string;
|
|
@@ -53,7 +70,9 @@ export declare function validatorInputs(): Promise<{
|
|
|
53
70
|
}>;
|
|
54
71
|
/** Artifact destinations are exclusive and cannot collide with source inputs. */
|
|
55
72
|
export declare function authoringArtifactPath(root: string, destination: string): Promise<string>;
|
|
56
|
-
export declare function writeAuthoringBaseline(configFile: string, output: string
|
|
73
|
+
export declare function writeAuthoringBaseline(configFile: string, output: string, options?: {
|
|
74
|
+
profile?: string;
|
|
75
|
+
}): Promise<{
|
|
57
76
|
schemaVersion: number;
|
|
58
77
|
status: string;
|
|
59
78
|
baseline: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { componentPluginFiles } from './component-manifest.js';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { lstat, mkdir, readFile, readdir, realpath, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { lstat, mkdir, open, readFile, readdir, realpath, stat, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { constants } from 'node:fs';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
7
|
import { explainSegments, loadConfig } from './config.js';
|
|
@@ -16,10 +17,28 @@ export class SnapshotError extends Error {
|
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
export const sha256 = (value) => createHash('sha256').update(value).digest('hex');
|
|
19
|
-
export const isWithin = (root, file) => file === root || file.startsWith(root + path.sep);
|
|
20
|
+
export const isWithin = (root, file) => file === root || file.startsWith(root.endsWith(path.sep) ? root : root + path.sep);
|
|
20
21
|
export const excludedNames = new Set(['.git', 'node_modules', 'dist', 'site-dist', '.vercel', '.shoalrun', '.private-archives', '.private-evals', '.aws', '.ssh', '.codex', '.npmrc']);
|
|
21
22
|
const excluded = (name) => excludedNames.has(name) || name === '.env' || name.startsWith('.env.');
|
|
22
23
|
const framework = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
24
|
+
export function validateProfileCapture(root, profile) {
|
|
25
|
+
if (profile === undefined)
|
|
26
|
+
return;
|
|
27
|
+
if (!profile || typeof profile.file !== 'string' || !profile.sources || typeof profile.sources !== 'object' || Array.isArray(profile.sources))
|
|
28
|
+
throw new SnapshotError('invalid_input', 'Invalid captured deployment profile');
|
|
29
|
+
const source = profile.sources[profile.file];
|
|
30
|
+
if (typeof source !== 'string' || !path.isAbsolute(source))
|
|
31
|
+
throw new SnapshotError('invalid_input', 'Captured profile needs its explicit source file');
|
|
32
|
+
const directory = `../.shoalrun-profile-${sha256(source).slice(0, 16)}`;
|
|
33
|
+
if (profile.file !== `${directory}/${path.basename(source)}`)
|
|
34
|
+
throw new SnapshotError('unsafe_path', 'Captured profile identity does not match its source');
|
|
35
|
+
for (const [file, original] of Object.entries(profile.sources)) {
|
|
36
|
+
relativeInput(root, file);
|
|
37
|
+
const relative = path.relative(directory, file);
|
|
38
|
+
if (!relative || path.isAbsolute(relative) || relative.startsWith('..') || typeof original !== 'string' || original !== path.resolve(path.dirname(source), relative))
|
|
39
|
+
throw new SnapshotError('unsafe_path', 'Captured profile asset escapes its declared directory');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
23
42
|
export function relativeInput(root, file) {
|
|
24
43
|
if (typeof file !== 'string' || !file || path.isAbsolute(file) || /[\\\0\r\n]/.test(file))
|
|
25
44
|
throw new SnapshotError('unsafe_path', 'Input paths must be project-relative');
|
|
@@ -40,6 +59,29 @@ async function noSymlinks(file, boundary) {
|
|
|
40
59
|
current = path.dirname(current);
|
|
41
60
|
}
|
|
42
61
|
}
|
|
62
|
+
/** Read only the named mounted file, including assets retained by a baseline. */
|
|
63
|
+
export async function readProfileInput(source) {
|
|
64
|
+
const boundary = path.parse(source).root;
|
|
65
|
+
await noSymlinks(source, boundary);
|
|
66
|
+
const handle = await open(source, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
67
|
+
try {
|
|
68
|
+
const before = await handle.stat();
|
|
69
|
+
if (!before.isFile())
|
|
70
|
+
throw new SnapshotError('unsafe_path', 'Profile inputs must be regular files');
|
|
71
|
+
if (before.size > 16 * 1024 * 1024)
|
|
72
|
+
throw new SnapshotError('capture_limit', 'Profile input exceeds 16 MiB');
|
|
73
|
+
const bytes = await handle.readFile();
|
|
74
|
+
const after = await handle.stat();
|
|
75
|
+
await noSymlinks(source, boundary);
|
|
76
|
+
const current = await lstat(source);
|
|
77
|
+
if (bytes.length > 16 * 1024 * 1024 || before.ino !== current.ino || before.dev !== current.dev || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.mode !== after.mode)
|
|
78
|
+
throw new SnapshotError('stale_input', 'Profile input changed while reading; retry validation');
|
|
79
|
+
return { bytes, digest: sha256(bytes), mode: before.mode & 0o777 };
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await handle.close();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
43
85
|
export async function collectSnapshotInputs(root, externalFiles = [], externalDirectories = []) {
|
|
44
86
|
const buffers = new Map();
|
|
45
87
|
const inputs = [];
|
|
@@ -89,18 +131,31 @@ export async function collectSnapshotInputs(root, externalFiles = [], externalDi
|
|
|
89
131
|
return { inputs, buffers, excluded: exclusions };
|
|
90
132
|
}
|
|
91
133
|
/** Capture static, project-owned inputs without running compiler/plugins/checks. */
|
|
92
|
-
export async function captureSnapshotInputs(configFile) {
|
|
134
|
+
export async function captureSnapshotInputs(configFile, options = {}) {
|
|
93
135
|
const config = await realpath(path.resolve(configFile));
|
|
94
136
|
const root = path.dirname(config);
|
|
95
137
|
const externalFiles = new Set(), externalDirectories = new Set();
|
|
138
|
+
const profileFile = options.profile ? await realpath(path.resolve(options.profile)) : undefined;
|
|
139
|
+
let profile;
|
|
140
|
+
const profileDependencies = new Set();
|
|
96
141
|
let configDigest;
|
|
97
142
|
let loaded;
|
|
98
143
|
try {
|
|
99
|
-
loaded = await loadConfig(config);
|
|
144
|
+
loaded = await loadConfig(config, { profile: profileFile });
|
|
100
145
|
configDigest = loaded.digest;
|
|
101
146
|
}
|
|
102
147
|
catch { /* Invalid configuration must still be captured and diagnosed in isolation. */ }
|
|
148
|
+
if (profileFile) {
|
|
149
|
+
const directory = `../.shoalrun-profile-${sha256(profileFile).slice(0, 16)}`;
|
|
150
|
+
profile = { file: `${directory}/${path.basename(profileFile)}`, sources: {} };
|
|
151
|
+
for (const source of [profileFile, ...(loaded?.deployment?.assetFiles ?? [])]) {
|
|
152
|
+
profileDependencies.add(source);
|
|
153
|
+
profile.sources[`${directory}/${path.relative(path.dirname(profileFile), source)}`] = source;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
103
156
|
const addExternal = async (absolute) => {
|
|
157
|
+
if (profileDependencies.has(absolute))
|
|
158
|
+
return;
|
|
104
159
|
if (isWithin(root, absolute))
|
|
105
160
|
return;
|
|
106
161
|
const name = path.relative(root, absolute);
|
|
@@ -138,7 +193,7 @@ export async function captureSnapshotInputs(configFile) {
|
|
|
138
193
|
for (const key of Object.keys(value))
|
|
139
194
|
await visit(value[key], [...segments, key]);
|
|
140
195
|
}
|
|
141
|
-
else if (typeof value === 'string' && (['file', 'path', 'module', 'manifest', 'customCss', 'customReaderModule', 'body', 'template', 'favicon'].includes(segments.at(-1)) || segments.at(-2) === 'files') && !/^(?:[a-z][a-z0-9+.-]*:|#)/i.test(value)) {
|
|
196
|
+
else if (typeof value === 'string' && (['file', 'path', 'module', 'manifest', 'customCss', 'customReaderModule', 'body', 'template', 'favicon', 'logo'].includes(segments.at(-1)) || segments.at(-2) === 'files') && !/^(?:[a-z][a-z0-9+.-]*:|#)/i.test(value)) {
|
|
142
197
|
if (path.isAbsolute(value))
|
|
143
198
|
throw new SnapshotError('unsupported_dependency', `Absolute dependency at ${segments.join('.')} cannot be isolated; use a relative path.`);
|
|
144
199
|
if (segments[0] === 'components' && segments.at(-1) === 'module' && !value.startsWith('.'))
|
|
@@ -199,28 +254,45 @@ export async function captureSnapshotInputs(configFile) {
|
|
|
199
254
|
for (const input of captured.inputs)
|
|
200
255
|
if (complete.inputs.find(item => item.file === input.file)?.digest !== input.digest)
|
|
201
256
|
throw new SnapshotError('stale_input', `Input changed during dependency discovery: ${input.file}`);
|
|
202
|
-
|
|
257
|
+
for (const [file, source] of Object.entries(profile?.sources ?? {})) {
|
|
258
|
+
relativeInput(root, file);
|
|
259
|
+
const input = await readProfileInput(source);
|
|
260
|
+
complete.buffers.set(file, input.bytes);
|
|
261
|
+
complete.inputs.push({ file, digest: input.digest, mode: input.mode });
|
|
262
|
+
}
|
|
263
|
+
complete.inputs.sort((a, b) => a.file.localeCompare(b.file));
|
|
264
|
+
return { root, config: path.basename(config), ...complete, externalDirectories: [...externalDirectories].sort(), configDigest, ...(profile ? { profile } : {}) };
|
|
203
265
|
}
|
|
204
266
|
export async function confirmSnapshotInputs(capture) {
|
|
205
|
-
const
|
|
267
|
+
const sources = capture.profile?.sources ?? {};
|
|
268
|
+
const ordinary = capture.inputs.filter(input => !Object.hasOwn(sources, input.file));
|
|
269
|
+
const external = ordinary.filter(input => (!isWithin(capture.root, path.resolve(capture.root, input.file)) || input.file.split(path.sep).some(excluded))).map(input => input.file);
|
|
206
270
|
try {
|
|
271
|
+
validateProfileCapture(capture.root, capture.profile);
|
|
207
272
|
const now = await collectSnapshotInputs(capture.root, external, capture.externalDirectories);
|
|
273
|
+
for (const [file, source] of Object.entries(sources)) {
|
|
274
|
+
relativeInput(capture.root, file);
|
|
275
|
+
const input = await readProfileInput(source);
|
|
276
|
+
now.inputs.push({ file, digest: input.digest, mode: input.mode });
|
|
277
|
+
}
|
|
278
|
+
now.inputs.sort((a, b) => a.file.localeCompare(b.file));
|
|
208
279
|
return JSON.stringify(now.inputs) === JSON.stringify(capture.inputs);
|
|
209
280
|
}
|
|
210
281
|
catch {
|
|
211
282
|
return false;
|
|
212
283
|
}
|
|
213
284
|
}
|
|
214
|
-
export async function captureAuthoringBaseline(configFile) {
|
|
215
|
-
const capture = await captureSnapshotInputs(configFile);
|
|
285
|
+
export async function captureAuthoringBaseline(configFile, options = {}) {
|
|
286
|
+
const capture = await captureSnapshotInputs(configFile, options);
|
|
216
287
|
if (!await confirmSnapshotInputs(capture))
|
|
217
288
|
throw new SnapshotError('stale_input', 'Working tree changed while capturing the task baseline; retry.');
|
|
218
|
-
return { schemaVersion: 1, kind: 'shoalrun-authoring-baseline', project: capture.root, config: capture.config, inputs: capture.inputs, externalDirectories: capture.externalDirectories, excluded: capture.excluded, digest: sha256(JSON.stringify(capture.inputs)) };
|
|
289
|
+
return { profile: capture.profile, schemaVersion: 1, kind: 'shoalrun-authoring-baseline', project: capture.root, config: capture.config, inputs: capture.inputs, externalDirectories: capture.externalDirectories, excluded: capture.excluded, digest: sha256(JSON.stringify(capture.inputs)) };
|
|
219
290
|
}
|
|
220
291
|
export function parseAuthoringBaseline(value, root, config) {
|
|
221
292
|
const baseline = value;
|
|
222
293
|
if (!baseline || baseline.schemaVersion !== 1 || baseline.kind !== 'shoalrun-authoring-baseline' || baseline.project !== root || baseline.config !== config || !Array.isArray(baseline.inputs) || !Array.isArray(baseline.externalDirectories) || !Array.isArray(baseline.excluded))
|
|
223
294
|
throw new SnapshotError('invalid_input', 'Baseline must be a version-1 task baseline for this project/configuration.');
|
|
295
|
+
validateProfileCapture(root, baseline.profile);
|
|
224
296
|
const seen = new Set();
|
|
225
297
|
for (const directory of baseline.externalDirectories)
|
|
226
298
|
relativeInput(root, directory);
|
|
@@ -284,8 +356,8 @@ export async function authoringArtifactPath(root, destination) {
|
|
|
284
356
|
await noSymlinks(target, path.parse(target).root);
|
|
285
357
|
return target;
|
|
286
358
|
}
|
|
287
|
-
export async function writeAuthoringBaseline(configFile, output) {
|
|
288
|
-
const baseline = await captureAuthoringBaseline(configFile);
|
|
359
|
+
export async function writeAuthoringBaseline(configFile, output, options = {}) {
|
|
360
|
+
const baseline = await captureAuthoringBaseline(configFile, options);
|
|
289
361
|
const target = await authoringArtifactPath(baseline.project, output);
|
|
290
362
|
await mkdir(path.dirname(target), { recursive: true });
|
|
291
363
|
await writeFile(target, JSON.stringify(baseline, null, 2) + '\n', { flag: 'wx' });
|