@astryxdesign/cli 0.6.5-canary.031021b → 0.6.5-canary.0353bb2
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/README.md +16 -15
- package/api/build/_adapter.d.mts +50 -2
- package/api/build/_adapter.mjs +98 -10
- package/api/build/build.doc.mjs +2 -2
- package/api/build/build.test.mjs +111 -2
- package/api/build/kit/kit.mjs +98 -22
- package/api/build/kit/rank.d.mts +32 -8
- package/api/build/kit/rank.mjs +291 -97
- package/api/build/kit/rank.test.mjs +231 -48
- package/api/build/kit/weights.d.mts +82 -0
- package/api/build/kit/weights.json +1 -0
- package/api/build/kit/weights.mjs +305 -0
- package/api/build/kit/weights.test.mjs +190 -0
- package/api/docs/docs.d.mts +6 -0
- package/api/docs/docs.depth.test.mjs +132 -0
- package/api/docs/docs.doc.mjs +31 -2
- package/api/docs/docs.mjs +44 -1
- package/api/docs/docs.test.mjs +279 -0
- package/api/docs/docs.type.d.mts +38 -1
- package/api/docs/docs.type.mjs +18 -1
- package/api/docs/integration-tree.test.mjs +572 -0
- package/api/docs/integrationDocs.test.mjs +336 -0
- package/api/docs/node/node.d.mts +28 -3
- package/api/docs/node/node.mjs +146 -23
- package/api/error.d.mts +22 -0
- package/api/error.mjs +42 -0
- package/api/gap-report/gap-report.d.mts +3 -1
- package/api/gap-report/gap-report.mjs +12 -2
- package/api/gap-report/gap-report.test.mjs +106 -0
- package/api/hook/list/list.d.mts +1 -1
- 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-theme.mjs +338 -26
- package/api/integration/add-theme.test.mjs +314 -1
- package/api/integration/authoring-checks.mjs +10 -7
- 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/integrationTemplateConflicts.doc.mjs +1 -1
- package/api/integration/pack-check.mjs +12 -3
- package/api/integration/pack-check.test.mjs +52 -3
- 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/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 +36 -5
- package/api/search/search.doc.mjs +4 -4
- package/api/search/search.mjs +194 -54
- package/api/search/search.test.mjs +825 -0
- package/api/search/search.type.d.mts +5 -5
- package/api/search/search.type.mjs +5 -5
- package/api/swizzle/copy/copy.mjs +66 -3
- package/api/swizzle/swizzle.doc.mjs +4 -0
- package/api/template/copy/copy.mjs +14 -8
- package/api/template/copy/copy.receipt.test.mjs +77 -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 +6 -2
- 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/build/build.d.mts +24 -0
- package/api/theme/build/build.icon-lineage.test.mjs +117 -0
- package/api/theme/build/build.icon-preservation.test.mjs +639 -0
- package/api/theme/build/build.mjs +381 -77
- package/api/theme/build/build.project-core.test.mjs +165 -0
- package/api/theme/build/build.test.mjs +190 -1
- package/api/theme/build/icon-imports.d.mts +47 -0
- package/api/theme/build/icon-imports.mjs +691 -0
- package/api/theme/themeBuild.doc.d.mts +4 -1
- package/api/theme/themeBuild.doc.mjs +14 -3
- package/api/upgrade/run/run.mjs +20 -3
- package/api/upgrade/run/run.test.mjs +45 -1
- package/api/upgrade/upgrade.doc.mjs +1 -1
- package/api/upgrade/upgrade.type.d.mts +1 -0
- package/api/upgrade/upgrade.type.mjs +1 -0
- package/assets/docs/icons.doc.mjs +1 -0
- package/assets/docs/theme.doc.mjs +2 -1
- package/assets/docs/tree/add-a-theme.doc.mjs +4 -0
- package/assets/docs/tree/add-a-topic.doc.mjs +1 -1
- package/assets/docs/tree/check-your-docs.doc.mjs +2 -2
- package/assets/docs/tree/checks.doc.mjs +1 -1
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +1 -1
- package/assets/docs/tree/define-the-theme.doc.mjs +1 -1
- package/assets/docs/tree/sections-and-placement.doc.mjs +1 -1
- package/assets/docs/tree/template-doc-overview.doc.mjs +13 -1
- package/assets/docs/tree/troubleshooting.doc.mjs +9 -5
- package/assets/docs/tree/versioning.doc.mjs +7 -6
- package/assets/templates/blocks/components/DropdownMenuGroup/DropdownMenuGroupShowcase.doc.mjs +15 -0
- package/assets/templates/blocks/components/DropdownMenuGroup/DropdownMenuGroupShowcase.tsx +38 -0
- package/assets/templates/blocks/components/DropdownMenuGroup/DropdownMenuGroupWithSelectable.doc.mjs +20 -0
- package/assets/templates/blocks/components/DropdownMenuGroup/DropdownMenuGroupWithSelectable.tsx +48 -0
- 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/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 +6 -0
- package/authoring/doctypes/template/type.ts +10 -0
- package/clients/cli/commands/build-theme.mjs +8 -2
- package/clients/cli/commands/discover.mjs +29 -1
- package/clients/cli/commands/discover.no-source.test.mjs +75 -0
- package/clients/cli/commands/docs.depth.test.mjs +219 -0
- package/clients/cli/commands/docs.doc.mjs +15 -2
- package/clients/cli/commands/docs.mjs +222 -5
- package/clients/cli/commands/docs.test.mjs +383 -0
- package/clients/cli/commands/doctor.mjs +4 -4
- package/clients/cli/commands/gap-report.mjs +12 -0
- package/clients/cli/commands/gap-report.test.mjs +63 -0
- package/clients/cli/commands/integration-add.doc.mjs +10 -0
- package/clients/cli/commands/integration-authoring.test.mjs +12 -2
- package/clients/cli/commands/integration.mjs +3 -1
- 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/search.doc.mjs +5 -4
- package/clients/cli/commands/search.mjs +30 -6
- package/clients/cli/commands/search.test.mjs +60 -16
- package/clients/cli/commands/template.copy-receipt.test.mjs +60 -0
- package/clients/cli/commands/template.mjs +19 -5
- package/clients/cli/commands/template.show-media.test.mjs +62 -0
- package/clients/cli/commands/theme-list.behavior.test.mjs +41 -0
- package/clients/cli/commands/upgrade.ascii-output.test.mjs +14 -0
- package/clients/cli/commands/write-failure.test.mjs +175 -0
- package/clients/cli/lib/manifest.mjs +44 -165
- package/clients/cli/lib/manifest.test.mjs +98 -7
- package/foundation/agent-docs/agent-docs.mjs +2 -1
- package/foundation/agent-docs/agent-docs.test.mjs +1168 -0
- 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/discovery/theme-discovery.d.mts +6 -0
- package/foundation/discovery/theme-discovery.mjs +9 -0
- package/foundation/doc-compiler/doc-loads.test.mjs +3 -3
- package/foundation/doc-compiler/tree.test.mjs +649 -0
- package/foundation/integrations/cli-requirement.d.mts +57 -13
- package/foundation/integrations/cli-requirement.mjs +84 -22
- package/foundation/integrations/cli-requirement.test.mjs +135 -8
- package/foundation/response/response-types.doc.d.mts +5 -5
- package/foundation/response/response-types.doc.mjs +13 -13
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ Results for "button" (20 of 239):
|
|
|
69
69
|
|
|
70
70
|
Options:
|
|
71
71
|
|
|
72
|
-
- `--type <component|hook|doc|template>`: restrict to a single domain
|
|
72
|
+
- `--type <component|hook|doc|template|theme>`: restrict to a single domain (`doc` and `theme` work outside an app too)
|
|
73
73
|
- `--limit <n>`: cap the number of results (default 20)
|
|
74
74
|
- `--verbose`: also print each result's match score and reason
|
|
75
75
|
- `--json`: typed `{ apiVersion, type: 'search', data: { query, matchCount, results } }` envelope — `matchCount` is how many candidates matched in total, `results` the slice `--limit` allowed
|
|
@@ -91,7 +91,7 @@ Options:
|
|
|
91
91
|
| `init` | Initialize the design system in your project |
|
|
92
92
|
| `integration` | Author and verify an Astryx integration package |
|
|
93
93
|
| `layout` | Generate XDS layouts from compressed expressions (XLE/XLO) |
|
|
94
|
-
| `search` | Search components, hooks, docs, and
|
|
94
|
+
| `search` | Search components, hooks, docs, templates, and themes in one ranked list |
|
|
95
95
|
| `swizzle` | Copy component source for customization |
|
|
96
96
|
| `template` | List, show, or scaffold page and block templates |
|
|
97
97
|
| `theme` | Create and build themes: add a shipped one, compile to CSS, or list what a theme can override |
|
|
@@ -303,11 +303,12 @@ Shape:
|
|
|
303
303
|
```
|
|
304
304
|
|
|
305
305
|
The manifest is **derived from Commander metadata** (commands, arguments, options)
|
|
306
|
-
so it can't drift from the real command definitions. The
|
|
307
|
-
|
|
308
|
-
the
|
|
309
|
-
|
|
310
|
-
|
|
306
|
+
so it can't drift from the real command definitions. The facts Commander doesn't
|
|
307
|
+
track come from each command's docs: examples from its CommandDoc, and emitted
|
|
308
|
+
response types from the returns of the FunctionDoc it wraps (plus the few
|
|
309
|
+
envelopes the CLI layer builds itself). `--json` support comes from the
|
|
310
|
+
`JSON_SUPPORTED` allowlist. Drift tests (`manifest.test.mjs`) fail CI when a
|
|
311
|
+
command is added without describing it.
|
|
311
312
|
|
|
312
313
|
**Backwards-compat:** the bare `astryx --json` envelope keeps `type: "help"` and its
|
|
313
314
|
original shallow fields (`name`, `version`, `commands` as a `string[]` of names,
|
|
@@ -450,9 +451,9 @@ Every response has a `type` discriminant. The full set is below (generated from
|
|
|
450
451
|
| `gap-report.categories` | The fixed gap category values and human-readable labels. |
|
|
451
452
|
| `gap-report.file` | An aggregate receipt: overall status, the selected package, issuesUrl (or null), deliveries in handler order, each {handlerType: project \| integration \| fallback, handler, audience, status, url, message}, and filedCount/routedOnlyCount totals. |
|
|
452
453
|
| `template.list` | The effective discovered TemplateListEntry[] for pages and blocks. A winning replacement entry includes optional `replaces`, naming the Core id omitted from the default list. |
|
|
453
|
-
| `template.show` | The resolved template's
|
|
454
|
+
| `template.show` | 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 were replaced with placeholders for you to swap for your own media). |
|
|
454
455
|
| `template.skeleton` | A layout skeleton (structural tags with spatial annotations) plus the template's description and the components it composes. |
|
|
455
|
-
| `template.copy` | A scaffold receipt: template id, output directory, written file name, and
|
|
456
|
+
| `template.copy` | A scaffold receipt: template id, output directory, written file name, file count, and demoMediaReplaced (how many Astryx demo media references were replaced with placeholders for you to swap for your own media). |
|
|
456
457
|
| `template.cdn` | A write receipt for the no-build-step CDN starter page: the path (relative to cwd), the Astryx version every CDN URL was pinned to, whether it was written, and the reason it was not. `exists` when a file was already there, which is a success. |
|
|
457
458
|
| `hook.list` | The hook catalog grouped by category: `detail` (the level: names \| compact \| full) and `components`, the grouped map of hook names, brief entries, or a full HookDoc per entry. |
|
|
458
459
|
| `hook.detail` | One hook's full authored HookDoc. |
|
|
@@ -468,18 +469,18 @@ Every response has a `type` discriminant. The full set is below (generated from
|
|
|
468
469
|
| `upgrade.list` | Every available codemod, oldest→newest, as {name, title, version, optional}; returned for --list without running anything. |
|
|
469
470
|
| `upgrade.registry` | The copied-composition receipt for --registry: applied, ok, the counts (found, current, wouldUpdate, updated, wouldMerge, merged, wouldRefreshReceipt, receiptsRefreshed, conflicts, missing, invalid, failed), and items. |
|
|
470
471
|
| `upgrade.status` | A short-circuit outcome with no codemods run (up_to_date, no_codemods, or config_fixable), each carrying the agent-docs summary. |
|
|
471
|
-
| `upgrade.run` | The run receipt: from/to versions, codemod count, integrations processed, the agent-docs summary, and (apply mode) filesChanged, transformsApplied, and per-codemod errors.
|
|
472
|
+
| `upgrade.run` | The run receipt: from/to versions, codemod count, integrations processed, the agent-docs summary, sourcePathFound (false when the resolved source directory does not exist, so no source file was read), and (apply mode) filesChanged, transformsApplied, and per-codemod errors. |
|
|
472
473
|
| `manifest` | The CLI capability manifest: name, version, apiVersion, description, globalOptions, commands (each name, description, arguments, options, json, aliases?, responseTypes?, examples?, exitCodes? as [{code, when}], subcommands?), jsonSupported, and the flat responseTypes index. |
|
|
473
474
|
| `help` | Help, in one of two shapes. A bare `astryx --json` returns the root manifest: name, version, commands (the command names), jsonSupported, and manifest (the full payload `astryx manifest --json` returns). `--help --json` on any command, or `astryx help [command] --json`, returns that command's help: command, description, usage, options (each flags, description, and defaultValue and choices when set), and subcommands (each name and description). data.manifest marks the first shape; data.usage marks the second. |
|
|
474
475
|
| `version` | The CLI version, for `astryx --version --json`: {version}. |
|
|
475
476
|
| `doctor` | The health-check report: `checks` (each with id, label, status: pass \| warn \| fail \| info, a message, and an optional fix, always present on warn and fail) plus a `summary` of counts per status. |
|
|
476
477
|
| `integration.add` | A contribution-writer receipt: kind, name, optional root {path, created}, integration-manifest path, every affected project-relative path, written, and dryRun. |
|
|
477
478
|
| `integration.pack-check` | The packed-package check: name, version, packable, tarball {filename, fileCount, size, unpackedSize} or null, inventory {manifest, roots [{kind, path, expectedFiles, missingFiles, complete}], expectedFiles, packedFiles}, contributions {local, packed}, each null or {themes [{slug, exportName}], components, templates [{id, type, name}], codemods [{version, id}], docs, agentDocsAppend}, and issues [{code, severity, message}]. |
|
|
478
|
-
| `integration.validate` | The validation result: the package name and version (both null when
|
|
479
|
-
| `integration.template-conflicts` |
|
|
480
|
-
| `integration.component-conflicts` |
|
|
481
|
-
| `integration.doc-conflicts` |
|
|
482
|
-
| `layout.expand` | The expansion: parsed form, generated TSX code, componentsUsed, states (count of useState hooks scaffolded), todos, blocksReferenced (each {name, mode}), warnings,
|
|
479
|
+
| `integration.validate` | The validation result: validated (false when no integration manifest was found, so nothing was checked and the empty issues list proves nothing), the package name and version (both null when validated is false) plus issues, an AstryxIntegrationIssue[] of {code, severity: warning \| error, message}. |
|
|
480
|
+
| `integration.template-conflicts` | validated (false when no integration manifest was found, so nothing was inspected), the integration identity, structural issues, and non-blocking Core template-id conflicts as {id, severity: warning, integrationPackage, integrationType, integrationName, coreMatches, message, command}. |
|
|
481
|
+
| `integration.component-conflicts` | validated (false when no integration manifest was found, so nothing was inspected), the integration identity, structural issues, and non-blocking conflicts where an integration component name is also owned by Core; each conflict includes the exact package-qualified command. |
|
|
482
|
+
| `integration.doc-conflicts` | validated (false when no integration manifest was found, so nothing was inspected), the integration identity, structural issues, and Core doc overlaps. Each finding includes `severity` (`info` \| `error`) and `relationship` (`replaces` \| `extends` \| `accidental`). |
|
|
483
|
+
| `layout.expand` | The expansion: parsed form, generated TSX code, componentsUsed, states (count of useState hooks scaffolded), todos, blocksReferenced (each {name, mode}), warnings, written (the output path, or null when nothing was written), and demoMediaReplaced (count of demo media placeholders). |
|
|
483
484
|
| `layout.check` | The validation result: a valid flag, the detected form, errors (each with line/col, message, formatted text, and suggestions), warnings, and the expression re-printed in both canonical surfaces (compact and outline). |
|
|
484
485
|
| `layout.grammar` | The XLE/XLO grammar cheatsheet: a text field with the full reference plus an aliases map (short name → canonical component) generated from this install's registry. |
|
|
485
486
|
|
package/api/build/_adapter.d.mts
CHANGED
|
@@ -7,8 +7,15 @@
|
|
|
7
7
|
* @property {string} name The template's own id, as search reports it.
|
|
8
8
|
* @property {string} command `astryx template <id> --type page`, the command that selects exactly this template: an integration replacement is selected by the Core id it replaces, and `--type page` keeps a block with the same id from making it ambiguous. Search prints template commands the same way.
|
|
9
9
|
* @property {string} displayName Human-facing name.
|
|
10
|
-
* @property {string} description What the page is
|
|
10
|
+
* @property {string} description What the page is and how it is laid out.
|
|
11
11
|
* @property {string} category The template's own `Family - Variant` label; empty when it declares none.
|
|
12
|
+
* @property {string[]} keywords The ideas the page serves, as its own descriptor names them; empty when it declares none.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* A component the project can use, as the ranker reads it.
|
|
16
|
+
* @typedef {object} ComponentWords
|
|
17
|
+
* @property {string} name The component's name, e.g. `DateRangeInput`.
|
|
18
|
+
* @property {string[]} keywords The keywords its own doc declares.
|
|
12
19
|
*/
|
|
13
20
|
/**
|
|
14
21
|
* Every ready page template the project can scaffold, in discovery order. This
|
|
@@ -23,6 +30,30 @@
|
|
|
23
30
|
* @returns {Promise<PageTemplate[]>}
|
|
24
31
|
*/
|
|
25
32
|
export function loadPageTemplates(cwd: string): Promise<PageTemplate[]>;
|
|
33
|
+
/**
|
|
34
|
+
* The components the project can use, Core's and its integrations', each with
|
|
35
|
+
* the keywords its own doc declares: what the ranker reads to tell a part of a
|
|
36
|
+
* page from a page. Search's own discovery, so both agree on what exists; empty
|
|
37
|
+
* when Core cannot be found.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} cwd
|
|
40
|
+
* @returns {Promise<ComponentWords[]>}
|
|
41
|
+
*/
|
|
42
|
+
export function loadComponents(cwd: string): Promise<ComponentWords[]>;
|
|
43
|
+
/**
|
|
44
|
+
* The matcher weights checked in beside the kit (`kit/weights.json`), read
|
|
45
|
+
* once; null when the file is absent or unreadable.
|
|
46
|
+
* @returns {import('./kit/weights.mjs').WeightsFile | null}
|
|
47
|
+
*/
|
|
48
|
+
export function loadWeights(): import("./kit/weights.mjs").WeightsFile | null;
|
|
49
|
+
/**
|
|
50
|
+
* Whether a parsed weights file has the shape the kit reads: every row has a
|
|
51
|
+
* weight per candidate, every bias a number per candidate, and three blend
|
|
52
|
+
* numbers per member (the tables plus the ranker) and one for the shell.
|
|
53
|
+
* @param {any} file
|
|
54
|
+
* @returns {file is import('./kit/weights.mjs').WeightsFile}
|
|
55
|
+
*/
|
|
56
|
+
export function isWeightsFile(file: any): file is import("./kit/weights.mjs").WeightsFile;
|
|
26
57
|
/**
|
|
27
58
|
* A page template the kit can recommend starting from.
|
|
28
59
|
*/
|
|
@@ -40,11 +71,28 @@ export type PageTemplate = {
|
|
|
40
71
|
*/
|
|
41
72
|
displayName: string;
|
|
42
73
|
/**
|
|
43
|
-
* What the page is
|
|
74
|
+
* What the page is and how it is laid out.
|
|
44
75
|
*/
|
|
45
76
|
description: string;
|
|
46
77
|
/**
|
|
47
78
|
* The template's own `Family - Variant` label; empty when it declares none.
|
|
48
79
|
*/
|
|
49
80
|
category: string;
|
|
81
|
+
/**
|
|
82
|
+
* The ideas the page serves, as its own descriptor names them; empty when it declares none.
|
|
83
|
+
*/
|
|
84
|
+
keywords: string[];
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* A component the project can use, as the ranker reads it.
|
|
88
|
+
*/
|
|
89
|
+
export type ComponentWords = {
|
|
90
|
+
/**
|
|
91
|
+
* The component's name, e.g. `DateRangeInput`.
|
|
92
|
+
*/
|
|
93
|
+
name: string;
|
|
94
|
+
/**
|
|
95
|
+
* The keywords its own doc declares.
|
|
96
|
+
*/
|
|
97
|
+
keywords: string[];
|
|
50
98
|
};
|
package/api/build/_adapter.mjs
CHANGED
|
@@ -2,20 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file The build subject's environment access: the page templates a project
|
|
5
|
-
* can scaffold.
|
|
5
|
+
* can scaffold, the components it can use, and the checked-in matcher weights.
|
|
6
6
|
*
|
|
7
|
-
* @input Template discovery for `cwd` — the CLI's own templates
|
|
8
|
-
* the project's configured integrations
|
|
7
|
+
* @input Template and component discovery for `cwd` — the CLI's own templates
|
|
8
|
+
* and Core's components, plus any that the project's configured integrations
|
|
9
|
+
* contribute.
|
|
9
10
|
* @output Ready page templates as `{name, displayName, description, category,
|
|
10
|
-
* command}`, where `command` is the `astryx template` command that
|
|
11
|
-
* exactly that template
|
|
12
|
-
* @position Beside build.mjs (api/build/). The kit leaf reads templates
|
|
13
|
-
* through here, because a subject's `_adapter.mjs` is its
|
|
14
|
-
* access.
|
|
15
|
-
*
|
|
11
|
+
* keywords, command}`, where `command` is the `astryx template` command that
|
|
12
|
+
* selects exactly that template; components as `{name, keywords}`.
|
|
13
|
+
* @position Beside build.mjs (api/build/). The kit leaf reads templates and
|
|
14
|
+
* components only through here, because a subject's `_adapter.mjs` is its
|
|
15
|
+
* only environment access. This adds no discovery of its own: templates come
|
|
16
|
+
* from the template subject's, components from search's.
|
|
16
17
|
*/
|
|
17
18
|
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
|
|
18
21
|
import {discoverTemplates} from '../template/template.mjs';
|
|
22
|
+
import {componentKeywords} from '../search/search.mjs';
|
|
23
|
+
import {findCoreDir} from '../../foundation/fs/paths.mjs';
|
|
19
24
|
|
|
20
25
|
/**
|
|
21
26
|
* A page template the kit can recommend starting from.
|
|
@@ -23,8 +28,16 @@ import {discoverTemplates} from '../template/template.mjs';
|
|
|
23
28
|
* @property {string} name The template's own id, as search reports it.
|
|
24
29
|
* @property {string} command `astryx template <id> --type page`, the command that selects exactly this template: an integration replacement is selected by the Core id it replaces, and `--type page` keeps a block with the same id from making it ambiguous. Search prints template commands the same way.
|
|
25
30
|
* @property {string} displayName Human-facing name.
|
|
26
|
-
* @property {string} description What the page is
|
|
31
|
+
* @property {string} description What the page is and how it is laid out.
|
|
27
32
|
* @property {string} category The template's own `Family - Variant` label; empty when it declares none.
|
|
33
|
+
* @property {string[]} keywords The ideas the page serves, as its own descriptor names them; empty when it declares none.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A component the project can use, as the ranker reads it.
|
|
38
|
+
* @typedef {object} ComponentWords
|
|
39
|
+
* @property {string} name The component's name, e.g. `DateRangeInput`.
|
|
40
|
+
* @property {string[]} keywords The keywords its own doc declares.
|
|
28
41
|
*/
|
|
29
42
|
|
|
30
43
|
/**
|
|
@@ -53,8 +66,83 @@ export async function loadPageTemplates(cwd) {
|
|
|
53
66
|
displayName: t.displayName || t.name,
|
|
54
67
|
description: t.description || '',
|
|
55
68
|
category: t.category || '',
|
|
69
|
+
keywords: t.keywords ?? [],
|
|
56
70
|
// The id `template()` resolves back to this entry: an active replacement
|
|
57
71
|
// owns the Core id it names, so that id selects it, not its own.
|
|
58
72
|
command: `astryx template ${t.replaces ?? t.dirName} --type page`,
|
|
59
73
|
}));
|
|
60
74
|
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The components the project can use, Core's and its integrations', each with
|
|
78
|
+
* the keywords its own doc declares: what the ranker reads to tell a part of a
|
|
79
|
+
* page from a page. Search's own discovery, so both agree on what exists; empty
|
|
80
|
+
* when Core cannot be found.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} cwd
|
|
83
|
+
* @returns {Promise<ComponentWords[]>}
|
|
84
|
+
*/
|
|
85
|
+
export async function loadComponents(cwd) {
|
|
86
|
+
const coreDir = findCoreDir(cwd);
|
|
87
|
+
if (!coreDir) return [];
|
|
88
|
+
try {
|
|
89
|
+
return await componentKeywords(coreDir, cwd);
|
|
90
|
+
} catch {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** @type {import('./kit/weights.mjs').WeightsFile | null | undefined} */
|
|
96
|
+
let weights;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The matcher weights checked in beside the kit (`kit/weights.json`), read
|
|
100
|
+
* once; null when the file is absent or unreadable.
|
|
101
|
+
* @returns {import('./kit/weights.mjs').WeightsFile | null}
|
|
102
|
+
*/
|
|
103
|
+
export function loadWeights() {
|
|
104
|
+
if (weights === undefined) {
|
|
105
|
+
try {
|
|
106
|
+
const file = JSON.parse(
|
|
107
|
+
fs.readFileSync(new URL('./kit/weights.json', import.meta.url), 'utf8'),
|
|
108
|
+
);
|
|
109
|
+
weights = isWeightsFile(file) ? file : null;
|
|
110
|
+
} catch {
|
|
111
|
+
weights = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return weights ?? null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Whether a parsed weights file has the shape the kit reads: every row has a
|
|
119
|
+
* weight per candidate, every bias a number per candidate, and three blend
|
|
120
|
+
* numbers per member (the tables plus the ranker) and one for the shell.
|
|
121
|
+
* @param {any} file
|
|
122
|
+
* @returns {file is import('./kit/weights.mjs').WeightsFile}
|
|
123
|
+
*/
|
|
124
|
+
export function isWeightsFile(file) {
|
|
125
|
+
const n = Array.isArray(file?.candidates) ? file.candidates.length : 0;
|
|
126
|
+
return (
|
|
127
|
+
n > 0 &&
|
|
128
|
+
Array.isArray(file.tables) &&
|
|
129
|
+
file.tables.length > 0 &&
|
|
130
|
+
file.tables.every(
|
|
131
|
+
(/** @type {any} */ t) =>
|
|
132
|
+
Array.isArray(t?.words) &&
|
|
133
|
+
Array.isArray(t.rows) &&
|
|
134
|
+
t.rows.length === t.words.length &&
|
|
135
|
+
t.rows.every(
|
|
136
|
+
(/** @type {any} */ r) => typeof r === 'string' && r.length === n,
|
|
137
|
+
) &&
|
|
138
|
+
Array.isArray(t.bias) &&
|
|
139
|
+
t.bias.length === n &&
|
|
140
|
+
t.bias.every((/** @type {any} */ b) => Number.isFinite(b)) &&
|
|
141
|
+
Number.isFinite(t.clip) &&
|
|
142
|
+
Number.isFinite(t.step),
|
|
143
|
+
) &&
|
|
144
|
+
Array.isArray(file.blend) &&
|
|
145
|
+
file.blend.length === 3 * (file.tables.length + 1) + 1 &&
|
|
146
|
+
file.blend.every((/** @type {any} */ x) => Number.isFinite(x))
|
|
147
|
+
);
|
|
148
|
+
}
|
package/api/build/build.doc.mjs
CHANGED
|
@@ -19,8 +19,8 @@ export const doc = {
|
|
|
19
19
|
'The "build a page" entry point. Called with no query it returns the ' +
|
|
20
20
|
'how-to-build-a-page playbook as data: the workflow steps with their ' +
|
|
21
21
|
'commands, the on-system rules, and related lookups. Called with a query it names the page template to ' +
|
|
22
|
-
'START from (always one: the page template a ranker built for long descriptions puts first
|
|
23
|
-
'shell) and the next two templates, ' +
|
|
22
|
+
'START from (always one: the page template a ranker built for long descriptions puts first; for a part ' +
|
|
23
|
+
'of a page, the page it names; else the app shell) and the next two templates, ' +
|
|
24
24
|
'and the unified search grouped around it: the other close page templates, drop-in blocks, and ' +
|
|
25
25
|
'idea-specific components/hooks, plus the always-on frame + foundation. A template carries the page ' +
|
|
26
26
|
'frame and spacing, so the kit never recommends composing a page from components.',
|
package/api/build/build.test.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import {describe, it, expect, vi} from 'vitest';
|
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import {fileURLToPath} from 'node:url';
|
|
10
10
|
import {build} from './build.mjs';
|
|
11
|
-
import {search} from '../search/search.mjs';
|
|
11
|
+
import {search, searchedComponents} from '../search/search.mjs';
|
|
12
12
|
|
|
13
13
|
// api/build/ -> up 3 = packages/cli, up 4 = repo root (has packages/core).
|
|
14
14
|
const REPO = path.resolve(
|
|
@@ -152,6 +152,16 @@ describe('build API', () => {
|
|
|
152
152
|
});
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
+
describe('build kit — reuses the components its search gathered', () => {
|
|
156
|
+
it('keeps them beside the search response, out of its JSON', async () => {
|
|
157
|
+
const all = await search('date picker', {cwd: REPO});
|
|
158
|
+
expect(searchedComponents(all)?.map(c => c.name)).toContain('DateRangeInput');
|
|
159
|
+
expect(JSON.stringify(all)).not.toContain('"keywords"');
|
|
160
|
+
const pagesOnly = await search('date picker', {cwd: REPO, type: 'template'});
|
|
161
|
+
expect(searchedComponents(pagesOnly)).toBeNull();
|
|
162
|
+
}, 60_000);
|
|
163
|
+
});
|
|
164
|
+
|
|
155
165
|
describe('build kit — coverage gates the pages group', () => {
|
|
156
166
|
it('does not call a one-word coincidence a direct match', async () => {
|
|
157
167
|
// A page's keywords include every component its source renders, so any
|
|
@@ -259,7 +269,7 @@ describe('build kit — a thin kit says what to try next', () => {
|
|
|
259
269
|
// A skeleton is a 35-line excerpt: a reader who studies it and composes
|
|
260
270
|
// the rest loses the spacing the template exists to carry. A loose match
|
|
261
271
|
// is still the best start there is, so `start` scaffolds it.
|
|
262
|
-
const r = await build('
|
|
272
|
+
const r = await build('weekly business review with targets', {cwd: REPO});
|
|
263
273
|
expect(r.type).toBe('build.kit');
|
|
264
274
|
if (r.type !== 'build.kit') return;
|
|
265
275
|
expect(r.data.directMatch).toBe(false);
|
|
@@ -357,9 +367,35 @@ describe('build kit — every page starts from a template', () => {
|
|
|
357
367
|
const placed = await build('an empty state for a settings page', {cwd: REPO});
|
|
358
368
|
if (placed.type !== 'build.kit') throw new Error(placed.type);
|
|
359
369
|
expect(placed.data.start).toMatchObject({name: 'settings', basis: 'closest'});
|
|
370
|
+
expect(placed.data.start?.reason).toMatch(/part of a page, so it starts from the page it names/);
|
|
360
371
|
const loose = await build('a date range picker', {cwd: REPO});
|
|
361
372
|
if (loose.type !== 'build.kit') throw new Error(loose.type);
|
|
362
373
|
expect(loose.data.start).toMatchObject({name: 'shell-top-nav', basis: 'fallback'});
|
|
374
|
+
expect(loose.data.start?.reason).toMatch(/part of a page and names no page, so it starts from the app shell/);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('starts a change to an existing page from the app shell', async () => {
|
|
378
|
+
// The page is the builder's to keep; no template scaffolds it.
|
|
379
|
+
const r = await build('add a sort toggle to the existing reports dashboard', {cwd: REPO});
|
|
380
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
381
|
+
expect(r.data.start).toMatchObject({name: 'shell-top-nav', basis: 'fallback'});
|
|
382
|
+
expect(r.data.start?.reason).toMatch(/changes a page you already have, so keep it/);
|
|
383
|
+
expect(r.data.start?.reason).not.toMatch(/too little of the idea fits/);
|
|
384
|
+
// A direct match the response reports is still named.
|
|
385
|
+
if (r.data.directMatch) expect(r.data.start?.reason).toContain(`\`${r.data.pages[0].name}\``);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('does not call a new page that mentions something existing a change', async () => {
|
|
389
|
+
for (const idea of [
|
|
390
|
+
'a new dashboard inspired by the existing one',
|
|
391
|
+
'a new dashboard based on the existing dashboard',
|
|
392
|
+
'clone the existing dashboard as a new page',
|
|
393
|
+
]) {
|
|
394
|
+
const r = await build(idea, {cwd: REPO});
|
|
395
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
396
|
+
expect(r.data.start?.name).toBe('dashboard');
|
|
397
|
+
expect(r.data.start?.reason).not.toMatch(/already have/);
|
|
398
|
+
}
|
|
363
399
|
});
|
|
364
400
|
|
|
365
401
|
it('names a direct match the ranker outweighed in the reason', async () => {
|
|
@@ -441,6 +477,79 @@ describe('build kit — every page starts from a template', () => {
|
|
|
441
477
|
expect(r.data.pages.map(p => p.name)).not.toContain('side-gallery');
|
|
442
478
|
});
|
|
443
479
|
|
|
480
|
+
it('names a direct match the start does not use, and calls the start direct only when it is', async () => {
|
|
481
|
+
for (const idea of ['a login form', 'a docs site for our API', 'contact form']) {
|
|
482
|
+
const r = await build(idea, {cwd: REPO});
|
|
483
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
484
|
+
expect(r.data.directMatch).toBe(true);
|
|
485
|
+
const match = r.data.pages[0].name;
|
|
486
|
+
if (r.data.start?.name === match) {
|
|
487
|
+
expect(r.data.start?.basis).toBe('direct');
|
|
488
|
+
} else {
|
|
489
|
+
expect(['closest', 'fallback']).toContain(r.data.start?.basis);
|
|
490
|
+
expect(r.data.start?.reason).toContain(`\`${match}\``);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('keeps a template search matched directly rather than the app shell', async () => {
|
|
496
|
+
for (const [idea, name] of [
|
|
497
|
+
['a login screen', 'login'],
|
|
498
|
+
['a checkout wizard', 'checkout-wizard'],
|
|
499
|
+
]) {
|
|
500
|
+
const r = await build(idea, {cwd: REPO});
|
|
501
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
502
|
+
expect(r.data.directMatch).toBe(true);
|
|
503
|
+
expect(r.data.start?.name).toBe(name);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('lets the weights choose another template over a direct match', async () => {
|
|
508
|
+
const r = await build('a login form', {cwd: REPO});
|
|
509
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
510
|
+
expect(r.data.directMatch).toBe(true);
|
|
511
|
+
const match = r.data.pages[0].name;
|
|
512
|
+
expect(r.data.start?.name).not.toBe('shell-top-nav');
|
|
513
|
+
expect(r.data.start?.name).not.toBe(match);
|
|
514
|
+
expect(r.data.start?.reason).toContain(`\`${match}\``);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('starts a part that names no page from the app shell', async () => {
|
|
518
|
+
for (const idea of ['a kanban card', 'a date range picker']) {
|
|
519
|
+
const r = await build(idea, {cwd: REPO});
|
|
520
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
521
|
+
expect(r.data.start).toMatchObject({name: 'shell-top-nav', basis: 'fallback'});
|
|
522
|
+
expect(r.data.start?.reason).toMatch(/part of a page/);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('does not keep a loose match over the app shell', async () => {
|
|
527
|
+
// Search matches no template directly, so the ranker's closest page does
|
|
528
|
+
// not override the shell the weights choose.
|
|
529
|
+
const r = await build('quarterly business review', {cwd: REPO});
|
|
530
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
531
|
+
expect(r.data.directMatch).toBe(false);
|
|
532
|
+
expect(r.data.start).toMatchObject({name: 'shell-top-nav', basis: 'fallback'});
|
|
533
|
+
expect(r.data.start?.reason).toMatch(/closest/);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('starts a new page with no matching template from the app shell', async () => {
|
|
537
|
+
const r = await build('a new page', {cwd: REPO});
|
|
538
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
539
|
+
expect(r.data.start?.name).toBe('shell-top-nav');
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('starts a page the words describe from its template', async () => {
|
|
543
|
+
for (const [idea, name] of [
|
|
544
|
+
['a weekly report of sales by region', 'dashboard-scorecard'],
|
|
545
|
+
['a pricing page with three plans and a comparison table', 'table-page'],
|
|
546
|
+
]) {
|
|
547
|
+
const r = await build(idea, {cwd: REPO});
|
|
548
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
549
|
+
expect(r.data.start?.name).toBe(name);
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
444
553
|
it('starts a component in a container from a template with that frame', async () => {
|
|
445
554
|
// "in a modal": the modal is the frame, so the dialog template leads
|
|
446
555
|
// instead of the app shell.
|