@astryxdesign/cli 0.7.2-canary.ebcc404 → 0.7.2-canary.f8f7cf1
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 +9 -8
- package/api/build/help/help.mjs +1 -1
- package/api/component/_adapter.d.mts +14 -0
- package/api/component/_adapter.mjs +48 -0
- package/api/component/component.mjs +10 -2
- package/api/component/integration-export-name.test.mjs +233 -8
- package/api/docs/_adapter.d.mts +11 -0
- package/api/docs/_adapter.mjs +244 -1
- package/api/docs/integrationDocs.test.mjs +124 -0
- package/api/doctor/theme-checks.mjs +1 -1
- package/api/gap-report/gap-report.mjs +2 -1
- package/api/index.d.mts +2 -0
- package/api/index.mjs +2 -0
- package/api/init/run/run.mjs +2 -2
- package/api/integration/_adapter.d.mts +12 -0
- package/api/integration/_adapter.mjs +54 -0
- package/api/integration/add-contribution.component-names.test.mjs +66 -0
- package/api/integration/add-contribution.mjs +8 -4
- package/api/integration/add-contribution.test.mjs +4 -2
- package/api/integration/authoring-checks.mjs +11 -2
- package/api/integration/authoring-checks.test.mjs +39 -0
- package/api/integration/authoring-checks.type.d.mts +9 -0
- package/api/integration/authoring-checks.type.mjs +14 -1
- package/api/integration/check/check.d.mts +96 -0
- package/api/integration/check/check.mjs +357 -0
- package/api/integration/check/check.test.mjs +489 -0
- package/api/integration/integration-check.type.d.mts +118 -0
- package/api/integration/integration-check.type.mjs +84 -0
- package/api/integration/integrationCheck.doc.d.mts +10 -0
- package/api/integration/integrationCheck.doc.mjs +73 -0
- package/api/integration/integrationComponentConflicts.doc.mjs +36 -5
- package/api/integration/pack-check.mjs +48 -3
- package/api/integration/pack-check.test.mjs +247 -0
- package/api/integration/validate-integration.mjs +8 -0
- package/api/integration/validate-integration.test.mjs +11 -0
- package/api/integration/validate-integration.type.d.mts +8 -0
- package/api/integration/validate-integration.type.mjs +12 -1
- package/api/integration/validateIntegration.doc.mjs +17 -3
- package/api/search/search.integration-check.test.mjs +24 -0
- package/api/search/search.mjs +20 -8
- package/api/theme/build/build.family.test.mjs +54 -6
- package/api/theme/build/build.mjs +114 -17
- package/api/theme/themeBuild.doc.mjs +4 -4
- package/assets/docs/README.md +10 -0
- package/assets/docs/author-a-theme.doc.mjs +1 -1
- package/assets/docs/getting-started.doc.mjs +3 -3
- package/assets/docs/theme.doc.dense.mjs +1 -1
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/docs/theme.doc.zh.mjs +1 -1
- package/assets/docs/tree/add-a-component.doc.mjs +10 -0
- package/assets/docs/tree/block-template.doc.mjs +2 -2
- package/assets/docs/tree/check-the-props.doc.mjs +196 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +44 -3
- package/assets/docs/tree/checks.doc.mjs +21 -13
- package/assets/docs/tree/component-family.doc.mjs +5 -1
- package/assets/docs/tree/component-imports.doc.mjs +1 -1
- package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
- package/assets/docs/tree/links.doc.mjs +2 -2
- package/assets/docs/tree/page-template.doc.mjs +1 -1
- package/assets/docs/tree/quick-start.doc.mjs +1 -1
- package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
- package/assets/docs/tree/single-component.doc.mjs +3 -2
- package/assets/docs/tree/troubleshooting.doc.mjs +9 -1
- package/assets/docs/tree/type-scale.doc.mjs +3 -3
- package/assets/docs/tree/versioning.doc.mjs +1 -0
- package/assets/docs/tree/write-the-template-file.doc.mjs +2 -1
- package/assets/docs/use-a-theme.doc.mjs +2 -2
- package/assets/theme.template.ts +1 -1
- package/authoring/doctypes/component/component.doc.mjs +1 -1
- package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
- package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
- package/authoring/doctypes/template/parse.d.mts +6 -2
- package/authoring/doctypes/template/parse.mjs +3 -1
- package/authoring/doctypes/template/template.doc.mjs +1 -1
- package/authoring/doctypes/template/type.ts +22 -0
- package/authoring/index.d.ts +1 -0
- package/authoring/integration/integration.doc.mjs +8 -1
- package/authoring/integration/parse.test.mjs +10 -0
- package/authoring/integration/schema.d.mts +2 -0
- package/authoring/integration/schema.mjs +1 -0
- package/authoring/integration/type.ts +4 -0
- package/clients/cli/commands/build-theme.batch-report.test.mjs +3 -1
- package/clients/cli/commands/build-theme.mjs +1 -1
- package/clients/cli/commands/build-theme.path-safety.test.mjs +155 -21
- package/clients/cli/commands/doctor-integration-components.doc.mjs +15 -4
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +11 -2
- package/clients/cli/commands/doctor-integration.test.mjs +837 -0
- package/clients/cli/commands/doctor.mjs +190 -0
- package/clients/cli/commands/integration-check.doc.mjs +85 -0
- package/clients/cli/commands/integration-check.test.mjs +267 -0
- package/clients/cli/commands/integration.doc.mjs +6 -2
- package/clients/cli/commands/integration.mjs +107 -1
- package/clients/cli/commands/text-json-parity.test.mjs +5 -0
- package/clients/cli/commands/theme-build.doc.mjs +2 -2
- package/clients/cli/index.mjs +1 -0
- package/foundation/config/project.mjs +82 -0
- package/foundation/discovery/authoring-self-docs.mjs +1 -0
- package/foundation/discovery/component-discovery.d.mts +108 -17
- package/foundation/discovery/component-discovery.export-names.test.mjs +469 -2
- package/foundation/discovery/component-discovery.mjs +380 -47
- package/foundation/discovery/component-replacement.test.mjs +20 -0
- package/foundation/discovery/doc-types.d.mts +157 -0
- package/foundation/discovery/doc-types.mjs +1224 -0
- package/foundation/discovery/doc-types.test.mjs +652 -0
- package/foundation/discovery/docs-discovery.d.mts +81 -0
- package/foundation/discovery/docs-discovery.mjs +57 -0
- package/foundation/discovery/integration-doc-files.d.mts +22 -0
- package/foundation/discovery/integration-doc-files.mjs +231 -0
- package/foundation/discovery/integration-doc-files.test.mjs +246 -0
- package/foundation/discovery/module-exports.d.mts +6 -0
- package/foundation/discovery/module-exports.mjs +15 -2
- package/foundation/discovery/package-tsconfig.d.mts +14 -0
- package/foundation/discovery/package-tsconfig.mjs +228 -0
- package/foundation/discovery/package-tsconfig.test.mjs +210 -0
- package/foundation/discovery/project-typescript.d.mts +23 -0
- package/foundation/discovery/project-typescript.mjs +61 -0
- package/foundation/discovery/prop-drift.d.mts +278 -0
- package/foundation/discovery/prop-drift.mjs +2263 -0
- package/foundation/discovery/prop-drift.test.mjs +2219 -0
- package/foundation/discovery/type-match.d.mts +53 -0
- package/foundation/discovery/type-match.mjs +660 -0
- package/foundation/discovery/type-match.test.mjs +169 -0
- package/foundation/discovery/type-text.d.mts +68 -0
- package/foundation/discovery/type-text.mjs +658 -0
- package/foundation/discovery/type-text.test.mjs +196 -0
- package/foundation/discovery/unresolved-references.d.mts +62 -0
- package/foundation/discovery/unresolved-references.mjs +790 -0
- package/foundation/discovery/unresolved-references.test.mjs +567 -0
- package/foundation/doc-compiler/doc-loads.test.mjs +6 -0
- package/foundation/doc-compiler/links.d.mts +9 -5
- package/foundation/doc-compiler/links.mjs +4 -2
- package/foundation/integrations/contribution-inventory.mjs +6 -1
- package/foundation/response/response-types.doc.mjs +7 -2
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -476,10 +476,11 @@ Every response has a `type` discriminant. The full set is below (generated from
|
|
|
476
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. |
|
|
477
477
|
| `integration.add` | A contribution-writer receipt: kind, name, optional root {path, created}, integration-manifest path, every affected project-relative path, written, and dryRun. |
|
|
478
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}]. |
|
|
479
|
-
| `integration.validate` | The validation result: validated (false when nothing was validated: with no package.json the issues list carries one no_package error so the command exits 1, and with a package.json but no integration manifest the issues list is empty and proves nothing), the package name and version (both null when validated is false) plus issues, an AstryxIntegrationIssue[] of {code, severity: warning \| error, message}.
|
|
479
|
+
| `integration.validate` | The validation result: validated (false when nothing was validated: with no package.json the issues list carries one no_package error so the command exits 1, and with a package.json but no integration manifest the issues list is empty and proves nothing), the package name and version (both null when validated is false) plus issues, an AstryxIntegrationIssue[] of {code, severity: warning \| error, message}. When the manifest loads, docTypes checks each doc against its published authoring type: status (checked \| skipped), reason, typescript, docs, findings (all warnings, each naming the field and a fix), unchecked, and fixtures (test fixture docs not checked). |
|
|
480
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.
|
|
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. When the manifest loads and declares a components root, propDrift compares documented props with the TypeScript props: status (checked \| skipped), reason, typescript, components, typesCompared, typesSkipped, findings (all warnings), unchecked, typesNotCompared, and propsNotChecked. |
|
|
482
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
|
+
| `integration.check` | Every source check of an integration package in one result: validated (false when no integration manifest was found, so nothing was checked), the package name and version, strict, and checks, one entry per check (validate, components, templates, docs), each with check, errors, warnings, skipped, failed, and response, the envelope that check returns on its own. The totals are errors, warnings, and skipped; unchecked counts what the checks could not compare. With --strict, failures lists each warning and each piece of skipped work again as an error, in the issue format the checks use; it is empty without --strict. failed decides the exit code. |
|
|
483
484
|
|
|
484
485
|
<!-- END GENERATED: response-types -->
|
|
485
486
|
<!-- Generated by scripts/generate-cli-readme.mjs from the response-types EnumDoc. Run `pnpm -F @astryxdesign/cli readme`. -->
|
|
@@ -551,12 +552,12 @@ data: { checks, summary } }`) that AI agents and scripts can parse.
|
|
|
551
552
|
Omit `[package]` to inspect the package at the current directory, or pass an
|
|
552
553
|
installed package name:
|
|
553
554
|
|
|
554
|
-
| Command | What it checks
|
|
555
|
-
| ----------------------------------------- |
|
|
556
|
-
| `doctor integration validate [package]` | Manifest shape and every declared contribution
|
|
557
|
-
| `doctor integration templates [package]` | Template IDs shared with Core
|
|
558
|
-
| `doctor integration components [package]` | Component names shared with Core
|
|
559
|
-
| `doctor integration docs [package]` | Core topic replacements, extensions, and same-name conflicts
|
|
555
|
+
| Command | What it checks | Exit `1` when |
|
|
556
|
+
| ----------------------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
557
|
+
| `doctor integration validate [package]` | Manifest shape and every declared contribution | A structural issue has error severity |
|
|
558
|
+
| `doctor integration templates [package]` | Template IDs shared with Core | The integration is invalid; ID conflicts are warnings |
|
|
559
|
+
| `doctor integration components [package]` | Component names shared with Core, and each props table against the TypeScript props | The integration is invalid; name conflicts and prop differences are warnings |
|
|
560
|
+
| `doctor integration docs [package]` | Core topic replacements, extensions, and same-name conflicts | A Core overlap is accidental or the docs are invalid |
|
|
560
561
|
|
|
561
562
|
Template and component conflicts include the exact `--package` command needed
|
|
562
563
|
to select the integration contribution. Doc replacements and extensions are
|
package/api/build/help/help.mjs
CHANGED
|
@@ -63,7 +63,7 @@ export function buildHelp() {
|
|
|
63
63
|
"Keep the template's page frame, gap, and padding values. Replace its data, copy, and sections.",
|
|
64
64
|
'No <div>/raw HTML for layout — use VStack/HStack/Grid/Stack/Card etc.',
|
|
65
65
|
'No style={{}} — use component props, and design tokens for values.',
|
|
66
|
-
"Wrap the app in <Theme theme={...}> (import {Theme} from '@astryxdesign/core') and import core reset.css + astryx.css.",
|
|
66
|
+
"Wrap the app in <Theme theme={...}> (import {Theme} from '@astryxdesign/core/theme') and import core reset.css + astryx.css.",
|
|
67
67
|
],
|
|
68
68
|
related: [
|
|
69
69
|
{
|
|
@@ -231,6 +231,20 @@ export function scopeSubComponent(docs: LoadedComponentDoc, dirName: string, cor
|
|
|
231
231
|
};
|
|
232
232
|
matchingComponent: any;
|
|
233
233
|
} | null;
|
|
234
|
+
/**
|
|
235
|
+
* The doc an integration owner shows for `dirName`: when `dirName` is a member
|
|
236
|
+
* its family doc lists under its own name (`AcmeSlide` in an `AcmeCarousel`
|
|
237
|
+
* doc), the doc scoped to that member as {@link scopeSubComponent} scopes
|
|
238
|
+
* Core's, keeping the import the family doc states; otherwise `docs` itself.
|
|
239
|
+
* @param {LoadedComponentDoc} docs
|
|
240
|
+
* @param {OwnershipSubject} owner
|
|
241
|
+
* @param {string} dirName
|
|
242
|
+
* @returns {LoadedComponentDoc & {parentDoc?: string, import?: string}}
|
|
243
|
+
*/
|
|
244
|
+
export function scopeIntegrationMember(docs: LoadedComponentDoc, owner: OwnershipSubject, dirName: string): LoadedComponentDoc & {
|
|
245
|
+
parentDoc?: string;
|
|
246
|
+
import?: string;
|
|
247
|
+
};
|
|
234
248
|
/**
|
|
235
249
|
* Internal ambiguity marker. Single-component callers still receive the same
|
|
236
250
|
* AstryxError code, message, and suggestions; batch callers can additionally
|
|
@@ -33,6 +33,9 @@ import {
|
|
|
33
33
|
findComponentSource,
|
|
34
34
|
findExternalComponentDoc,
|
|
35
35
|
findIntegrationComponent,
|
|
36
|
+
integrationMemberEntry,
|
|
37
|
+
isIntegrationMember,
|
|
38
|
+
recordedDocName,
|
|
36
39
|
resolveImportPath,
|
|
37
40
|
resolveIntegrationImportPath as resolveIntegrationImport,
|
|
38
41
|
unscopedComponentOwners,
|
|
@@ -621,3 +624,48 @@ export function scopeSubComponent(docs, dirName, coreDir) {
|
|
|
621
624
|
|
|
622
625
|
return {scoped, matchingComponent};
|
|
623
626
|
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* The doc an integration owner shows for `dirName`: when `dirName` is a member
|
|
630
|
+
* its family doc lists under its own name (`AcmeSlide` in an `AcmeCarousel`
|
|
631
|
+
* doc), the doc scoped to that member as {@link scopeSubComponent} scopes
|
|
632
|
+
* Core's, keeping the import the family doc states; otherwise `docs` itself.
|
|
633
|
+
* @param {LoadedComponentDoc} docs
|
|
634
|
+
* @param {OwnershipSubject} owner
|
|
635
|
+
* @param {string} dirName
|
|
636
|
+
* @returns {LoadedComponentDoc & {parentDoc?: string, import?: string}}
|
|
637
|
+
*/
|
|
638
|
+
export function scopeIntegrationMember(docs, owner, dirName) {
|
|
639
|
+
const record = {name: dirName, docPath: owner.docPath ?? null};
|
|
640
|
+
if (!isIntegrationMember(record)) return docs;
|
|
641
|
+
const entry = integrationMemberEntry(docs, record);
|
|
642
|
+
// The files list the member but the doc this process loaded has no entry
|
|
643
|
+
// for it (the doc changed after it loaded): never answer with the family.
|
|
644
|
+
if (entry == null) {
|
|
645
|
+
throw new AstryxError(
|
|
646
|
+
`No component named "${dirName}": its family doc changed after this process loaded it.`,
|
|
647
|
+
undefined,
|
|
648
|
+
ERROR_CODES.ERR_UNKNOWN_COMPONENT,
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
// Built from the entry, as scopeSubComponent scopes Core's, so a member that
|
|
652
|
+
// shares the doc's own `name` is scoped too.
|
|
653
|
+
/** @type {LoadedComponentDoc & {parentDoc?: string, import?: string}} */
|
|
654
|
+
const scoped = {
|
|
655
|
+
name: dirName,
|
|
656
|
+
description: entry.description,
|
|
657
|
+
props: entry.props,
|
|
658
|
+
components: [entry],
|
|
659
|
+
// The name the family doc opens under, for every member.
|
|
660
|
+
parentDoc:
|
|
661
|
+
owner.integration && record.docPath
|
|
662
|
+
? recordedDocName(owner.integration, record.docPath)
|
|
663
|
+
: docs.name === dirName && record.docPath
|
|
664
|
+
? docStem(record.docPath)
|
|
665
|
+
: docs.name,
|
|
666
|
+
};
|
|
667
|
+
if (docs.usage) scoped.usage = docs.usage;
|
|
668
|
+
if (docs.theming) scoped.theming = docs.theming;
|
|
669
|
+
if (docs.import) scoped.import = docs.import;
|
|
670
|
+
return scoped;
|
|
671
|
+
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
requireCoreDir,
|
|
20
20
|
loadIntegrationsSafely,
|
|
21
21
|
resolveOwners,
|
|
22
|
+
scopeIntegrationMember,
|
|
22
23
|
classifyScope,
|
|
23
24
|
assertUnambiguousOwners,
|
|
24
25
|
resolveLegacyExternalDoc,
|
|
@@ -429,7 +430,13 @@ export async function component(name, options = {}) {
|
|
|
429
430
|
if (blocks) {
|
|
430
431
|
return componentDetailBlocks(dirName, cwd, owner.package);
|
|
431
432
|
}
|
|
432
|
-
const
|
|
433
|
+
const loaded = await loadComponentDoc(owner.docPath, docOpts);
|
|
434
|
+
// A family member an integration lists under its own name reads as that
|
|
435
|
+
// member, the way a Core sub-component does below.
|
|
436
|
+
const docs =
|
|
437
|
+
scoped.kind === 'integration'
|
|
438
|
+
? scopeIntegrationMember(loaded, owner, dirName)
|
|
439
|
+
: loaded;
|
|
433
440
|
if (props) return componentDetailProps(docs, owner.package);
|
|
434
441
|
return componentDetail(docs, owner, dirName, coreDir);
|
|
435
442
|
}
|
|
@@ -525,7 +532,8 @@ export async function component(name, options = {}) {
|
|
|
525
532
|
// Integration components don't carry showcases — fail without scanning.
|
|
526
533
|
return componentDetailShowcase(dirName, {cwd, name, resolve: false, ownerPackage: owner.package});
|
|
527
534
|
}
|
|
528
|
-
const
|
|
535
|
+
const loaded = await loadComponentDoc(owner.docPath, docOpts);
|
|
536
|
+
const docs = scopeIntegrationMember(loaded, owner, dirName);
|
|
529
537
|
if (props) return componentDetailProps(docs, owner.package);
|
|
530
538
|
return componentDetail(docs, owner, dirName, coreDir);
|
|
531
539
|
}
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file Every surface knows an integration component
|
|
5
|
-
* documents. Fixtures live under a repo-local temp dir: Vite
|
|
6
|
-
* a module from outside the project root.
|
|
4
|
+
* @file Every surface knows an integration component, or a family member, by
|
|
5
|
+
* the name its doc documents. Fixtures live under a repo-local temp dir: Vite
|
|
6
|
+
* refuses to import a module from outside the project root.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
10
10
|
import * as fs from 'node:fs';
|
|
11
11
|
import * as path from 'node:path';
|
|
12
|
+
import {spawnSync} from 'node:child_process';
|
|
12
13
|
import {component} from './component.mjs';
|
|
13
14
|
import {search} from '../search/search.mjs';
|
|
14
15
|
import {validateIntegration} from '../integration/validate-integration.mjs';
|
|
15
16
|
import {integrationComponentConflicts} from '../integration/authoring-checks.mjs';
|
|
16
17
|
import {swizzleCopy} from '../swizzle/copy/copy.mjs';
|
|
17
18
|
import {gapReport} from '../gap-report/gap-report.mjs';
|
|
19
|
+
import {Project} from '../../foundation/config/project.mjs';
|
|
20
|
+
import {collectDocInputs} from '../../foundation/doc-compiler/inputs.mjs';
|
|
18
21
|
import {COMPONENT_REPLACES_CLI} from '../../foundation/integrations/cli-requirement.mjs';
|
|
22
|
+
import {scopeIntegrationMember} from './_adapter.mjs';
|
|
19
23
|
|
|
20
24
|
const SLOW = 30_000;
|
|
21
25
|
|
|
@@ -76,6 +80,7 @@ beforeEach(() => {
|
|
|
76
80
|
'./gallery/Slides': './src/gallery/Slides.tsx',
|
|
77
81
|
'./gallery/Overlays': './src/gallery/Overlays.tsx',
|
|
78
82
|
'./AcmeBadge': './src/AcmeBadge.tsx',
|
|
83
|
+
'./tabs': './src/tabs/AcmeTabs.tsx',
|
|
79
84
|
},
|
|
80
85
|
}),
|
|
81
86
|
);
|
|
@@ -95,6 +100,43 @@ beforeEach(() => {
|
|
|
95
100
|
'export function AcmeBadge() { return null; }\n',
|
|
96
101
|
'A small status label.',
|
|
97
102
|
);
|
|
103
|
+
// A family doc whose members the module it imports exports.
|
|
104
|
+
fs.mkdirSync(path.join(pkgDir, 'src', 'tabs'), {recursive: true});
|
|
105
|
+
fs.writeFileSync(
|
|
106
|
+
path.join(pkgDir, 'src', 'tabs', 'AcmeTabs.tsx'),
|
|
107
|
+
'export function AcmeTabs() { return null; }\nexport function AcmeTab() { return null; }\n',
|
|
108
|
+
);
|
|
109
|
+
fs.writeFileSync(
|
|
110
|
+
path.join(pkgDir, 'src', 'tabs', 'AcmeTabs.doc.mjs'),
|
|
111
|
+
`export default ${JSON.stringify(
|
|
112
|
+
{
|
|
113
|
+
type: 'component',
|
|
114
|
+
name: 'AcmeTabs',
|
|
115
|
+
displayName: 'Acme Tabs',
|
|
116
|
+
import: '@acme/widgets/tabs',
|
|
117
|
+
keywords: ['tabs', 'navigation'],
|
|
118
|
+
usage: {description: 'Switches between views.'},
|
|
119
|
+
components: [
|
|
120
|
+
{
|
|
121
|
+
name: 'AcmeTabs',
|
|
122
|
+
displayName: 'Acme Tabs',
|
|
123
|
+
description: 'The tab list.',
|
|
124
|
+
props: [],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'AcmeTab',
|
|
128
|
+
displayName: 'Acme Tab',
|
|
129
|
+
description: 'One labelled tab in the list.',
|
|
130
|
+
props: [
|
|
131
|
+
{name: 'label', type: 'string', description: 'The tab label.'},
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
null,
|
|
137
|
+
2,
|
|
138
|
+
)};\n`,
|
|
139
|
+
);
|
|
98
140
|
});
|
|
99
141
|
|
|
100
142
|
afterEach(() => {
|
|
@@ -158,7 +200,7 @@ describe('a component doc that names another export of its file', () => {
|
|
|
158
200
|
async () => {
|
|
159
201
|
expect(
|
|
160
202
|
listedNames(await component(undefined, {cwd: tmpDir, list: true})),
|
|
161
|
-
).toEqual(['AcmeBadge', 'AcmeCarousel']);
|
|
203
|
+
).toEqual(['AcmeBadge', 'AcmeCarousel', 'AcmeTab', 'AcmeTabs']);
|
|
162
204
|
|
|
163
205
|
const detail = await component('AcmeCarousel', {cwd: tmpDir});
|
|
164
206
|
expect(detail.type).toBe('component.detail');
|
|
@@ -198,15 +240,18 @@ describe('a component doc that names another export of its file', () => {
|
|
|
198
240
|
SLOW,
|
|
199
241
|
);
|
|
200
242
|
|
|
201
|
-
it(
|
|
202
|
-
"leaves a Core name to Core unless --package names it, and passes Doctor's package checks",
|
|
203
|
-
async
|
|
243
|
+
it.each(['the export its doc names', 'a member its family doc lists'])(
|
|
244
|
+
"leaves a Core name to Core unless --package names it, when it is %s, and passes Doctor's package checks",
|
|
245
|
+
async kind => {
|
|
204
246
|
// Overlays.tsx exports Dialog, a Core name, and nothing named Overlays.
|
|
247
|
+
const members = [{name: 'Dialog', description: 'A dialog.', props: []}];
|
|
248
|
+
const member = kind === 'a member its family doc lists';
|
|
205
249
|
writeComponent(
|
|
206
250
|
'gallery/Overlays',
|
|
207
|
-
'Dialog',
|
|
251
|
+
member ? 'AcmeOverlays' : 'Dialog',
|
|
208
252
|
'export function AcmeOverlays() { return null; }\nexport function Dialog() { return null; }\n',
|
|
209
253
|
'Overlays.',
|
|
254
|
+
member ? {components: members} : {},
|
|
210
255
|
);
|
|
211
256
|
|
|
212
257
|
const validation = await validateIntegration(undefined, {cwd: pkgDir});
|
|
@@ -416,4 +461,184 @@ describe('a component doc that names another export of its file', () => {
|
|
|
416
461
|
},
|
|
417
462
|
SLOW,
|
|
418
463
|
);
|
|
464
|
+
|
|
465
|
+
it(
|
|
466
|
+
"opens a member named like its family doc's own name as that member",
|
|
467
|
+
async () => {
|
|
468
|
+
// Overlays.tsx exports Overlays, so the doc keeps its file name, and
|
|
469
|
+
// AcmeChip, the doc's own name, is a member.
|
|
470
|
+
const label = {name: 'label', type: 'string', description: 'The label.'};
|
|
471
|
+
writeComponent(
|
|
472
|
+
'gallery/Overlays',
|
|
473
|
+
'AcmeChip',
|
|
474
|
+
'export function Overlays() { return null; }\nexport function AcmeChip() { return null; }\nexport function AcmeChipIcon() { return null; }\n',
|
|
475
|
+
'Chips.',
|
|
476
|
+
{
|
|
477
|
+
components: [
|
|
478
|
+
{name: 'AcmeChip', description: 'One chip.', props: [label]},
|
|
479
|
+
{name: 'AcmeChipIcon', description: 'An icon.', props: []},
|
|
480
|
+
],
|
|
481
|
+
},
|
|
482
|
+
);
|
|
483
|
+
// Every member names the family by the name it opens under.
|
|
484
|
+
expect(
|
|
485
|
+
(await component('AcmeChipIcon', {cwd: tmpDir})).data.parentDoc,
|
|
486
|
+
).toBe('Overlays');
|
|
487
|
+
const detail = await component('AcmeChip', {cwd: tmpDir});
|
|
488
|
+
expect(detail.data).toMatchObject({
|
|
489
|
+
name: 'AcmeChip',
|
|
490
|
+
description: 'One chip.',
|
|
491
|
+
parentDoc: 'Overlays',
|
|
492
|
+
});
|
|
493
|
+
const props = await component('AcmeChip', {cwd: tmpDir, props: true});
|
|
494
|
+
expect(props.data.map(prop => prop.name)).toEqual(['label']);
|
|
495
|
+
},
|
|
496
|
+
SLOW,
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
it(
|
|
500
|
+
'opens, lists and finds a family member as itself, with the import its family states',
|
|
501
|
+
async () => {
|
|
502
|
+
const list = await component(undefined, {cwd: tmpDir, list: true});
|
|
503
|
+
const entry = Object.values(list.data.components)
|
|
504
|
+
.flat()
|
|
505
|
+
.find(item => item.name === 'AcmeTab');
|
|
506
|
+
expect(entry).toEqual({
|
|
507
|
+
name: 'AcmeTab',
|
|
508
|
+
package: '@acme/widgets',
|
|
509
|
+
import: '@acme/widgets/tabs',
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const detail = await component('AcmeTab', {cwd: tmpDir});
|
|
513
|
+
expect(detail.data).toMatchObject({
|
|
514
|
+
name: 'AcmeTab',
|
|
515
|
+
description: 'One labelled tab in the list.',
|
|
516
|
+
parentDoc: 'AcmeTabs',
|
|
517
|
+
package: '@acme/widgets',
|
|
518
|
+
import: '@acme/widgets/tabs',
|
|
519
|
+
sourceAvailable: true,
|
|
520
|
+
});
|
|
521
|
+
expect(detail.data.props.map(prop => prop.name)).toEqual(['label']);
|
|
522
|
+
const props = await component('AcmeTab', {cwd: tmpDir, props: true});
|
|
523
|
+
expect(props.data.map(prop => prop.name)).toEqual(['label']);
|
|
524
|
+
// The family's own doc is unchanged.
|
|
525
|
+
expect((await component('AcmeTabs', {cwd: tmpDir})).data).toMatchObject({
|
|
526
|
+
name: 'AcmeTabs',
|
|
527
|
+
import: '@acme/widgets/tabs',
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
const hits = (await search('AcmeTab', {cwd: tmpDir, type: 'component'}))
|
|
531
|
+
.data.results;
|
|
532
|
+
expect(hits[0]).toMatchObject({
|
|
533
|
+
name: 'AcmeTab',
|
|
534
|
+
description: 'One labelled tab in the list.',
|
|
535
|
+
import: '@acme/widgets/tabs',
|
|
536
|
+
});
|
|
537
|
+
// The member does not carry the family's keywords.
|
|
538
|
+
const tabs = (
|
|
539
|
+
await search('navigation', {cwd: tmpDir, type: 'component'})
|
|
540
|
+
).data.results;
|
|
541
|
+
expect(tabs.map(hit => hit.name)).toContain('AcmeTabs');
|
|
542
|
+
expect(tabs.map(hit => hit.name)).not.toContain('AcmeTab');
|
|
543
|
+
},
|
|
544
|
+
SLOW,
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
it(
|
|
548
|
+
'leaves a member named like a Core sub-component to Core, and swizzle unknown',
|
|
549
|
+
async () => {
|
|
550
|
+
const tabs = path.join(pkgDir, 'src', 'tabs');
|
|
551
|
+
const doc = path.join(tabs, 'AcmeTabs.doc.mjs');
|
|
552
|
+
fs.writeFileSync(
|
|
553
|
+
doc,
|
|
554
|
+
fs
|
|
555
|
+
.readFileSync(doc, 'utf-8')
|
|
556
|
+
.replace(
|
|
557
|
+
'"components": [',
|
|
558
|
+
'"components": [{"name": "DialogHeader", "description": "x"},',
|
|
559
|
+
),
|
|
560
|
+
);
|
|
561
|
+
fs.appendFileSync(
|
|
562
|
+
path.join(tabs, 'AcmeTabs.tsx'),
|
|
563
|
+
'export function DialogHeader() { return null; }\n',
|
|
564
|
+
);
|
|
565
|
+
const cwd = tmpDir;
|
|
566
|
+
expect(
|
|
567
|
+
listedNames(await component(undefined, {cwd, list: true})),
|
|
568
|
+
).toContain('DialogHeader');
|
|
569
|
+
expect((await component('DialogHeader', {cwd})).data.package).toBe(
|
|
570
|
+
'@astryxdesign/core',
|
|
571
|
+
);
|
|
572
|
+
await expect(swizzleCopy('DialogHeader', {cwd})).rejects.toMatchObject({
|
|
573
|
+
code: 'ERR_UNKNOWN_COMPONENT',
|
|
574
|
+
});
|
|
575
|
+
},
|
|
576
|
+
SLOW,
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
it('fails a member its family doc, as this process loaded it, does not list', async () => {
|
|
580
|
+
const owner = {
|
|
581
|
+
package: '@acme/widgets',
|
|
582
|
+
docPath: path.join(pkgDir, 'src', 'tabs', 'AcmeTabs.doc.mjs'),
|
|
583
|
+
sourcePath: null,
|
|
584
|
+
};
|
|
585
|
+
// The doc this process loaded before AcmeTab was added.
|
|
586
|
+
const stale = {type: 'component', name: 'AcmeTabs', components: []};
|
|
587
|
+
expect(() =>
|
|
588
|
+
scopeIntegrationMember(/** @type {any} */ (stale), owner, 'AcmeTab', ''),
|
|
589
|
+
).toThrow(expect.objectContaining({code: 'ERR_UNKNOWN_COMPONENT'}));
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it(
|
|
593
|
+
'indexes a .doc.ts family member as a member when Node cannot load the doc',
|
|
594
|
+
() => {
|
|
595
|
+
const fam = path.join(pkgDir, 'src', 'fam');
|
|
596
|
+
fs.mkdirSync(fam, {recursive: true});
|
|
597
|
+
const manifest = path.join(pkgDir, 'package.json');
|
|
598
|
+
const pkg = JSON.parse(fs.readFileSync(manifest, 'utf-8'));
|
|
599
|
+
pkg.exports['./fam'] = './src/fam/AcmeFamTs.tsx';
|
|
600
|
+
fs.writeFileSync(manifest, JSON.stringify(pkg));
|
|
601
|
+
fs.writeFileSync(
|
|
602
|
+
path.join(fam, 'AcmeFamTs.doc.ts'),
|
|
603
|
+
"export default {type: 'component', name: 'AcmeFamTs', import: '@acme/widgets/fam', usage: {description: 'A family.'}, components: [{name: 'AcmeFamTs', description: 'x'}, {name: 'AcmeFamItem', description: 'One item.'}]};\n",
|
|
604
|
+
);
|
|
605
|
+
fs.writeFileSync(
|
|
606
|
+
path.join(fam, 'AcmeFamTs.tsx'),
|
|
607
|
+
'export function AcmeFamTs() { return null; }\nexport function AcmeFamItem() { return null; }\n',
|
|
608
|
+
);
|
|
609
|
+
// The real CLI under Node, which does not strip types in node_modules.
|
|
610
|
+
const run = spawnSync(
|
|
611
|
+
process.execPath,
|
|
612
|
+
[
|
|
613
|
+
path.join(process.cwd(), 'packages/cli/clients/cli/bin/astryx.mjs'),
|
|
614
|
+
'search',
|
|
615
|
+
'AcmeFamTs',
|
|
616
|
+
'--json',
|
|
617
|
+
],
|
|
618
|
+
{cwd: tmpDir, encoding: 'utf-8'},
|
|
619
|
+
);
|
|
620
|
+
const hits = JSON.parse(run.stdout).data.results.filter(
|
|
621
|
+
(/** @type {any} */ hit) => hit.domain === 'component',
|
|
622
|
+
);
|
|
623
|
+
expect(hits.map((/** @type {any} */ hit) => hit.name)).toEqual([
|
|
624
|
+
'AcmeFamTs',
|
|
625
|
+
]);
|
|
626
|
+
},
|
|
627
|
+
SLOW,
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
it(
|
|
631
|
+
'reads a family doc once when the docs compile',
|
|
632
|
+
async () => {
|
|
633
|
+
const {inputs, problems} = await collectDocInputs(
|
|
634
|
+
await Project.load(tmpDir),
|
|
635
|
+
);
|
|
636
|
+
expect(problems).toEqual([]);
|
|
637
|
+
const tabsDoc = path.join(pkgDir, 'src', 'tabs', 'AcmeTabs.doc.mjs');
|
|
638
|
+
expect(
|
|
639
|
+
inputs.filter(input => input.file === tabsDoc).map(input => input.name),
|
|
640
|
+
).toEqual(['AcmeTabs']);
|
|
641
|
+
},
|
|
642
|
+
SLOW,
|
|
643
|
+
);
|
|
419
644
|
});
|
package/api/docs/_adapter.d.mts
CHANGED
|
@@ -281,6 +281,17 @@ export function resolveTopicDocs(topic: unknown, options?: {
|
|
|
281
281
|
lang: string | null;
|
|
282
282
|
entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry;
|
|
283
283
|
}>;
|
|
284
|
+
/**
|
|
285
|
+
* Each doc one integration package ships, checked against the published
|
|
286
|
+
* authoring type for its kind with the project's own TypeScript (spec:AST-039
|
|
287
|
+
* FR13). Without TypeScript the report says nothing was checked.
|
|
288
|
+
* @param {any} integration a loaded integration
|
|
289
|
+
* @param {string} cwd
|
|
290
|
+
* @returns {Promise<import('../../foundation/discovery/doc-types.mjs').DocTypesReport & {fixtures?: number}>}
|
|
291
|
+
*/
|
|
292
|
+
export function integrationDocTypes(integration: any, cwd: string): Promise<import("../../foundation/discovery/doc-types.mjs").DocTypesReport & {
|
|
293
|
+
fixtures?: number;
|
|
294
|
+
}>;
|
|
284
295
|
/**
|
|
285
296
|
* A doc a link found: what a read shows of the link, and the typed doc behind
|
|
286
297
|
* it when a reference block can include it.
|