@astryxdesign/cli 0.6.4 → 0.6.5-canary.00f1ed9
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 +103 -95
- package/api/build/_adapter.d.mts +36 -2
- package/api/build/_adapter.mjs +41 -10
- package/api/build/build.doc.mjs +8 -3
- package/api/build/build.test.mjs +60 -2
- package/api/build/kit/kit.mjs +109 -26
- package/api/build/kit/rank.d.mts +24 -8
- package/api/build/kit/rank.mjs +277 -97
- package/api/build/kit/rank.test.mjs +231 -48
- 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/docs/docs.test.mjs +54 -18
- package/api/docs/integration-tree.test.mjs +17 -0
- package/api/docs/integrationDocs.test.mjs +27 -5
- 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/error.d.mts +22 -0
- package/api/error.mjs +42 -0
- 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-contribution.d.mts +2 -1
- package/api/integration/add-contribution.mjs +7 -3
- package/api/integration/add-contribution.test.mjs +3 -3
- package/api/integration/add-helpers.d.mts +5 -2
- package/api/integration/add-helpers.mjs +36 -9
- package/api/integration/add-theme.mjs +266 -23
- package/api/integration/add-theme.test.mjs +247 -0
- package/api/integration/authoring-checks.mjs +12 -9
- package/api/integration/authoring-checks.type.d.mts +8 -0
- package/api/integration/authoring-checks.type.mjs +7 -3
- package/api/integration/integration-authoring.type.d.mts +4 -1
- package/api/integration/integration-authoring.type.mjs +6 -1
- package/api/integration/integrationAdd.doc.mjs +6 -0
- package/api/integration/integrationAddTheme.doc.mjs +10 -0
- package/api/integration/integrationComponentConflicts.doc.mjs +1 -1
- package/api/integration/integrationDocConflicts.doc.mjs +1 -1
- package/api/integration/integrationPackCheck.doc.mjs +3 -3
- package/api/integration/integrationTemplateConflicts.doc.mjs +1 -1
- package/api/integration/pack-check.lifecycle-output.test.mjs +107 -0
- package/api/integration/pack-check.mjs +92 -10
- package/api/integration/pack-check.test.mjs +140 -1
- package/api/integration/pack-check.type.mjs +1 -1
- package/api/integration/validate-integration.d.mts +4 -2
- package/api/integration/validate-integration.mjs +7 -2
- package/api/integration/validate-integration.test.mjs +55 -0
- package/api/integration/validate-integration.type.d.mts +5 -0
- package/api/integration/validate-integration.type.mjs +5 -1
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/json/assertResponse.doc.mjs +1 -1
- package/api/json/isError.doc.mjs +1 -1
- package/api/layout/expand/expand.mjs +12 -7
- package/api/layout/expand/expand.receipt.test.mjs +74 -0
- package/api/layout/layout.type.d.mts +1 -0
- package/api/layout/layout.type.mjs +1 -0
- package/api/layout/layoutExpand.doc.mjs +1 -1
- package/api/search/search.d.mts +51 -1
- package/api/search/search.doc.mjs +2 -2
- package/api/search/search.mjs +299 -17
- package/api/search/search.test.mjs +216 -18
- package/api/swizzle/copy/copy.mjs +66 -3
- package/api/swizzle/swizzle.doc.mjs +11 -5
- package/api/template/copy/copy.mjs +15 -9
- package/api/template/copy/copy.receipt.test.mjs +77 -0
- package/api/template/copy/copy.test.mjs +9 -0
- package/api/template/show/show.mjs +15 -4
- package/api/template/show/show.test.mjs +76 -0
- package/api/template/template-integration.test.mjs +14 -0
- package/api/template/template.d.mts +1 -1
- package/api/template/template.doc.mjs +8 -3
- package/api/template/template.mjs +1 -0
- package/api/template/template.type.d.mts +2 -0
- package/api/template/template.type.mjs +2 -0
- 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.d.mts +24 -0
- package/api/theme/build/build.family.test.mjs +7 -12
- package/api/theme/build/build.mjs +243 -26
- package/api/theme/build/build.project-core.test.mjs +165 -0
- 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 +25 -6
- package/api/upgrade/run/run.test.mjs +45 -1
- package/api/upgrade/upgrade.doc.mjs +24 -22
- package/api/upgrade/upgrade.type.d.mts +1 -0
- package/api/upgrade/upgrade.type.mjs +3 -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 +3 -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 +121 -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 +153 -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 +162 -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/assets/templates/pages/ai-chat/template.doc.mjs +16 -1
- package/assets/templates/pages/ai-chat-landing/template.doc.mjs +9 -1
- package/assets/templates/pages/blank/template.doc.mjs +3 -1
- package/assets/templates/pages/canvas-editor/template.doc.mjs +9 -1
- package/assets/templates/pages/centered-hero/template.doc.mjs +3 -1
- package/assets/templates/pages/checkout-wizard/template.doc.mjs +1 -0
- package/assets/templates/pages/classic-gallery/template.doc.mjs +3 -1
- package/assets/templates/pages/contact-form/template.doc.mjs +16 -1
- package/assets/templates/pages/dashboard/template.doc.mjs +15 -1
- package/assets/templates/pages/dashboard-alert-rail/template.doc.mjs +23 -1
- package/assets/templates/pages/dashboard-cohort-funnel/template.doc.mjs +10 -1
- package/assets/templates/pages/dashboard-comparison/template.doc.mjs +9 -1
- package/assets/templates/pages/dashboard-composition/template.doc.mjs +10 -1
- package/assets/templates/pages/dashboard-progress/template.doc.mjs +17 -1
- package/assets/templates/pages/dashboard-scorecard/template.doc.mjs +2 -1
- package/assets/templates/pages/detail-page/template.doc.mjs +8 -0
- package/assets/templates/pages/documentation/template.doc.mjs +10 -1
- package/assets/templates/pages/documentation-design/template.doc.mjs +10 -1
- package/assets/templates/pages/documentation-technical/template.doc.mjs +10 -1
- package/assets/templates/pages/editor/template.doc.mjs +9 -1
- package/assets/templates/pages/file-explorer/template.doc.mjs +3 -1
- package/assets/templates/pages/form-two-column/template.doc.mjs +17 -1
- package/assets/templates/pages/form-wizard/template.doc.mjs +14 -1
- package/assets/templates/pages/form-wizard-dialog/template.doc.mjs +1 -0
- package/assets/templates/pages/gallery-hero/template.doc.mjs +10 -1
- package/assets/templates/pages/ide/template.doc.mjs +9 -1
- package/assets/templates/pages/incident-console/template.doc.mjs +10 -1
- package/assets/templates/pages/kanban-board/template.doc.mjs +11 -1
- package/assets/templates/pages/library/template.doc.mjs +15 -1
- package/assets/templates/pages/login/template.doc.mjs +10 -1
- package/assets/templates/pages/login-card/template.doc.mjs +11 -1
- package/assets/templates/pages/login-split/template.doc.mjs +10 -1
- package/assets/templates/pages/login-sso/template.doc.mjs +10 -1
- package/assets/templates/pages/messaging-shell/template.doc.mjs +11 -1
- package/assets/templates/pages/mixed-gallery/template.doc.mjs +10 -1
- package/assets/templates/pages/payment-form/template.doc.mjs +3 -1
- package/assets/templates/pages/product-detail/template.doc.mjs +9 -1
- package/assets/templates/pages/product-gallery/template.doc.mjs +10 -1
- package/assets/templates/pages/settings/template.doc.mjs +3 -1
- package/assets/templates/pages/settings-dialog/template.doc.mjs +9 -1
- package/assets/templates/pages/settings-sidebar/template.doc.mjs +9 -1
- package/assets/templates/pages/shell-nav/template.doc.mjs +10 -1
- package/assets/templates/pages/shell-side-nav/template.doc.mjs +14 -1
- package/assets/templates/pages/shell-top-nav/template.doc.mjs +11 -1
- package/assets/templates/pages/side-gallery/template.doc.mjs +3 -1
- package/assets/templates/pages/table/template.doc.mjs +11 -1
- package/assets/templates/pages/table-filter/template.doc.mjs +21 -1
- package/assets/templates/pages/table-grouped/template.doc.mjs +15 -1
- package/assets/templates/pages/table-inbox/template.doc.mjs +18 -6
- package/assets/templates/pages/table-page/template.doc.mjs +20 -1
- package/assets/templates/pages/table-tree/template.doc.mjs +14 -1
- package/assets/templates/pages/theme-showcase/template.doc.mjs +10 -1
- package/assets/templates/pages/work-item-detail/template.doc.mjs +10 -0
- package/assets/templates/themes/butter/icons.tsx +2 -0
- package/assets/templates/themes/chocolate/icons.tsx +2 -0
- package/assets/templates/themes/gothic/icons.tsx +2 -0
- package/assets/templates/themes/matcha/icons.tsx +2 -0
- package/assets/templates/themes/neutral/icons.tsx +2 -0
- package/assets/templates/themes/stone/icons.tsx +2 -0
- package/assets/templates/themes/y2k/icons.tsx +2 -0
- 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/parse.d.mts +2 -0
- package/authoring/doctypes/template/parse.mjs +1 -0
- package/authoring/doctypes/template/parse.test.mjs +21 -0
- package/authoring/doctypes/template/template.doc.mjs +7 -1
- package/authoring/doctypes/template/type.ts +12 -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/docs.test.mjs +113 -24
- 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 +53 -9
- 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 +17 -7
- package/clients/cli/commands/integration-authoring.test.mjs +70 -9
- 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 +76 -43
- package/clients/cli/commands/layout-expand.doc.mjs +3 -1
- package/clients/cli/commands/layout.expand-receipt.test.mjs +94 -0
- package/clients/cli/commands/layout.mjs +16 -0
- 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.copy-receipt.test.mjs +60 -0
- package/clients/cli/commands/template.doc.mjs +1 -1
- package/clients/cli/commands/template.mjs +19 -5
- package/clients/cli/commands/template.show-media.test.mjs +62 -0
- 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.ascii-output.test.mjs +14 -0
- package/clients/cli/commands/upgrade.doc.mjs +62 -3
- package/clients/cli/commands/write-failure.test.mjs +175 -0
- 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 +48 -156
- package/clients/cli/lib/manifest.test.mjs +103 -9
- package/clients/cli/lib/parse-error-format.test.mjs +81 -0
- package/foundation/agent-docs/agent-docs.mjs +1 -1
- package/foundation/agent-docs/agent-docs.test.mjs +3 -2
- 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/discovery/template-adapter.d.mts +14 -0
- package/foundation/discovery/template-adapter.fixture-refs.test.mjs +37 -1
- package/foundation/discovery/template-adapter.mjs +21 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +5 -4
- 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/doc-compiler/tree.test.mjs +65 -14
- package/foundation/integrations/cli-requirement.d.mts +75 -11
- package/foundation/integrations/cli-requirement.mjs +120 -23
- package/foundation/integrations/cli-requirement.test.mjs +141 -9
- 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 +5 -4
- package/foundation/response/response-types.doc.mjs +49 -19
- package/foundation/response/response-types.doc.test.mjs +23 -0
- package/foundation/response/response.doc.mjs +11 -10
- package/package.json +9 -9
- package/assets/docs/tree/integrations.test.mjs +0 -62
- package/assets/docs/tree/writing-docs.doc.mjs +0 -286
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Colocated tests for the template.show leaf — the source it returns is
|
|
5
|
+
* the source template.copy writes. An agent that prints a template and pastes
|
|
6
|
+
* it must end up with the same file as one that scaffolds it, demo media
|
|
7
|
+
* included (spec:AST-028 FR7).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {describe, it, expect, beforeAll, afterAll} from 'vitest';
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as os from 'node:os';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import {discoverAll, template} from '../template.mjs';
|
|
15
|
+
import {templateCopy} from '../copy/copy.mjs';
|
|
16
|
+
import {templateShow} from './show.mjs';
|
|
17
|
+
|
|
18
|
+
const SLOW = 60_000;
|
|
19
|
+
/** Astryx's demo-media namespace: only its own previews serve these files. */
|
|
20
|
+
const FIXTURE = '/template-assets/';
|
|
21
|
+
const FIXTURE_IMAGE = /\/template-assets\/[^'"`\s]+\.(png|jpe?g|webp)/i;
|
|
22
|
+
|
|
23
|
+
const read = (/** @type {string} */ file) => fs.readFileSync(file, 'utf-8');
|
|
24
|
+
|
|
25
|
+
describe('template.show', () => {
|
|
26
|
+
/** @type {string} */
|
|
27
|
+
let dir;
|
|
28
|
+
/** @type {Awaited<ReturnType<typeof discoverAll>>} */
|
|
29
|
+
let templates;
|
|
30
|
+
beforeAll(async () => {
|
|
31
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tmpl-show-'));
|
|
32
|
+
templates = await discoverAll(dir);
|
|
33
|
+
}, SLOW);
|
|
34
|
+
afterAll(() => fs.rmSync(dir, {recursive: true, force: true}));
|
|
35
|
+
|
|
36
|
+
it('returns exactly the source copy writes, for every template', () => {
|
|
37
|
+
let withFixtures = 0;
|
|
38
|
+
templates.forEach((match, index) => {
|
|
39
|
+
if (read(match.filePath).includes(FIXTURE)) withFixtures += 1;
|
|
40
|
+
const shown = templateShow(match).data.source;
|
|
41
|
+
const copied = templateCopy(match, {targetPath: `out-${index}`, cwd: dir});
|
|
42
|
+
const written = read(
|
|
43
|
+
path.join(dir, copied.data.outputDir, copied.data.fileName),
|
|
44
|
+
);
|
|
45
|
+
expect(shown, match.dirName).toBe(written);
|
|
46
|
+
});
|
|
47
|
+
// The templates with demo media are the ones that used to differ.
|
|
48
|
+
expect(withFixtures).toBeGreaterThan(0);
|
|
49
|
+
}, SLOW);
|
|
50
|
+
|
|
51
|
+
it('reports a replaced count when the source changed, and 0 when it did not', () => {
|
|
52
|
+
let reported = 0;
|
|
53
|
+
for (const match of templates) {
|
|
54
|
+
const raw = read(match.filePath);
|
|
55
|
+
const {source, demoMediaReplaced} = templateShow(match).data;
|
|
56
|
+
if (source === raw) expect(demoMediaReplaced, match.dirName).toBe(0);
|
|
57
|
+
else expect(demoMediaReplaced, match.dirName).toBeGreaterThan(0);
|
|
58
|
+
reported += demoMediaReplaced;
|
|
59
|
+
}
|
|
60
|
+
expect(reported).toBeGreaterThan(0);
|
|
61
|
+
}, SLOW);
|
|
62
|
+
|
|
63
|
+
it('prints a placeholder where a template shows a demo image', async () => {
|
|
64
|
+
const match = templates.find(
|
|
65
|
+
t => t.type === 'page' && FIXTURE_IMAGE.test(read(t.filePath)),
|
|
66
|
+
);
|
|
67
|
+
expect(match).toBeDefined();
|
|
68
|
+
if (!match) return;
|
|
69
|
+
const res = await template(match.dirName, {type: 'page', cwd: dir});
|
|
70
|
+
expect(res.type).toBe('template.show');
|
|
71
|
+
if (res.type !== 'template.show') return;
|
|
72
|
+
expect(res.data.source).not.toContain(FIXTURE);
|
|
73
|
+
expect(res.data.source).toContain('data:image/svg+xml,');
|
|
74
|
+
expect(res.data.demoMediaReplaced).toBeGreaterThan(0);
|
|
75
|
+
}, SLOW);
|
|
76
|
+
});
|
|
@@ -125,6 +125,20 @@ describe('integration template discovery', () => {
|
|
|
125
125
|
expect(entry.description).toBe('pricing desc');
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
it("reads an integration template's keywords in search and build", async () => {
|
|
129
|
+
const pkgDir = installWidgets(tmpDir);
|
|
130
|
+
writeTemplate(pkgDir, 'observatory', {
|
|
131
|
+
kind: 'page',
|
|
132
|
+
body: "export default {type: 'page', name: 'Observatory', description: 'Live tiles over a sky view.', category: 'Dashboard - Observatory', keywords: ['telescope', 'star map']};\n",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const found = await search('telescope', {cwd: tmpDir, type: 'template'});
|
|
136
|
+
expect(found.data.results.map(r => r.name)).toContain('observatory');
|
|
137
|
+
const kit = await build('telescope star map page', {cwd: tmpDir});
|
|
138
|
+
if (kit.type !== 'build.kit') throw new Error(kit.type);
|
|
139
|
+
expect(kit.data.start?.name).toBe('observatory');
|
|
140
|
+
});
|
|
141
|
+
|
|
128
142
|
it('preserves integration block showcase metadata in list output', async () => {
|
|
129
143
|
const pkgDir = installWidgets(tmpDir);
|
|
130
144
|
writeTemplate(pkgDir, 'chart-showcase', {
|
|
@@ -41,4 +41,4 @@ export function template(name?: string, options?: {
|
|
|
41
41
|
export type DiscoveredTemplate = import("../../foundation/discovery/template-adapter.mjs").DiscoveredTemplate;
|
|
42
42
|
export type TemplateDiscoveryError = import("../../foundation/discovery/template-adapter.mjs").TemplateDiscoveryError;
|
|
43
43
|
export type TemplateDocModule = import("../../foundation/discovery/template-adapter.mjs").TemplateDocModule;
|
|
44
|
-
export { discoverAll, discoverAll as discoverTemplates, discoverAllWithErrors, discoverCoreTemplates, discoverIntegrationTemplatesForOne, stripTemplateAssetRefs, listTemplates, findRelatedBlocks, findShowcase, extractComponents } from "../../foundation/discovery/template-adapter.mjs";
|
|
44
|
+
export { discoverAll, discoverAll as discoverTemplates, discoverAllWithErrors, discoverCoreTemplates, discoverIntegrationTemplatesForOne, stripTemplateAssetRefs, replaceDemoMedia, listTemplates, findRelatedBlocks, findShowcase, extractComponents } from "../../foundation/discovery/template-adapter.mjs";
|
|
@@ -102,6 +102,7 @@ export const doc = {
|
|
|
102
102
|
type: 'string',
|
|
103
103
|
description:
|
|
104
104
|
'Directory to discover templates and resolve the target path from.',
|
|
105
|
+
default: 'process.cwd()',
|
|
105
106
|
},
|
|
106
107
|
],
|
|
107
108
|
returns: [
|
|
@@ -113,7 +114,7 @@ export const doc = {
|
|
|
113
114
|
{
|
|
114
115
|
type: 'template.show',
|
|
115
116
|
description:
|
|
116
|
-
"The resolved template's
|
|
117
|
+
"The resolved template's source, exactly as a copy writes it, plus its description, kind, the component names it composes, and `demoMediaReplaced` — how many Astryx demo media references (images, posters, videos) in the source were replaced with placeholders for you to swap for your own media.",
|
|
117
118
|
},
|
|
118
119
|
{
|
|
119
120
|
type: 'template.skeleton',
|
|
@@ -123,7 +124,7 @@ export const doc = {
|
|
|
123
124
|
{
|
|
124
125
|
type: 'template.copy',
|
|
125
126
|
description:
|
|
126
|
-
'A receipt after scaffolding the template into the project: the template id, output directory, written file name, and file
|
|
127
|
+
'A receipt after scaffolding the template into the project: the template id, output directory, written file name, file count, and `demoMediaReplaced` — how many Astryx demo media references (images, posters, videos) in the written file were replaced with placeholders for you to swap for your own media.',
|
|
127
128
|
},
|
|
128
129
|
{
|
|
129
130
|
type: 'template.cdn',
|
|
@@ -134,7 +135,7 @@ export const doc = {
|
|
|
134
135
|
throws: [
|
|
135
136
|
{
|
|
136
137
|
code: 'ERR_UNKNOWN_TEMPLATE',
|
|
137
|
-
when: 'the named template does not exist, or
|
|
138
|
+
when: 'the named template does not exist, or options.skeleton is set without a name',
|
|
138
139
|
},
|
|
139
140
|
{
|
|
140
141
|
code: 'ERR_AMBIGUOUS_TEMPLATE',
|
|
@@ -152,6 +153,10 @@ export const doc = {
|
|
|
152
153
|
code: 'ERR_FILE_EXISTS',
|
|
153
154
|
when: 'the copy target already exists and overwrite is not set',
|
|
154
155
|
},
|
|
156
|
+
{
|
|
157
|
+
code: 'ERR_WRITE_FAILED',
|
|
158
|
+
when: 'the copy target could not be written (no permission, read-only mount, full disk); nothing is written',
|
|
159
|
+
},
|
|
155
160
|
],
|
|
156
161
|
examples: [
|
|
157
162
|
{label: 'List templates', code: 'const {data} = await template();'},
|
|
@@ -67,6 +67,7 @@ export type TemplateShowResponse = {
|
|
|
67
67
|
type: "page" | "block";
|
|
68
68
|
components: string[];
|
|
69
69
|
source: string;
|
|
70
|
+
demoMediaReplaced: number;
|
|
70
71
|
};
|
|
71
72
|
};
|
|
72
73
|
/**
|
|
@@ -91,6 +92,7 @@ export type TemplateCopyResponse = {
|
|
|
91
92
|
outputDir: string;
|
|
92
93
|
fileName: string;
|
|
93
94
|
filesCopied: number;
|
|
95
|
+
demoMediaReplaced: number;
|
|
94
96
|
};
|
|
95
97
|
};
|
|
96
98
|
/**
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
* @property {'page' | 'block'} data.type
|
|
54
54
|
* @property {string[]} data.components
|
|
55
55
|
* @property {string} data.source
|
|
56
|
+
* @property {number} data.demoMediaReplaced Astryx demo media references (images, posters, videos) replaced in the returned source: images with a neutral placeholder, videos with an empty source. Swap in your own media at those points; no media is installed. 0 when the template carried none.
|
|
56
57
|
*/
|
|
57
58
|
|
|
58
59
|
/**
|
|
@@ -75,6 +76,7 @@
|
|
|
75
76
|
* @property {string} data.outputDir
|
|
76
77
|
* @property {string} data.fileName
|
|
77
78
|
* @property {number} data.filesCopied
|
|
79
|
+
* @property {number} data.demoMediaReplaced Astryx demo media references (images, posters, videos) replaced in the written file: images with a neutral placeholder, videos with an empty source. Swap in your own media at those points; no media is installed. 0 when the template carried none.
|
|
78
80
|
*/
|
|
79
81
|
|
|
80
82
|
/**
|
package/api/theme/add/add.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import {AstryxError} from '../../error.mjs';
|
|
16
16
|
import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
|
|
17
17
|
import {listAvailableThemes, findTheme} from '../_adapter.mjs';
|
|
18
|
+
import {applyWrites} from '../../integration/add-helpers.mjs';
|
|
18
19
|
// Scaffolded files must not carry our repo boilerplate into a consumer's tree.
|
|
19
20
|
import {stripCopyrightHeader} from '../../../foundation/text/copyright-header.mjs';
|
|
20
21
|
|
|
@@ -136,37 +137,28 @@ export async function themeAdd(slug, options = {}) {
|
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
// a failure (e.g. an ancestor is a file → EEXIST/ENOTDIR) surfaces as
|
|
142
|
-
// stable ERR_WRITE_FAILED rather than leaking a raw fs errno + absolute path.
|
|
143
|
-
const staged = [];
|
|
140
|
+
// One transaction: every file publishes, or every replaced file gets its
|
|
141
|
+
// previous bytes back and every created file is removed. mkdir is inside the
|
|
142
|
+
// try so a failure (e.g. an ancestor is a file → EEXIST/ENOTDIR) surfaces as
|
|
143
|
+
// a stable ERR_WRITE_FAILED rather than leaking a raw fs errno + absolute path.
|
|
144
144
|
try {
|
|
145
145
|
fs.mkdirSync(resolvedDir, {recursive: true});
|
|
146
|
-
|
|
146
|
+
const plans = writes.map(w => {
|
|
147
|
+
fs.mkdirSync(path.dirname(w.dest), {recursive: true});
|
|
148
|
+
// Confine again once the directories exist: one may have been swapped
|
|
149
|
+
// for a link since the destination was first checked.
|
|
147
150
|
const dest = assertWithin(w.name, resolvedDir, {
|
|
148
151
|
label: `theme destination for ${w.name}`,
|
|
149
152
|
});
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
staged.push({tmp, dest});
|
|
158
|
-
}
|
|
159
|
-
for (const s of staged) {
|
|
160
|
-
fs.renameSync(s.tmp, s.dest);
|
|
161
|
-
}
|
|
153
|
+
return {
|
|
154
|
+
path: dest,
|
|
155
|
+
contents: scaffoldContents(fs.readFileSync(w.src)),
|
|
156
|
+
createOnly: !overwrite,
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
applyWrites(plans);
|
|
162
160
|
} catch (err) {
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
fs.rmSync(s.tmp, {force: true});
|
|
166
|
-
} catch {
|
|
167
|
-
/* best-effort */
|
|
168
|
-
}
|
|
169
|
-
}
|
|
161
|
+
if (err instanceof AstryxError) throw err;
|
|
170
162
|
if (err instanceof PathSafetyError) {
|
|
171
163
|
throw new AstryxError(
|
|
172
164
|
err.message,
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `theme add` writes a theme as one transaction: when a later file fails to
|
|
5
|
+
* publish, every file it already replaced gets its previous bytes back and
|
|
6
|
+
* every file it created is removed. Publishing is forced to fail by wrapping
|
|
7
|
+
* the two calls that publish a staged file: `linkSync` for a new file and
|
|
8
|
+
* `renameSync` for a replacement.
|
|
9
|
+
*
|
|
10
|
+
* Separate file because vi.mock is hoisted and affects the whole module.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
14
|
+
import * as os from 'node:os';
|
|
15
|
+
import * as path from 'node:path';
|
|
16
|
+
|
|
17
|
+
const failures = vi.hoisted(() => ({
|
|
18
|
+
/** Fail the Nth staged-file publish (1-based); 0 disables. */
|
|
19
|
+
publish: 0,
|
|
20
|
+
/** Also fail every rollback restore. */
|
|
21
|
+
restore: false,
|
|
22
|
+
count: 0,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('node:fs', async importOriginal => {
|
|
26
|
+
const actual = /** @type {typeof import('node:fs')} */ (
|
|
27
|
+
await importOriginal()
|
|
28
|
+
);
|
|
29
|
+
/** @param {string} source */
|
|
30
|
+
const isStaged = source => path.basename(String(source)).includes('.tmp');
|
|
31
|
+
/** @param {string} source */
|
|
32
|
+
const isRestore = source =>
|
|
33
|
+
path.basename(String(source)).includes('.restore-');
|
|
34
|
+
/** @param {string} source @param {string} op */
|
|
35
|
+
const maybeFail = (source, op) => {
|
|
36
|
+
if (isStaged(source) && failures.publish > 0) {
|
|
37
|
+
failures.count++;
|
|
38
|
+
if (failures.count === failures.publish) {
|
|
39
|
+
throw Object.assign(new Error(`EIO: forced ${op} failure`), {
|
|
40
|
+
code: 'EIO',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (isRestore(source) && failures.restore) {
|
|
45
|
+
throw Object.assign(new Error(`EIO: forced restore failure`), {
|
|
46
|
+
code: 'EIO',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
...actual,
|
|
52
|
+
linkSync: vi.fn((source, destination) => {
|
|
53
|
+
maybeFail(source, 'link');
|
|
54
|
+
return actual.linkSync(source, destination);
|
|
55
|
+
}),
|
|
56
|
+
renameSync: vi.fn((source, destination) => {
|
|
57
|
+
maybeFail(source, 'rename');
|
|
58
|
+
return actual.renameSync(source, destination);
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const fs = await import('node:fs');
|
|
64
|
+
const {themeAdd} = await import('./add.mjs');
|
|
65
|
+
const {listThemes} = await import('../_adapter.mjs');
|
|
66
|
+
|
|
67
|
+
let tmpDir;
|
|
68
|
+
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-themeadd-rollback-'));
|
|
71
|
+
failures.publish = 0;
|
|
72
|
+
failures.restore = false;
|
|
73
|
+
failures.count = 0;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
failures.publish = 0;
|
|
78
|
+
failures.restore = false;
|
|
79
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
function stoneFiles() {
|
|
83
|
+
const theme = listThemes().find(entry => entry.slug === 'stone');
|
|
84
|
+
if (!theme) throw new Error('missing bundled theme stone');
|
|
85
|
+
expect(theme.files.length).toBeGreaterThanOrEqual(2);
|
|
86
|
+
return theme.files.map(name =>
|
|
87
|
+
path.join(tmpDir, 'src', 'themes', 'stone', name),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Entries staging or rollback left behind in the theme directory. */
|
|
92
|
+
function strays() {
|
|
93
|
+
const dir = path.join(tmpDir, 'src', 'themes', 'stone');
|
|
94
|
+
if (!fs.existsSync(dir)) return [];
|
|
95
|
+
return fs
|
|
96
|
+
.readdirSync(dir, {recursive: true})
|
|
97
|
+
.map(String)
|
|
98
|
+
.filter(name => name.includes('.tmp') || name.includes('.restore-'));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
describe('themeAdd rolls back a partial write', () => {
|
|
102
|
+
it('removes every created file when the second publish fails', async () => {
|
|
103
|
+
const files = stoneFiles();
|
|
104
|
+
failures.publish = 2;
|
|
105
|
+
|
|
106
|
+
await expect(themeAdd('stone', {cwd: tmpDir})).rejects.toMatchObject({
|
|
107
|
+
code: 'ERR_WRITE_FAILED',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
for (const file of files) expect(fs.existsSync(file)).toBe(false);
|
|
111
|
+
expect(strays()).toEqual([]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('restores every replaced byte when the second publish fails', async () => {
|
|
115
|
+
const files = stoneFiles();
|
|
116
|
+
fs.mkdirSync(path.dirname(files[0]), {recursive: true});
|
|
117
|
+
const before = Buffer.from([0x00, 0xff, 0x0a, 0x62, 0x65, 0x66]);
|
|
118
|
+
fs.writeFileSync(files[0], before);
|
|
119
|
+
failures.publish = 2;
|
|
120
|
+
|
|
121
|
+
await expect(
|
|
122
|
+
themeAdd('stone', {cwd: tmpDir, overwrite: true}),
|
|
123
|
+
).rejects.toMatchObject({code: 'ERR_WRITE_FAILED'});
|
|
124
|
+
|
|
125
|
+
expect(fs.readFileSync(files[0]).equals(before)).toBe(true);
|
|
126
|
+
for (const file of files.slice(1)) expect(fs.existsSync(file)).toBe(false);
|
|
127
|
+
expect(strays()).toEqual([]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('names a file it could not restore', async () => {
|
|
131
|
+
const files = stoneFiles();
|
|
132
|
+
fs.mkdirSync(path.dirname(files[0]), {recursive: true});
|
|
133
|
+
fs.writeFileSync(files[0], 'before\n');
|
|
134
|
+
failures.publish = 2;
|
|
135
|
+
failures.restore = true;
|
|
136
|
+
|
|
137
|
+
let error;
|
|
138
|
+
try {
|
|
139
|
+
await themeAdd('stone', {cwd: tmpDir, overwrite: true});
|
|
140
|
+
} catch (caught) {
|
|
141
|
+
error = caught;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
expect(error?.code).toBe('ERR_WRITE_FAILED');
|
|
145
|
+
expect(error?.message).toContain('Could not restore');
|
|
146
|
+
expect(error?.message).toContain(files[0]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('writes every file when nothing fails', async () => {
|
|
150
|
+
const files = stoneFiles();
|
|
151
|
+
|
|
152
|
+
const result = await themeAdd('stone', {cwd: tmpDir});
|
|
153
|
+
|
|
154
|
+
expect(result.type).toBe('theme.add');
|
|
155
|
+
for (const file of files) expect(fs.existsSync(file)).toBe(true);
|
|
156
|
+
expect(strays()).toEqual([]);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -6,14 +6,15 @@ import * as os from 'node:os';
|
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import {themeAdd} from './add.mjs';
|
|
8
8
|
import {listThemes} from '../_adapter.mjs';
|
|
9
|
-
import {isErrorCode} from '../../../foundation/response/error-codes.mjs';
|
|
10
9
|
|
|
11
10
|
let tmpDir;
|
|
12
11
|
let outsideDir;
|
|
13
12
|
|
|
14
13
|
beforeEach(() => {
|
|
15
14
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-themeadd-staging-'));
|
|
16
|
-
outsideDir = fs.mkdtempSync(
|
|
15
|
+
outsideDir = fs.mkdtempSync(
|
|
16
|
+
path.join(os.tmpdir(), 'astryx-themeadd-outside-'),
|
|
17
|
+
);
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
afterEach(() => {
|
|
@@ -22,45 +23,61 @@ afterEach(() => {
|
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
|
-
*
|
|
26
|
+
* Where `theme add` writes the first file of `slug`.
|
|
26
27
|
* @param {string} slug
|
|
27
|
-
* @param {string} target
|
|
28
28
|
*/
|
|
29
|
-
function
|
|
29
|
+
function firstDestination(slug) {
|
|
30
30
|
const theme = listThemes().find(entry => entry.slug === slug);
|
|
31
31
|
if (!theme) throw new Error(`missing bundled theme ${slug}`);
|
|
32
|
-
const
|
|
33
|
-
const dest = path.join(tmpDir, 'src', 'themes', slug, first);
|
|
32
|
+
const dest = path.join(tmpDir, 'src', 'themes', slug, theme.files[0]);
|
|
34
33
|
fs.mkdirSync(path.dirname(dest), {recursive: true});
|
|
35
|
-
fs.symlinkSync(target, `${dest}.${process.pid}.tmp`);
|
|
36
34
|
return dest;
|
|
37
35
|
}
|
|
38
36
|
|
|
37
|
+
// Staging names are unpredictable and created exclusively, so an entry planted
|
|
38
|
+
// at a predictable name beside the destination is never written through.
|
|
39
39
|
describe('themeAdd staging writes stay inside the project', () => {
|
|
40
|
-
it('
|
|
40
|
+
it('never writes through a link planted at a predictable staging name', async () => {
|
|
41
41
|
const victim = path.join(outsideDir, 'victim.txt');
|
|
42
42
|
fs.writeFileSync(victim, 'outside\n');
|
|
43
|
-
const dest =
|
|
43
|
+
const dest = firstDestination('stone');
|
|
44
|
+
fs.symlinkSync(victim, `${dest}.${process.pid}.tmp`);
|
|
45
|
+
|
|
46
|
+
await themeAdd('stone', {cwd: tmpDir});
|
|
44
47
|
|
|
45
|
-
await expect(themeAdd('stone', {cwd: tmpDir})).rejects.toMatchObject({
|
|
46
|
-
code: 'ERR_PATH_TRAVERSAL',
|
|
47
|
-
});
|
|
48
48
|
expect(fs.readFileSync(victim, 'utf-8')).toBe('outside\n');
|
|
49
|
-
expect(fs.
|
|
49
|
+
expect(fs.lstatSync(dest).isFile()).toBe(true);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
it('never creates a file through a dangling staging
|
|
52
|
+
it('never creates a file through a dangling link at a predictable staging name', async () => {
|
|
53
53
|
const victim = path.join(outsideDir, 'created.txt');
|
|
54
|
-
|
|
54
|
+
fs.symlinkSync(victim, `${firstDestination('stone')}.${process.pid}.tmp`);
|
|
55
|
+
|
|
56
|
+
await themeAdd('stone', {cwd: tmpDir});
|
|
57
|
+
|
|
58
|
+
expect(fs.existsSync(victim)).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('refuses to replace a destination that links outside the project', async () => {
|
|
62
|
+
const victim = path.join(outsideDir, 'victim.txt');
|
|
63
|
+
fs.writeFileSync(victim, 'outside\n');
|
|
64
|
+
const dest = firstDestination('stone');
|
|
65
|
+
fs.symlinkSync(victim, dest);
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
await expect(
|
|
68
|
+
themeAdd('stone', {cwd: tmpDir, overwrite: true}),
|
|
69
|
+
).rejects.toMatchObject({code: 'ERR_PATH_TRAVERSAL'});
|
|
70
|
+
expect(fs.readFileSync(victim, 'utf-8')).toBe('outside\n');
|
|
71
|
+
expect(fs.lstatSync(dest).isSymbolicLink()).toBe(true);
|
|
72
|
+
});
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
it('never creates a file through a dangling destination link', async () => {
|
|
75
|
+
const victim = path.join(outsideDir, 'created.txt');
|
|
76
|
+
fs.symlinkSync(victim, firstDestination('stone'));
|
|
77
|
+
|
|
78
|
+
await expect(themeAdd('stone', {cwd: tmpDir})).rejects.toMatchObject({
|
|
79
|
+
code: 'ERR_PATH_TRAVERSAL',
|
|
80
|
+
});
|
|
64
81
|
expect(fs.existsSync(victim)).toBe(false);
|
|
65
82
|
});
|
|
66
83
|
});
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
2
|
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Why `theme build` has no Core to generate with, and the fix for where it
|
|
6
|
+
* ran: the Astryx repository builds Core, an app installs it, and an installed
|
|
7
|
+
* Core that will not load is named.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} cwd
|
|
10
|
+
* @param {any} importError - Why the import failed, when it threw.
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function coreUnavailableMessage(cwd: string, importError: any): string;
|
|
4
14
|
/**
|
|
5
15
|
* Import specifier for install/scaffold instructions. Drops a leading `src/`
|
|
6
16
|
* from the cwd-relative dir (most consumers import from a file under src/) but
|
|
@@ -106,3 +116,17 @@ export function themeBuildFamily(files: string[], options: {
|
|
|
106
116
|
* validateAdaptationEnrollment)
|
|
107
117
|
*/
|
|
108
118
|
export type ComponentValueClass = "builtin" | "enrollment" | "unresolved";
|
|
119
|
+
/**
|
|
120
|
+
* The bindings one Core provides: the namespaces interception spreads, the
|
|
121
|
+
* functions the build calls, and why the import failed, if it did.
|
|
122
|
+
*/
|
|
123
|
+
export type CoreBindings = {
|
|
124
|
+
themeModule: any;
|
|
125
|
+
rootModule: any;
|
|
126
|
+
importError: any;
|
|
127
|
+
defineTheme: any;
|
|
128
|
+
generateThemeRulesSplit: any;
|
|
129
|
+
generateOnMediaCSS: any;
|
|
130
|
+
generateAdaptationCSS: any;
|
|
131
|
+
dataTokenDefaults: any;
|
|
132
|
+
};
|
|
@@ -251,23 +251,18 @@ describe('themeBuildFamily()', () => {
|
|
|
251
251
|
const dir = makeDir();
|
|
252
252
|
const files = writeFamily(dir);
|
|
253
253
|
const outputDir = path.join(dir, 'themes');
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
);
|
|
258
|
-
fs.mkdirSync(blockedTmp);
|
|
254
|
+
// A directory where the JS output goes fails its staging after the CSS
|
|
255
|
+
// output was already staged.
|
|
256
|
+
fs.mkdirSync(path.join(outputDir, `${FAMILY_KEY}.js`));
|
|
259
257
|
|
|
260
258
|
await expect(build(dir, files)).rejects.toThrow(
|
|
261
259
|
/Failed to write theme outputs/,
|
|
262
260
|
);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
261
|
+
expect(fs.existsSync(path.join(outputDir, `${FAMILY_KEY}.css`))).toBe(false);
|
|
262
|
+
expect(fs.existsSync(path.join(outputDir, `${FAMILY_KEY}.d.ts`))).toBe(false);
|
|
266
263
|
expect(
|
|
267
|
-
fs.
|
|
268
|
-
|
|
269
|
-
),
|
|
270
|
-
).toBe(false);
|
|
264
|
+
fs.readdirSync(outputDir).filter(name => name.includes('.tmp-')),
|
|
265
|
+
).toEqual([]);
|
|
271
266
|
});
|
|
272
267
|
|
|
273
268
|
it('rejects invalid graphs and keys before touching an existing trio', async () => {
|