@astryxdesign/cli 0.6.4 → 0.6.5-canary.031021b
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 +56 -0
- package/README.md +97 -90
- package/api/build/build.doc.mjs +6 -1
- package/api/build/build.test.mjs +22 -0
- package/api/build/kit/kit.mjs +44 -5
- package/api/component/_adapter.d.mts +25 -0
- package/api/component/_adapter.mjs +59 -5
- package/api/component/component.d.mts +6 -3
- package/api/component/component.doc.mjs +37 -17
- package/api/component/component.mjs +249 -9
- package/api/component/component.type.d.mts +25 -0
- package/api/component/component.type.mjs +44 -0
- package/api/discover/_adapter.d.mts +114 -6
- package/api/discover/_adapter.mjs +372 -17
- package/api/discover/_adapter.test.mjs +215 -0
- package/api/discover/_catalog-view.d.mts +115 -0
- package/api/discover/_catalog-view.mjs +203 -0
- package/api/discover/_catalog-view.test.mjs +128 -0
- package/api/discover/detail/detail.d.mts +18 -6
- package/api/discover/detail/detail.mjs +67 -13
- package/api/discover/detail/detail.test.mjs +85 -0
- package/api/discover/detail/item/item.d.mts +26 -0
- package/api/discover/detail/item/item.mjs +78 -0
- package/api/discover/detail/item/item.test.mjs +73 -0
- package/api/discover/discover.d.mts +3 -9
- package/api/discover/discover.doc.mjs +61 -18
- package/api/discover/discover.mjs +220 -36
- package/api/discover/discover.test.mjs +11 -2
- package/api/discover/discover.type.d.mts +147 -8
- package/api/discover/discover.type.mjs +102 -12
- package/api/discover/list/list.d.mts +20 -6
- package/api/discover/list/list.mjs +45 -12
- package/api/discover/list/list.test.mjs +46 -0
- package/api/discover/search/search.d.mts +18 -16
- package/api/discover/search/search.mjs +102 -56
- package/api/discover/search/search.test.mjs +144 -10
- package/api/docs/_adapter.d.mts +8 -3
- package/api/docs/_adapter.mjs +14 -6
- package/api/docs/docOverlays.test.mjs +27 -1
- package/api/docs/docs.doc.mjs +2 -2
- package/api/doctor/doctor.d.mts +8 -3
- package/api/doctor/doctor.doc.mjs +17 -8
- package/api/doctor/doctor.mjs +90 -9
- package/api/doctor/doctor.test.mjs +122 -10
- package/api/doctor/doctor.type.d.mts +1 -1
- package/api/doctor/doctor.type.mjs +1 -1
- package/api/gap-report/gap-report.doc.mjs +19 -10
- package/api/hook/hook.doc.mjs +6 -3
- package/api/index.d.mts +1 -0
- package/api/index.mjs +5 -3
- package/api/init/init.doc.mjs +17 -12
- package/api/integration/add-helpers.d.mts +5 -2
- package/api/integration/add-helpers.mjs +36 -9
- package/api/integration/add-theme.mjs +22 -1
- package/api/integration/add-theme.test.mjs +34 -0
- package/api/integration/authoring-checks.mjs +2 -2
- package/api/integration/integrationPackCheck.doc.mjs +3 -3
- package/api/integration/pack-check.lifecycle-output.test.mjs +107 -0
- package/api/integration/pack-check.mjs +82 -9
- package/api/integration/pack-check.test.mjs +90 -0
- package/api/integration/pack-check.type.mjs +1 -1
- package/api/json/assertResponse.doc.mjs +1 -1
- package/api/json/isError.doc.mjs +1 -1
- package/api/search/search.d.mts +27 -1
- package/api/search/search.doc.mjs +2 -2
- package/api/search/search.mjs +228 -16
- package/api/swizzle/swizzle.doc.mjs +7 -5
- package/api/template/copy/copy.mjs +1 -1
- package/api/template/copy/copy.test.mjs +9 -0
- package/api/template/template.doc.mjs +2 -1
- package/api/theme/add/add.mjs +17 -25
- package/api/theme/add/add.rollback.test.mjs +158 -0
- package/api/theme/add/add.staging.test.mjs +40 -23
- package/api/theme/build/build.family.test.mjs +7 -12
- package/api/theme/build/build.mjs +8 -18
- package/api/theme/build/build.rollback.test.mjs +148 -0
- package/api/theme/generateTonalPalette.doc.mjs +1 -2
- package/api/theme/listThemes.doc.mjs +1 -1
- package/api/theme/themeAdd.doc.mjs +9 -10
- package/api/theme/themeBuild.doc.mjs +13 -13
- package/api/theme/themeList.doc.mjs +1 -1
- package/api/theme/themeListAvailable.doc.mjs +2 -1
- package/api/theme/themePaletteGenerate.doc.mjs +15 -8
- package/api/theme/themeTargets.doc.mjs +3 -2
- package/api/theme/themeTemplate.doc.mjs +2 -1
- package/api/upgrade/run/files-changed.test.mjs +111 -0
- package/api/upgrade/run/run.mjs +5 -3
- package/api/upgrade/upgrade.doc.mjs +24 -22
- package/api/upgrade/upgrade.type.mjs +2 -2
- package/assets/codemods/__tests__/runner.test.mjs +3 -1
- package/assets/codemods/file-count.test.mjs +163 -0
- package/assets/codemods/integration-runner.mjs +3 -3
- package/assets/codemods/runner.mjs +5 -4
- package/assets/docs/README.md +4 -2
- package/assets/docs/browser-support.doc.mjs +11 -11
- package/assets/docs/color.doc.mjs +8 -2
- package/assets/docs/elevation.doc.mjs +6 -4
- package/assets/docs/getting-started.doc.mjs +5 -16
- package/assets/docs/icons.doc.mjs +2 -21
- package/assets/docs/illustrations.doc.mjs +7 -15
- package/assets/docs/internationalization.doc.mjs +7 -5
- package/assets/docs/layout.doc.dense.mjs +130 -82
- package/assets/docs/layout.doc.mjs +133 -77
- package/assets/docs/migration.doc.mjs +19 -21
- package/assets/docs/motion.doc.mjs +16 -3
- package/assets/docs/principles.doc.dense.mjs +5 -5
- package/assets/docs/principles.doc.mjs +8 -0
- package/assets/docs/principles.doc.zh.mjs +6 -6
- package/assets/docs/shape.doc.mjs +8 -3
- package/assets/docs/spacing.doc.mjs +7 -2
- package/assets/docs/styling-libraries.doc.mjs +6 -2
- package/assets/docs/styling.doc.mjs +19 -23
- package/assets/docs/theme.doc.dense.mjs +58 -18
- package/assets/docs/theme.doc.mjs +57 -47
- package/assets/docs/theme.doc.zh.mjs +9 -8
- package/assets/docs/tokens.doc.dense.mjs +2 -2
- package/assets/docs/tokens.doc.mjs +389 -8
- package/assets/docs/tokens.doc.zh.mjs +2 -2
- package/assets/docs/tree/add-a-component.doc.mjs +75 -0
- package/assets/docs/tree/add-a-theme.doc.mjs +85 -0
- package/assets/docs/tree/add-a-topic.doc.mjs +144 -0
- package/assets/docs/tree/agent-guidance.doc.mjs +138 -0
- package/assets/docs/tree/block-template.doc.mjs +130 -0
- package/assets/docs/tree/build-the-template.doc.mjs +28 -0
- package/assets/docs/tree/building-blocks.doc.mjs +46 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +137 -0
- package/assets/docs/tree/checks.doc.mjs +119 -0
- package/assets/docs/tree/codemods.doc.mjs +147 -0
- package/assets/docs/tree/component-family.doc.mjs +113 -0
- package/assets/docs/tree/component-imports.doc.mjs +69 -0
- package/assets/docs/tree/component-lookups.doc.mjs +149 -0
- package/assets/docs/tree/components.doc.mjs +23 -0
- package/assets/docs/tree/configuration.doc.mjs +23 -0
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +182 -0
- package/assets/docs/tree/define-the-theme.doc.mjs +118 -0
- package/assets/docs/tree/describe-the-component.doc.mjs +57 -0
- package/assets/docs/tree/docs.doc.mjs +21 -0
- package/assets/docs/tree/document-the-template.doc.mjs +28 -0
- package/assets/docs/tree/document-the-theme.doc.mjs +68 -0
- package/assets/docs/tree/export-template-assets.doc.mjs +147 -0
- package/assets/docs/tree/extend-or-replace.doc.mjs +103 -0
- package/assets/docs/tree/fonts-and-assets.doc.mjs +106 -0
- package/assets/docs/tree/generate-a-palette.doc.mjs +66 -0
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +105 -0
- package/assets/docs/tree/help.doc.mjs +16 -0
- package/assets/docs/tree/integrations.doc.mjs +25 -451
- package/assets/docs/tree/links.doc.mjs +98 -0
- package/assets/docs/tree/package-and-test.doc.mjs +32 -0
- package/assets/docs/tree/page-template.doc.mjs +71 -0
- package/assets/docs/tree/publishing.doc.mjs +111 -0
- package/assets/docs/tree/quick-start.doc.mjs +272 -0
- package/assets/docs/tree/replace-a-core-component.doc.mjs +104 -0
- package/assets/docs/tree/replace-a-core-template.doc.mjs +172 -0
- package/assets/docs/tree/sections-and-placement.doc.mjs +108 -0
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +59 -0
- package/assets/docs/tree/ship.doc.mjs +16 -0
- package/assets/docs/tree/short-and-findable.doc.mjs +108 -0
- package/assets/docs/tree/single-component.doc.mjs +165 -0
- package/assets/docs/tree/start-a-template.doc.mjs +143 -0
- package/assets/docs/tree/subcomponent.doc.mjs +115 -0
- package/assets/docs/tree/template-assets.doc.mjs +64 -0
- package/assets/docs/tree/template-doc-overview.doc.mjs +109 -0
- package/assets/docs/tree/template-fonts.doc.mjs +102 -0
- package/assets/docs/tree/template-grading-rubric.doc.mjs +452 -0
- package/assets/docs/tree/template-icons.doc.mjs +97 -0
- package/assets/docs/tree/template-images-media.doc.mjs +127 -0
- package/assets/docs/tree/template-styles.doc.mjs +93 -0
- package/assets/docs/tree/templates.doc.mjs +34 -0
- package/assets/docs/tree/test-in-an-app.doc.mjs +115 -0
- package/assets/docs/tree/test-template-in-app.doc.mjs +128 -0
- package/assets/docs/tree/themes.doc.mjs +39 -0
- package/assets/docs/tree/troubleshooting.doc.mjs +149 -0
- package/assets/docs/tree/upgrading.doc.mjs +103 -0
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +51 -0
- package/assets/docs/tree/verify-packed-template.doc.mjs +77 -0
- package/assets/docs/tree/versioning.doc.mjs +161 -0
- package/assets/docs/tree/write-good-templates.doc.mjs +64 -0
- package/assets/docs/tree/write-the-template-file.doc.mjs +154 -0
- package/assets/docs/typography.doc.mjs +24 -4
- package/assets/docs/working-with-ai.doc.mjs +30 -22
- package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
- package/authoring/config/config.doc.mjs +9 -1
- package/authoring/config/parse.d.mts +2 -0
- package/authoring/config/parse.mjs +19 -0
- package/authoring/config/parse.test.mjs +8 -0
- package/authoring/config/type.ts +11 -0
- package/authoring/discover/discover.doc.d.mts +13 -0
- package/authoring/discover/discover.doc.mjs +138 -0
- package/authoring/discover/parse.d.mts +24 -0
- package/authoring/discover/parse.mjs +128 -0
- package/authoring/discover/parse.test.mjs +124 -0
- package/authoring/discover/type.ts +87 -0
- package/authoring/doctypes/_schema.d.mts +3 -2
- package/authoring/doctypes/_schema.mjs +6 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +3 -3
- package/authoring/doctypes/base/type.ts +4 -2
- package/authoring/doctypes/component/component.doc.mjs +6 -0
- package/authoring/doctypes/component/type.ts +8 -0
- package/authoring/doctypes/reference/reference.doc.mjs +7 -0
- package/authoring/doctypes/reference/type.ts +5 -0
- package/authoring/doctypes/schema/schema.doc.mjs +2 -2
- package/authoring/doctypes/template/template.doc.mjs +1 -1
- package/authoring/doctypes/template/type.ts +2 -2
- package/authoring/index.d.mts +1 -0
- package/authoring/index.d.ts +10 -0
- package/authoring/index.mjs +1 -0
- package/authoring/integration/integration.doc.mjs +12 -10
- package/clients/cli/commands/component/index.mjs +152 -55
- package/clients/cli/commands/component-batch.test.mjs +341 -0
- package/clients/cli/commands/component-ownership.test.mjs +89 -0
- package/clients/cli/commands/component.doc.mjs +27 -9
- package/clients/cli/commands/discover.doc.mjs +53 -9
- package/clients/cli/commands/discover.mjs +393 -118
- package/clients/cli/commands/discover.sources.test.mjs +267 -0
- package/clients/cli/commands/docs.doc.mjs +1 -1
- package/clients/cli/commands/docs.mjs +60 -17
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -2
- package/clients/cli/commands/doctor-integration.test.mjs +53 -0
- package/clients/cli/commands/doctor.doc.mjs +3 -1
- package/clients/cli/commands/doctor.mjs +49 -5
- package/clients/cli/commands/gap-report.doc.mjs +10 -9
- package/clients/cli/commands/init.doc.mjs +9 -6
- package/clients/cli/commands/integration-add.doc.mjs +9 -9
- package/clients/cli/commands/integration-authoring.test.mjs +61 -10
- package/clients/cli/commands/integration-pack.doc.mjs +5 -9
- package/clients/cli/commands/integration-real-world.test.mjs +1 -1
- package/clients/cli/commands/integration-verify.doc.mjs +22 -0
- package/clients/cli/commands/integration.doc.mjs +4 -4
- package/clients/cli/commands/integration.mjs +74 -43
- package/clients/cli/commands/manifest.doc.mjs +1 -1
- package/clients/cli/commands/search.doc.mjs +10 -3
- package/clients/cli/commands/search.mjs +21 -2
- package/clients/cli/commands/search.test.mjs +21 -4
- package/clients/cli/commands/swizzle.doc.mjs +1 -1
- package/clients/cli/commands/template.doc.mjs +1 -1
- package/clients/cli/commands/text-json-parity.test.mjs +7 -1
- package/clients/cli/commands/theme-add.doc.mjs +1 -1
- package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -2
- package/clients/cli/commands/theme-palette.doc.mjs +1 -2
- package/clients/cli/commands/theme-targets.doc.mjs +2 -2
- package/clients/cli/commands/theme.doc.mjs +2 -1
- package/clients/cli/commands/upgrade.doc.mjs +62 -3
- package/clients/cli/index.mjs +28 -6
- package/clients/cli/lib/define-command.mjs +28 -4
- package/clients/cli/lib/define-command.test.mjs +54 -0
- package/clients/cli/lib/exit-codes.test.mjs +17 -1
- package/clients/cli/lib/json-shim.mjs +24 -14
- package/clients/cli/lib/manifest.mjs +18 -5
- package/clients/cli/lib/manifest.test.mjs +5 -2
- package/clients/cli/lib/parse-error-format.test.mjs +81 -0
- package/foundation/agent-docs/agent-docs.mjs +1 -1
- package/foundation/discovery/authoring-self-docs.mjs +1 -0
- package/foundation/discovery/authoring-self-docs.test.mjs +6 -2
- package/foundation/discovery/cli-self-docs.mjs +16 -2
- package/foundation/discovery/cli-self-docs.test.mjs +20 -0
- package/foundation/discovery/docs-discovery.mjs +5 -1
- package/foundation/discovery/docs-discovery.test.mjs +21 -0
- package/foundation/discovery/docs-section-key.d.mts +1 -1
- package/foundation/discovery/docs-section-key.mjs +1 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +3 -2
- package/foundation/doc-compiler/inputs.test.mjs +0 -1
- package/foundation/doc-compiler/tree.d.mts +4 -0
- package/foundation/doc-compiler/tree.mjs +6 -1
- package/foundation/integrations/cli-requirement.d.mts +26 -6
- package/foundation/integrations/cli-requirement.mjs +46 -11
- package/foundation/integrations/cli-requirement.test.mjs +7 -2
- package/foundation/integrations/contribution-inventory.mjs +1 -1
- package/foundation/integrations/integrations.d.mts +14 -1
- package/foundation/integrations/integrations.mjs +41 -1
- package/foundation/integrations/integrations.test.mjs +31 -0
- package/foundation/response/batch.type.d.mts +33 -0
- package/foundation/response/batch.type.mjs +34 -0
- package/foundation/response/error-codes.doc.mjs +6 -8
- package/foundation/response/error-codes.test.mjs +30 -5
- package/foundation/response/response-types.doc.d.mts +4 -3
- package/foundation/response/response-types.doc.mjs +40 -10
- package/foundation/response/response-types.doc.test.mjs +23 -0
- package/foundation/response/response.doc.mjs +11 -10
- package/package.json +9 -9
- package/api/docs/docs.test.mjs +0 -243
- package/api/docs/integration-tree.test.mjs +0 -555
- package/api/docs/integrationDocs.test.mjs +0 -314
- package/api/search/search.test.mjs +0 -512
- package/assets/docs/tree/integrations.test.mjs +0 -62
- package/assets/docs/tree/writing-docs.doc.mjs +0 -286
- package/clients/cli/commands/docs.test.mjs +0 -294
- package/foundation/agent-docs/agent-docs.test.mjs +0 -1159
- package/foundation/doc-compiler/tree.test.mjs +0 -598
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Hosts the atomic staged-write transaction, package.json files-array
|
|
7
7
|
* maintenance, package-dir resolution, and project-path normalization that
|
|
8
|
-
* every `integration add <kind>` command needs.
|
|
8
|
+
* every `integration add <kind>` command needs. `theme add` and `theme build`
|
|
9
|
+
* write through the same transaction. Stateless and side-effect-free
|
|
9
10
|
* outside of {@link applyWrites}.
|
|
10
11
|
*/
|
|
11
12
|
|
|
@@ -40,7 +41,7 @@ export function findPackageDir(startDir) {
|
|
|
40
41
|
/**
|
|
41
42
|
* @typedef {object} WritePlan
|
|
42
43
|
* @property {string} path
|
|
43
|
-
* @property {string} contents
|
|
44
|
+
* @property {string | Buffer} contents a Buffer is written byte for byte
|
|
44
45
|
* @property {boolean} createOnly
|
|
45
46
|
* @property {Buffer} [expectedOriginal] bytes captured before validation;
|
|
46
47
|
* a different current file is a concurrent edit and must never be overwritten
|
|
@@ -192,21 +193,29 @@ export function packageJsonUpdate(
|
|
|
192
193
|
|
|
193
194
|
// ── Atomic staged-write transaction ─────────────────────────────────
|
|
194
195
|
|
|
195
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* @param {string} file
|
|
198
|
+
* @returns {boolean} false when the file is still there
|
|
199
|
+
*/
|
|
196
200
|
function removeTemporary(file) {
|
|
197
201
|
try {
|
|
198
202
|
fs.rmSync(file, {force: true});
|
|
203
|
+
return true;
|
|
199
204
|
} catch {
|
|
200
205
|
// Best effort. The transaction error remains the actionable failure.
|
|
206
|
+
return false;
|
|
201
207
|
}
|
|
202
208
|
}
|
|
203
209
|
|
|
204
210
|
/**
|
|
205
211
|
* Restore writes that already published. Best-effort so callers preserve
|
|
206
|
-
* the original actionable error.
|
|
212
|
+
* the original actionable error; returns every path it could not put back.
|
|
207
213
|
* @param {Array<WritePlan & {temporary: string, original: Buffer|null, mode: number}>} published
|
|
214
|
+
* @returns {string[]}
|
|
208
215
|
*/
|
|
209
216
|
function rollbackWrites(published) {
|
|
217
|
+
/** @type {string[]} */
|
|
218
|
+
const unrestored = [];
|
|
210
219
|
for (const plan of [...published].reverse()) {
|
|
211
220
|
let restore = null;
|
|
212
221
|
try {
|
|
@@ -225,12 +234,18 @@ function rollbackWrites(published) {
|
|
|
225
234
|
fs.renameSync(restore, plan.path);
|
|
226
235
|
restore = null;
|
|
227
236
|
}
|
|
228
|
-
} catch {
|
|
229
|
-
//
|
|
237
|
+
} catch (error) {
|
|
238
|
+
// A created file that is already gone needs nothing. Any other failure
|
|
239
|
+
// leaves this call's bytes, or no bytes, where the original was.
|
|
240
|
+
const gone =
|
|
241
|
+
error instanceof Error &&
|
|
242
|
+
/** @type {NodeJS.ErrnoException} */ (error).code === 'ENOENT';
|
|
243
|
+
if (!(gone && plan.original == null)) unrestored.push(plan.path);
|
|
230
244
|
} finally {
|
|
231
245
|
if (restore != null) removeTemporary(restore);
|
|
232
246
|
}
|
|
233
247
|
}
|
|
248
|
+
return unrestored;
|
|
234
249
|
}
|
|
235
250
|
|
|
236
251
|
/** @param {string} file */
|
|
@@ -325,10 +340,22 @@ export function applyWrites(plans) {
|
|
|
325
340
|
}
|
|
326
341
|
published.push(plan);
|
|
327
342
|
}
|
|
328
|
-
return () =>
|
|
343
|
+
return () => {
|
|
344
|
+
rollbackWrites(published);
|
|
345
|
+
};
|
|
329
346
|
} catch (error) {
|
|
330
|
-
|
|
331
|
-
|
|
347
|
+
const leftovers = staged
|
|
348
|
+
.filter(plan => !removeTemporary(plan.temporary))
|
|
349
|
+
.map(plan => plan.temporary);
|
|
350
|
+
const unrestored = rollbackWrites(published);
|
|
351
|
+
if (error instanceof Error) {
|
|
352
|
+
if (unrestored.length > 0) {
|
|
353
|
+
error.message += ` Could not restore: ${unrestored.join(', ')}.`;
|
|
354
|
+
}
|
|
355
|
+
if (leftovers.length > 0) {
|
|
356
|
+
error.message += ` Could not remove temporary files: ${leftovers.join(', ')}.`;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
332
359
|
throw error;
|
|
333
360
|
}
|
|
334
361
|
}
|
|
@@ -24,6 +24,10 @@ import {
|
|
|
24
24
|
import {loadManifestObject} from '../../foundation/integrations/integrations.mjs';
|
|
25
25
|
import {themeDescriptorSource} from '../../foundation/integrations/theme-descriptor.mjs';
|
|
26
26
|
import {assertContributionVisible} from '../../foundation/integrations/contribution-inventory.mjs';
|
|
27
|
+
import {
|
|
28
|
+
themesCliProblem,
|
|
29
|
+
withDocsTreeCli,
|
|
30
|
+
} from '../../foundation/integrations/cli-requirement.mjs';
|
|
27
31
|
import {
|
|
28
32
|
applyWrites,
|
|
29
33
|
findPackageDir,
|
|
@@ -233,11 +237,28 @@ export async function integrationAddTheme(name, options = {}) {
|
|
|
233
237
|
createOnly: true,
|
|
234
238
|
},
|
|
235
239
|
];
|
|
236
|
-
|
|
240
|
+
let packageUpdate = packageJsonUpdate(
|
|
237
241
|
packageFile,
|
|
238
242
|
rootPath,
|
|
239
243
|
path.basename(manifestFile),
|
|
240
244
|
);
|
|
245
|
+
// A CLI older than the one that reads typed theme descriptors rejects the
|
|
246
|
+
// themes root and withholds the package's themes and docs. Declare the CLI
|
|
247
|
+
// that reads them as a peer, so an older one is flagged at install instead.
|
|
248
|
+
{
|
|
249
|
+
const expectedOriginal =
|
|
250
|
+
packageUpdate?.expectedOriginal ?? fs.readFileSync(packageFile);
|
|
251
|
+
const text = packageUpdate?.contents ?? expectedOriginal.toString('utf-8');
|
|
252
|
+
const current = JSON.parse(text);
|
|
253
|
+
if (themesCliProblem(current) != null) {
|
|
254
|
+
packageUpdate = {
|
|
255
|
+
contents:
|
|
256
|
+
JSON.stringify(withDocsTreeCli(current), null, 2) +
|
|
257
|
+
(text.endsWith('\n') ? '\n' : ''),
|
|
258
|
+
expectedOriginal,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
241
262
|
if (packageUpdate != null) {
|
|
242
263
|
plans.push({
|
|
243
264
|
path: packageFile,
|
|
@@ -77,6 +77,40 @@ describe('integrationAddTheme', () => {
|
|
|
77
77
|
expect((await validateLocalIntegration(tmpDir)).issues).toEqual([]);
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
it('declares the optional CLI peer that reads typed theme descriptors', async () => {
|
|
81
|
+
setup({includeFiles: false});
|
|
82
|
+
const result = await integrationAddTheme('ocean', {cwd: tmpDir});
|
|
83
|
+
expect(result.data.files).toContain('package.json');
|
|
84
|
+
const pkg = JSON.parse(
|
|
85
|
+
fs.readFileSync(path.join(tmpDir, 'package.json'), 'utf-8'),
|
|
86
|
+
);
|
|
87
|
+
expect(pkg.peerDependencies).toEqual({'@astryxdesign/cli': '>=0.7.0'});
|
|
88
|
+
expect(pkg.peerDependenciesMeta).toEqual({
|
|
89
|
+
'@astryxdesign/cli': {optional: true},
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('keeps a CLI peer that already reads typed theme descriptors', async () => {
|
|
94
|
+
const pkg = {
|
|
95
|
+
name: '@acme/themes',
|
|
96
|
+
version: '1.0.0',
|
|
97
|
+
peerDependencies: {'@astryxdesign/cli': '^0.7.2'},
|
|
98
|
+
};
|
|
99
|
+
fs.writeFileSync(
|
|
100
|
+
path.join(tmpDir, 'package.json'),
|
|
101
|
+
`${JSON.stringify(pkg, null, 2)}\n`,
|
|
102
|
+
);
|
|
103
|
+
fs.writeFileSync(
|
|
104
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
105
|
+
'export default {};\n',
|
|
106
|
+
);
|
|
107
|
+
const result = await integrationAddTheme('ocean', {cwd: tmpDir});
|
|
108
|
+
expect(result.data.files).not.toContain('package.json');
|
|
109
|
+
expect(
|
|
110
|
+
JSON.parse(fs.readFileSync(path.join(tmpDir, 'package.json'), 'utf-8')),
|
|
111
|
+
).toEqual(pkg);
|
|
112
|
+
});
|
|
113
|
+
|
|
80
114
|
it('dry-runs the identical receipt without writing anything', async () => {
|
|
81
115
|
setup();
|
|
82
116
|
const beforeManifest = fs.readFileSync(
|
|
@@ -368,11 +368,11 @@ export async function integrationDocConflicts(pkg, options = {}) {
|
|
|
368
368
|
// and placed guides this package adds to the docs tree, and every link in
|
|
369
369
|
// its docs (spec:AST-046, spec:AST-047).
|
|
370
370
|
if (errors.length === 0) {
|
|
371
|
-
for (const message of await packageDocsProblems(
|
|
371
|
+
for (const {severity, message} of await packageDocsProblems(
|
|
372
372
|
/** @type {{name: string}} */ (resolved.integration),
|
|
373
373
|
discovered,
|
|
374
374
|
)) {
|
|
375
|
-
issues.push({code: 'invalid_doc_graph', severity
|
|
375
|
+
issues.push({code: 'invalid_doc_graph', severity, message});
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
// A reference block includes content rather than linking to it, so one
|
|
@@ -7,13 +7,13 @@ export const doc = {
|
|
|
7
7
|
name: 'integrationPackCheck',
|
|
8
8
|
namespace: 'cli/api',
|
|
9
9
|
displayName: 'integrationPackCheck()',
|
|
10
|
-
summary: '
|
|
10
|
+
summary: 'Check an integration package the way npm will publish it.',
|
|
11
11
|
description:
|
|
12
12
|
"Validates the local integration, runs the package lifecycle, packs with npm, checks required files against npm's authoritative tarball list, extracts the real tarball into a scratch consumer, compares local and packed contribution inventories, and resolves every packed component through its documented public import to verify that module exports the component. A package that ships a namespace doc or a placed guide, or has a template that sets `replaces`, fails unless its `@astryxdesign/cli` peer range admits only CLIs that read them.",
|
|
13
13
|
importPath: '@astryxdesign/cli/api',
|
|
14
14
|
signature:
|
|
15
15
|
'integrationPackCheck(options?: IntegrationPackCheckOptions): Promise<IntegrationPackCheckResponse>',
|
|
16
|
-
keywords: ['
|
|
16
|
+
keywords: ['verify', 'pack', 'publish', 'tarball', 'consumer'],
|
|
17
17
|
params: [
|
|
18
18
|
{
|
|
19
19
|
name: 'options.cwd',
|
|
@@ -31,6 +31,6 @@ export const doc = {
|
|
|
31
31
|
examples: [
|
|
32
32
|
{label: 'Check the local package', code: 'await integrationPackCheck();'},
|
|
33
33
|
],
|
|
34
|
-
command: 'integration
|
|
34
|
+
command: 'integration verify',
|
|
35
35
|
related: ['integrationAdd', 'validateIntegration'],
|
|
36
36
|
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import {integrationPackCheck} from './pack-check.mjs';
|
|
7
|
+
|
|
8
|
+
let tmpDir;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
tmpDir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-pack-check-'));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/** @param {Record<string, string>} scripts */
|
|
19
|
+
function writeThemePackage(scripts) {
|
|
20
|
+
fs.writeFileSync(
|
|
21
|
+
path.join(tmpDir, 'package.json'),
|
|
22
|
+
`${JSON.stringify(
|
|
23
|
+
{
|
|
24
|
+
name: '@acme/widgets',
|
|
25
|
+
version: '1.0.0',
|
|
26
|
+
files: ['astryx.integration.mjs', 'themes'],
|
|
27
|
+
peerDependencies: {'@astryxdesign/cli': '>=0.7.0'},
|
|
28
|
+
peerDependenciesMeta: {'@astryxdesign/cli': {optional: true}},
|
|
29
|
+
scripts,
|
|
30
|
+
},
|
|
31
|
+
null,
|
|
32
|
+
2,
|
|
33
|
+
)}\n`,
|
|
34
|
+
);
|
|
35
|
+
fs.writeFileSync(
|
|
36
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
37
|
+
"export default {themes: './themes'};\n",
|
|
38
|
+
);
|
|
39
|
+
const root = path.join(tmpDir, 'themes');
|
|
40
|
+
fs.mkdirSync(path.join(root, 'ocean'), {recursive: true});
|
|
41
|
+
fs.writeFileSync(
|
|
42
|
+
path.join(root, 'ocean', 'oceanTheme.ts'),
|
|
43
|
+
"import {defineTheme} from '@astryxdesign/core/theme';\n\nexport const oceanTheme = defineTheme({name: 'ocean'});\n",
|
|
44
|
+
);
|
|
45
|
+
fs.writeFileSync(
|
|
46
|
+
path.join(root, 'ocean', 'oceanTheme.doc.mjs'),
|
|
47
|
+
`/** @type {import('@astryxdesign/cli/authoring').ThemeDoc} */
|
|
48
|
+
export default {type: 'theme', name: 'ocean', displayName: 'Ocean', description: 'Ocean theme.', maintained: true};
|
|
49
|
+
`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('integrationPackCheck with lifecycle script output', () => {
|
|
54
|
+
it('checks the tarball when lifecycle scripts print to stdout', async () => {
|
|
55
|
+
writeThemePackage({
|
|
56
|
+
prepack: 'node -e "console.log(\'building the package\')"',
|
|
57
|
+
prepare: 'node -e "console.log(\'preparing\')"',
|
|
58
|
+
postpack: 'node -e "console.log(\'packed\')"',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
62
|
+
|
|
63
|
+
expect(result.data.issues).toEqual([]);
|
|
64
|
+
expect(result.data.packable).toBe(true);
|
|
65
|
+
expect(result.data.tarball?.filename).toBe('acme-widgets-1.0.0.tgz');
|
|
66
|
+
expect(result.data.contributions.packed).toEqual(
|
|
67
|
+
result.data.contributions.local,
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('still runs a chatty lifecycle script and compares what it packed', async () => {
|
|
72
|
+
const renameTheme = [
|
|
73
|
+
"console.log('renaming ocean to storm')",
|
|
74
|
+
"const fs=require('fs')",
|
|
75
|
+
"fs.renameSync('themes/ocean','themes/storm')",
|
|
76
|
+
"const p='themes/storm/oceanTheme.doc.mjs'",
|
|
77
|
+
"let x=fs.readFileSync(p,'utf8')",
|
|
78
|
+
"x=x.replace(/name: 'ocean'/, 'name: '+String.fromCharCode(39)+'storm'+String.fromCharCode(39))",
|
|
79
|
+
'fs.writeFileSync(p,x)',
|
|
80
|
+
].join(';');
|
|
81
|
+
writeThemePackage({prepack: `node -e "${renameTheme}"`});
|
|
82
|
+
|
|
83
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
84
|
+
const codes = result.data.issues.map(issue => issue.code);
|
|
85
|
+
|
|
86
|
+
expect(codes).not.toContain('pack_failed');
|
|
87
|
+
expect(codes).toContain('identity_not_packed');
|
|
88
|
+
expect(codes).toContain('packed_identity_unexpected');
|
|
89
|
+
expect(result.data.packable).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('keeps a failing lifecycle script output in the pack_failed issue', async () => {
|
|
93
|
+
writeThemePackage({
|
|
94
|
+
prepack:
|
|
95
|
+
'node -e "console.error(\'prepack exploded\'); process.exit(3)"',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
99
|
+
const failure = result.data.issues.find(
|
|
100
|
+
issue => issue.code === 'pack_failed',
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(result.data.packable).toBe(false);
|
|
104
|
+
expect(failure?.message).toContain('npm pack failed (exit 3)');
|
|
105
|
+
expect(failure?.message).toContain('prepack exploded');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file `astryx integration
|
|
4
|
+
* @file `astryx integration verify` — verify an integration package is
|
|
5
5
|
* ready to publish by cross-referencing its declared contributions against the
|
|
6
6
|
* real npm tarball.
|
|
7
7
|
*
|
|
@@ -30,8 +30,13 @@ import {resolvePackageDir} from '../../foundation/integrations/integrations.mjs'
|
|
|
30
30
|
import {
|
|
31
31
|
docsTreeCliProblem,
|
|
32
32
|
replacesCliProblem,
|
|
33
|
+
sectionIdsCliProblem,
|
|
34
|
+
themesCliProblem,
|
|
33
35
|
} from '../../foundation/integrations/cli-requirement.mjs';
|
|
34
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
discoverIntegrationDocs,
|
|
38
|
+
loadTopicModule,
|
|
39
|
+
} from '../../foundation/discovery/docs-discovery.mjs';
|
|
35
40
|
import {
|
|
36
41
|
discoverIntegrationComponents,
|
|
37
42
|
resolveIntegrationImportPath,
|
|
@@ -162,6 +167,22 @@ export function parseNpmPackOutput(output) {
|
|
|
162
167
|
};
|
|
163
168
|
}
|
|
164
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Summarize the JSON error npm prints on stdout when `--json` pack fails.
|
|
172
|
+
* @param {string} output
|
|
173
|
+
* @returns {string}
|
|
174
|
+
*/
|
|
175
|
+
function npmPackErrorDetail(output) {
|
|
176
|
+
try {
|
|
177
|
+
const error = JSON.parse(output)?.error;
|
|
178
|
+
return [error?.summary, error?.detail]
|
|
179
|
+
.filter(part => typeof part === 'string' && part.trim() !== '')
|
|
180
|
+
.join(': ');
|
|
181
|
+
} catch {
|
|
182
|
+
return (output || '').trim();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
165
186
|
/**
|
|
166
187
|
* Run `npm pack --json` with output directed to `destDir` so no preexisting
|
|
167
188
|
* tgz is overwritten. This intentionally runs the package lifecycle, matching
|
|
@@ -175,16 +196,25 @@ export function parseNpmPackOutput(output) {
|
|
|
175
196
|
function runNpmPack(packageDir, destDir) {
|
|
176
197
|
const result = spawnSync(
|
|
177
198
|
'npm',
|
|
178
|
-
[
|
|
199
|
+
[
|
|
200
|
+
'pack',
|
|
201
|
+
'--json',
|
|
202
|
+
'--silent',
|
|
203
|
+
// Lifecycle scripts still run; background mode keeps their output off
|
|
204
|
+
// the stdout that carries the JSON result.
|
|
205
|
+
'--foreground-scripts=false',
|
|
206
|
+
`--pack-destination=${destDir}`,
|
|
207
|
+
],
|
|
179
208
|
{cwd: packageDir, encoding: 'utf-8', timeout: 60_000},
|
|
180
209
|
);
|
|
181
210
|
if (result.error) {
|
|
182
211
|
throw new Error(`Could not start npm pack: ${result.error.message}`);
|
|
183
212
|
}
|
|
184
213
|
if (result.status !== 0) {
|
|
185
|
-
const
|
|
214
|
+
const detail =
|
|
215
|
+
(result.stderr || '').trim() || npmPackErrorDetail(result.stdout);
|
|
186
216
|
throw new Error(
|
|
187
|
-
`npm pack failed (exit ${result.status})${
|
|
217
|
+
`npm pack failed (exit ${result.status})${detail ? `: ${detail}` : ''}.`,
|
|
188
218
|
);
|
|
189
219
|
}
|
|
190
220
|
return parseNpmPackOutput(result.stdout);
|
|
@@ -308,6 +338,23 @@ function moduleExportsName(file, exportName, seen = new Set()) {
|
|
|
308
338
|
return found;
|
|
309
339
|
}
|
|
310
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Whether any of these doc files has a section that sets `id`.
|
|
343
|
+
* @param {string[]} files
|
|
344
|
+
* @returns {Promise<boolean>}
|
|
345
|
+
*/
|
|
346
|
+
async function setsSectionIds(files) {
|
|
347
|
+
for (const file of files) {
|
|
348
|
+
if (typeof file !== 'string') continue;
|
|
349
|
+
const doc = /** @type {any} */ (await loadTopicModule(file).catch(() => null));
|
|
350
|
+
const sections = Array.isArray(doc?.sections) ? doc.sections : [];
|
|
351
|
+
if (sections.some((/** @type {any} */ section) => section?.id != null)) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
|
|
311
358
|
/**
|
|
312
359
|
* Resolve package specifiers through Node's real ESM resolver from the scratch
|
|
313
360
|
* consumer. Resolution does not execute the target module, so source `.tsx`
|
|
@@ -600,19 +647,31 @@ export async function integrationPackCheck(options = {}) {
|
|
|
600
647
|
// (spec:AST-046 FR11): an older CLI can hide every doc topic the package
|
|
601
648
|
// ships, so the declared CLI range must admit only CLIs that read it.
|
|
602
649
|
if (loaded.docs) {
|
|
603
|
-
const {namespaces, guides} = await discoverIntegrationDocs(
|
|
604
|
-
|
|
605
|
-
);
|
|
650
|
+
const {records, namespaces, guides} = await discoverIntegrationDocs(
|
|
651
|
+
loaded,
|
|
652
|
+
).catch(() => ({records: [], namespaces: [], guides: []}));
|
|
606
653
|
const problem =
|
|
607
654
|
namespaces.length > 0 || guides.length > 0
|
|
608
655
|
? docsTreeCliProblem(pkg)
|
|
609
656
|
: null;
|
|
610
657
|
if (problem != null) {
|
|
611
658
|
issues.push(error('docs_tree_needs_cli', problem));
|
|
659
|
+
} else if (
|
|
660
|
+
await setsSectionIds([
|
|
661
|
+
...records.map(record => record.path),
|
|
662
|
+
...guides.map(guide => /** @type {any} */ (guide.ref).topicFile),
|
|
663
|
+
])
|
|
664
|
+
) {
|
|
665
|
+
// A section `id` is also a field an older CLI rejects, hiding the
|
|
666
|
+
// package's doc topics; the same peer range fixes both.
|
|
667
|
+
const idProblem = sectionIdsCliProblem(pkg);
|
|
668
|
+
if (idProblem != null) {
|
|
669
|
+
issues.push(error('section_ids_need_cli', idProblem));
|
|
670
|
+
}
|
|
612
671
|
}
|
|
613
672
|
}
|
|
614
673
|
// A template that sets `replaces` needs a CLI that reads the field
|
|
615
|
-
// (spec:AST-035): an older CLI
|
|
674
|
+
// (spec:AST-035): an older CLI drops that template and hides the package's docs.
|
|
616
675
|
if (loaded.templates) {
|
|
617
676
|
const found = await discoverIntegrationTemplatesForOne(loaded).catch(
|
|
618
677
|
() => ({templates: [], errors: []}),
|
|
@@ -625,6 +684,12 @@ export async function integrationPackCheck(options = {}) {
|
|
|
625
684
|
const problem = setsReplaces ? replacesCliProblem(pkg) : null;
|
|
626
685
|
if (problem != null) issues.push(error('replaces_needs_cli', problem));
|
|
627
686
|
}
|
|
687
|
+
// A theme needs a CLI that reads typed theme descriptors: an older CLI
|
|
688
|
+
// rejects the themes root and withholds the package's themes and docs.
|
|
689
|
+
if (localIdentities.themes.length > 0) {
|
|
690
|
+
const problem = themesCliProblem(pkg);
|
|
691
|
+
if (problem != null) issues.push(error('themes_need_cli', problem));
|
|
692
|
+
}
|
|
628
693
|
|
|
629
694
|
// Temp resources — always cleaned up
|
|
630
695
|
const tgzTmpDir = fs.mkdtempSync(
|
|
@@ -657,6 +722,14 @@ export async function integrationPackCheck(options = {}) {
|
|
|
657
722
|
// package's already-installed dependencies. The unique suffix makes
|
|
658
723
|
// concurrent checks independent.
|
|
659
724
|
scratchBase = fs.mkdtempSync(path.join(packageDir, '.astryx-pack-check-'));
|
|
725
|
+
// The consumer's own package.json makes it the package scope for its
|
|
726
|
+
// imports. Without one, Node resolves the package's name through the
|
|
727
|
+
// SOURCE package.json (self-reference), so an export target left out of
|
|
728
|
+
// the tarball would still resolve.
|
|
729
|
+
fs.writeFileSync(
|
|
730
|
+
path.join(scratchBase, 'package.json'),
|
|
731
|
+
`${JSON.stringify({name: 'astryx-verify-consumer', private: true})}\n`,
|
|
732
|
+
);
|
|
660
733
|
|
|
661
734
|
// Cross-reference file inventory vs pack list
|
|
662
735
|
if (!packResult.packedPaths.has(fileInv.manifest)) {
|
|
@@ -36,6 +36,8 @@ function writePackage({
|
|
|
36
36
|
scripts,
|
|
37
37
|
} = {}) {
|
|
38
38
|
const pkg = {name, version};
|
|
39
|
+
// A theme needs a CLI that reads typed theme descriptors.
|
|
40
|
+
if (themes) pkg.peerDependencies = {'@astryxdesign/cli': '>=0.7.0'};
|
|
39
41
|
if (files !== undefined) pkg.files = files;
|
|
40
42
|
if (scripts !== undefined) pkg.scripts = scripts;
|
|
41
43
|
fs.writeFileSync(
|
|
@@ -94,6 +96,30 @@ describe('integrationPackCheck', () => {
|
|
|
94
96
|
);
|
|
95
97
|
});
|
|
96
98
|
|
|
99
|
+
it('fails a package that ships a theme on a CLI range that cannot read it', async () => {
|
|
100
|
+
writePackage({files: ['astryx.integration.mjs', 'themes']});
|
|
101
|
+
const pkgFile = path.join(tmpDir, 'package.json');
|
|
102
|
+
const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));
|
|
103
|
+
delete pkg.peerDependencies;
|
|
104
|
+
fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
105
|
+
|
|
106
|
+
const missing = await integrationPackCheck({cwd: tmpDir});
|
|
107
|
+
expect(missing.data.packable).toBe(false);
|
|
108
|
+
expect(missing.data.issues).toContainEqual(
|
|
109
|
+
expect.objectContaining({
|
|
110
|
+
code: 'themes_need_cli',
|
|
111
|
+
message: expect.stringContaining('ships a theme'),
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
pkg.peerDependencies = {'@astryxdesign/cli': '^0.6.3'};
|
|
116
|
+
fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
117
|
+
const old = await integrationPackCheck({cwd: tmpDir});
|
|
118
|
+
expect(old.data.issues).toContainEqual(
|
|
119
|
+
expect.objectContaining({code: 'themes_need_cli'}),
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
97
123
|
it('fails when a theme entry omits its inferred runtime export', async () => {
|
|
98
124
|
writePackage({files: ['astryx.integration.mjs', 'themes']});
|
|
99
125
|
fs.writeFileSync(
|
|
@@ -193,6 +219,39 @@ describe('integrationPackCheck', () => {
|
|
|
193
219
|
);
|
|
194
220
|
});
|
|
195
221
|
|
|
222
|
+
it('resolves public imports in the packed package, not the source', async () => {
|
|
223
|
+
// The root export's target is left out of `files`: the source package
|
|
224
|
+
// resolves it, but an app that installs the tarball cannot.
|
|
225
|
+
fs.writeFileSync(
|
|
226
|
+
path.join(tmpDir, 'package.json'),
|
|
227
|
+
`${JSON.stringify({
|
|
228
|
+
name: '@acme/widgets',
|
|
229
|
+
version: '1.0.0',
|
|
230
|
+
files: ['astryx.integration.mjs'],
|
|
231
|
+
exports: {'.': './index.mjs'},
|
|
232
|
+
})}\n`,
|
|
233
|
+
);
|
|
234
|
+
fs.writeFileSync(
|
|
235
|
+
path.join(tmpDir, 'index.mjs'),
|
|
236
|
+
"export {AcmeWidget} from './components/AcmeWidget.tsx';\n",
|
|
237
|
+
);
|
|
238
|
+
await integrationAddComponent('AcmeWidget', {cwd: tmpDir});
|
|
239
|
+
const docFile = path.join(tmpDir, 'components', 'AcmeWidget.doc.mjs');
|
|
240
|
+
const doc = fs.readFileSync(docFile, 'utf-8');
|
|
241
|
+
expect(doc).toContain('@acme/widgets/components/AcmeWidget');
|
|
242
|
+
fs.writeFileSync(
|
|
243
|
+
docFile,
|
|
244
|
+
doc.replace('@acme/widgets/components/AcmeWidget', '@acme/widgets'),
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
248
|
+
|
|
249
|
+
expect(result.data.packable).toBe(false);
|
|
250
|
+
expect(result.data.issues).toContainEqual(
|
|
251
|
+
expect.objectContaining({code: 'component_export_missing'}),
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
|
|
196
255
|
it('fails when packed template source is hidden by package exports', async () => {
|
|
197
256
|
writePackage({
|
|
198
257
|
manifest: "export default {templates: './templates'};\n",
|
|
@@ -336,6 +395,37 @@ describe('integrationPackCheck', () => {
|
|
|
336
395
|
expect(await codes()).not.toContain('docs_tree_needs_cli');
|
|
337
396
|
}, 120_000);
|
|
338
397
|
|
|
398
|
+
it('fails a package whose doc section sets id on a CLI range that rejects the field', async () => {
|
|
399
|
+
writePackage({manifest: "export default {docs: './docs'};\n", themes: false});
|
|
400
|
+
fs.mkdirSync(path.join(tmpDir, 'docs'), {recursive: true});
|
|
401
|
+
const topic = (/** @type {string} */ section) =>
|
|
402
|
+
`export default {type: 'generic', name: 'notes', title: 'Notes', description: 'Notes.', sections: [${section}]};\n`;
|
|
403
|
+
const file = path.join(tmpDir, 'docs', 'notes.doc.mjs');
|
|
404
|
+
const codes = async () =>
|
|
405
|
+
(await integrationPackCheck({cwd: tmpDir})).data.issues.map(
|
|
406
|
+
(/** @type {{code: string}} */ issue) => issue.code,
|
|
407
|
+
);
|
|
408
|
+
fs.writeFileSync(
|
|
409
|
+
file,
|
|
410
|
+
topic("{title: 'Take notes', content: [{type: 'prose', text: 'Notes.'}]}"),
|
|
411
|
+
);
|
|
412
|
+
expect(await codes()).not.toContain('section_ids_need_cli');
|
|
413
|
+
// A fresh file name: the module loader caches a path once it is imported.
|
|
414
|
+
fs.rmSync(file);
|
|
415
|
+
fs.writeFileSync(
|
|
416
|
+
path.join(tmpDir, 'docs', 'notes-with-ids.doc.mjs'),
|
|
417
|
+
topic(
|
|
418
|
+
"{id: 'take-notes', title: 'Take notes', content: [{type: 'prose', text: 'Notes.'}]}",
|
|
419
|
+
),
|
|
420
|
+
);
|
|
421
|
+
expect(await codes()).toContain('section_ids_need_cli');
|
|
422
|
+
const pkgFile = path.join(tmpDir, 'package.json');
|
|
423
|
+
const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));
|
|
424
|
+
pkg.peerDependencies = {'@astryxdesign/cli': '>=0.7.0'};
|
|
425
|
+
fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
426
|
+
expect(await codes()).not.toContain('section_ids_need_cli');
|
|
427
|
+
}, 120_000);
|
|
428
|
+
|
|
339
429
|
it('fails a package with a template that sets replaces on a CLI range that rejects the field', async () => {
|
|
340
430
|
writePackage({manifest: "export default {templates: './templates'};\n", themes: false});
|
|
341
431
|
fs.mkdirSync(path.join(tmpDir, 'templates'), {recursive: true});
|
|
@@ -49,7 +49,7 @@ export const doc = {
|
|
|
49
49
|
throws: [
|
|
50
50
|
{
|
|
51
51
|
code: 'Error',
|
|
52
|
-
when: 'the CLI returned an error envelope (
|
|
52
|
+
when: 'a plain Error with no code: the CLI returned an error envelope (its message is rethrown; code and suggestions are dropped), or the response `type` is not expectedType',
|
|
53
53
|
},
|
|
54
54
|
],
|
|
55
55
|
examples: [
|
package/api/json/isError.doc.mjs
CHANGED
|
@@ -15,7 +15,7 @@ export const doc = {
|
|
|
15
15
|
displayName: 'isError()',
|
|
16
16
|
summary: 'Did the CLI return an error envelope?',
|
|
17
17
|
description:
|
|
18
|
-
'Tests a parsed response for an `error` key. Branch on this before touching `data
|
|
18
|
+
'Tests a parsed response for an `error` key. Branch on this before touching `data`, ' +
|
|
19
19
|
'and prefer the stable `code` field over matching the human-readable message, which is ' +
|
|
20
20
|
'not a contract. Note this returns a plain boolean, not a TypeScript type predicate, so ' +
|
|
21
21
|
'it does not narrow on its own: cast to the matching *Response type to get typed access.',
|