@astryxdesign/cli 0.6.3-canary.ea2f048 → 0.6.3-canary.ebaebc4
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 +2 -1
- package/api/build/build.type.d.mts +2 -2
- package/api/build/build.type.mjs +2 -2
- package/api/component/component.type.d.mts +6 -6
- package/api/component/component.type.mjs +19 -19
- package/api/discover/discover.type.d.mts +4 -4
- package/api/discover/discover.type.mjs +10 -10
- package/api/docs/_adapter.d.mts +37 -24
- package/api/docs/_adapter.mjs +169 -83
- package/api/docs/compiled-topics.test.mjs +78 -0
- package/api/docs/detail/detail.mjs +14 -63
- package/api/docs/detail/section/section.d.mts +1 -1
- package/api/docs/detail/section/section.mjs +44 -20
- package/api/docs/detail/section/section.test.mjs +41 -0
- package/api/docs/docs.d.mts +7 -2
- package/api/docs/docs.doc.mjs +27 -10
- package/api/docs/docs.mjs +16 -9
- package/api/docs/docs.test.mjs +6 -0
- package/api/docs/docs.type.d.mts +40 -3
- package/api/docs/docs.type.mjs +36 -8
- package/api/docs/index/index.d.mts +18 -0
- package/api/docs/index/index.mjs +32 -0
- package/api/docs/index/index.test.mjs +62 -0
- package/api/docs/integrationDocs.test.mjs +106 -0
- package/api/doctor/doctor.d.mts +48 -0
- package/api/doctor/doctor.mjs +232 -0
- package/api/doctor/doctor.test.mjs +196 -0
- package/api/hook/hook.type.d.mts +3 -3
- package/api/hook/hook.type.mjs +11 -11
- package/api/hook/list/list.d.mts +1 -1
- package/api/integration/add-contribution.mjs +5 -3
- package/api/integration/add-contribution.test.mjs +4 -4
- package/api/integration/integration-authoring.type.d.mts +1 -1
- package/api/integration/pack-check.mjs +49 -7
- package/api/integration/pack-check.test.mjs +249 -0
- package/api/search/search.d.mts +1 -1
- package/api/search/search.mjs +5 -5
- package/api/search/search.type.d.mts +2 -2
- package/api/search/search.type.mjs +1 -1
- package/api/swizzle/swizzle.type.d.mts +2 -2
- package/api/swizzle/swizzle.type.mjs +2 -2
- package/api/template/template.d.mts +1 -1
- package/api/template/template.type.d.mts +6 -6
- package/api/template/template.type.mjs +12 -12
- package/api/theme/build/build.mjs +20 -6
- package/api/theme/build/build.test.mjs +127 -0
- package/api/theme/palette/generate/generate.mjs +1 -1
- package/api/theme/palette/generate/generator.d.mts +10 -13
- package/api/theme/palette/generate/generator.mjs +7 -3
- package/api/theme/theme.type.d.mts +170 -11
- package/api/theme/theme.type.mjs +94 -27
- package/api/upgrade/_adapter.mjs +71 -5
- package/api/upgrade/project-context.test.mjs +272 -0
- package/api/upgrade/upgrade.doc.mjs +4 -3
- package/api/upgrade/upgrade.type.d.mts +5 -5
- package/api/upgrade/upgrade.type.mjs +11 -11
- package/assets/codemods/integration-discovery.mjs +40 -2
- package/assets/codemods/integration-discovery.test.mjs +58 -0
- package/assets/codemods/transforms/v0.3.0/__tests__/unwrap-authoring-factories.test.mjs +27 -5
- package/assets/codemods/transforms/v0.3.0/unwrap-authoring-factories.mjs +20 -5
- package/assets/docs/README.md +9 -0
- package/assets/docs/authoring.doc.mjs +14 -0
- package/assets/docs/cli-integrations.doc.mjs +86 -15
- package/assets/docs/styling-libraries.doc.mjs +1 -1
- package/assets/docs/working-with-ai.doc.mjs +1 -1
- package/authoring/_shared/contract.ts +22 -0
- package/authoring/codemod/codemod.doc.mjs +6 -1
- package/authoring/codemod/parse.d.mts +8 -8
- package/authoring/codemod/parse.mjs +8 -6
- package/authoring/config/parse.d.mts +13 -13
- package/authoring/config/parse.mjs +8 -8
- package/authoring/config/type.ts +3 -3
- package/authoring/debug/parse.d.mts +5 -5
- package/authoring/debug/parse.mjs +3 -3
- package/authoring/doctypes/_schema.d.mts +788 -23
- package/authoring/doctypes/_schema.mjs +492 -39
- package/authoring/doctypes/base/graph-fields.doc.d.mts +9 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +62 -0
- package/authoring/doctypes/base/type.ts +40 -0
- package/authoring/doctypes/command/command.doc.mjs +3 -2
- package/authoring/doctypes/command/parse.d.mts +2 -2
- package/authoring/doctypes/command/parse.mjs +1 -1
- package/authoring/doctypes/command/type.ts +3 -2
- package/authoring/doctypes/component/component.doc.mjs +6 -3
- package/authoring/doctypes/component/parse.d.mts +2 -2
- package/authoring/doctypes/component/parse.mjs +1 -1
- package/authoring/doctypes/component/type.ts +4 -3
- package/authoring/doctypes/enum/parse.d.mts +2 -2
- package/authoring/doctypes/enum/parse.mjs +1 -1
- package/authoring/doctypes/enum/type.ts +3 -1
- package/authoring/doctypes/function/function.doc.mjs +4 -0
- package/authoring/doctypes/function/parse.d.mts +2 -2
- package/authoring/doctypes/function/parse.mjs +1 -1
- package/authoring/doctypes/function/type.ts +6 -2
- package/authoring/doctypes/hook/hook.doc.mjs +4 -0
- package/authoring/doctypes/hook/parse.d.mts +2 -2
- package/authoring/doctypes/hook/parse.mjs +1 -1
- package/authoring/doctypes/hook/type.ts +3 -2
- package/authoring/doctypes/legacy.d.mts +8 -6
- package/authoring/doctypes/legacy.mjs +5 -4
- package/authoring/doctypes/load-contract.test.mjs +207 -0
- package/authoring/doctypes/namespace/namespace.doc.d.mts +9 -0
- package/authoring/doctypes/namespace/namespace.doc.mjs +132 -0
- package/authoring/doctypes/namespace/parse.d.mts +12 -0
- package/authoring/doctypes/namespace/parse.mjs +25 -0
- package/authoring/doctypes/namespace/parse.test.mjs +165 -0
- package/authoring/doctypes/namespace/type.ts +71 -0
- package/authoring/doctypes/parse.d.mts +20 -18
- package/authoring/doctypes/parse.mjs +16 -10
- package/authoring/doctypes/parse.test.mjs +77 -3
- package/authoring/doctypes/reference/parse.d.mts +2 -2
- package/authoring/doctypes/reference/parse.mjs +8 -5
- package/authoring/doctypes/reference/reference.doc.mjs +17 -4
- package/authoring/doctypes/reference/type.ts +51 -5
- package/authoring/doctypes/schema/parse.d.mts +2 -2
- package/authoring/doctypes/schema/parse.mjs +1 -1
- package/authoring/doctypes/schema/type.ts +3 -2
- package/authoring/doctypes/template/parse.d.mts +92 -1
- package/authoring/doctypes/template/parse.mjs +36 -2
- package/authoring/doctypes/template/parse.test.mjs +8 -2
- package/authoring/doctypes/template/template.doc.mjs +4 -0
- package/authoring/doctypes/template/type.ts +5 -2
- package/authoring/doctypes/types.ts +10 -9
- package/authoring/gap-report/parse.d.mts +10 -10
- package/authoring/gap-report/parse.mjs +6 -6
- package/authoring/gap-report/type.ts +1 -1
- package/authoring/identity/identity.doc.d.mts +9 -0
- package/authoring/identity/identity.doc.mjs +61 -0
- package/authoring/identity/type.ts +132 -0
- package/authoring/index.d.mts +1 -0
- package/authoring/index.d.ts +49 -17
- package/authoring/index.mjs +1 -0
- package/authoring/integration/integration.doc.mjs +13 -6
- package/authoring/integration/parse.d.mts +2 -2
- package/authoring/integration/parse.mjs +1 -1
- package/authoring/integration/parse.test.mjs +10 -1
- package/authoring/integration/schema.d.mts +6 -4
- package/authoring/integration/schema.mjs +9 -3
- package/authoring/integration/type.ts +23 -6
- package/authoring/shadcn/receipt.d.mts +6 -6
- package/clients/cli/commands/docs.doc.mjs +13 -3
- package/clients/cli/commands/docs.mjs +121 -21
- package/clients/cli/commands/docs.test.mjs +88 -0
- package/clients/cli/commands/integration-authoring.test.mjs +13 -9
- package/clients/cli/commands/theme-palette-generate.doc.mjs +8 -4
- package/clients/cli/commands/upgrade.doc.mjs +2 -2
- package/clients/cli/formatters/index.mjs +162 -1
- package/clients/cli/formatters/index.test.mjs +91 -0
- package/clients/cli/lib/manifest.mjs +7 -2
- package/foundation/config/project.mjs +21 -6
- package/foundation/discovery/authoring-self-docs.d.mts +69 -0
- package/foundation/discovery/authoring-self-docs.mjs +214 -0
- package/foundation/discovery/authoring-self-docs.test.mjs +154 -0
- package/foundation/discovery/component-discovery.d.mts +1 -1
- package/foundation/discovery/component-discovery.mjs +2 -1
- package/foundation/discovery/docs-discovery.d.mts +11 -4
- package/foundation/discovery/docs-discovery.mjs +208 -88
- package/foundation/discovery/docs-discovery.test.mjs +279 -13
- package/foundation/discovery/docs-output-budget.d.mts +28 -0
- package/foundation/discovery/docs-output-budget.mjs +50 -0
- package/foundation/discovery/docs-section-key.d.mts +98 -0
- package/foundation/discovery/docs-section-key.mjs +221 -0
- package/foundation/discovery/docs-section-key.test.mjs +224 -0
- package/foundation/discovery/template-adapter.mjs +2 -1
- package/foundation/discovery/theming-targets.test.mjs +4 -0
- package/foundation/doc-compiler/compile.d.mts +162 -0
- package/foundation/doc-compiler/compile.mjs +262 -0
- package/foundation/doc-compiler/doc-compiler.test.mjs +687 -0
- package/foundation/doc-compiler/ir.d.mts +9 -0
- package/foundation/doc-compiler/ir.mjs +287 -0
- package/foundation/doc-compiler/lenses.d.mts +33 -0
- package/foundation/doc-compiler/lenses.mjs +127 -0
- package/foundation/identity/provider-identity.d.mts +90 -0
- package/foundation/identity/provider-identity.mjs +320 -0
- package/foundation/identity/provider-identity.test.mjs +254 -0
- package/foundation/identity/providers.d.mts +7 -0
- package/foundation/identity/providers.mjs +16 -0
- package/foundation/integrations/autolink.mjs +12 -5
- package/foundation/integrations/integration-warnings.mjs +6 -0
- package/foundation/integrations/integrations.d.mts +46 -2
- package/foundation/integrations/integrations.mjs +167 -8
- package/foundation/integrations/integrations.test.mjs +384 -1
- package/foundation/integrations/provider-conflicts.test.mjs +125 -0
- package/foundation/integrations/validate-contributions.d.mts +2 -0
- package/foundation/integrations/validate-contributions.mjs +10 -0
- package/foundation/response/json-contract.test.mjs +46 -17
- package/foundation/response/response-types.doc.mjs +6 -1
- package/package.json +9 -11
package/api/doctor/doctor.mjs
CHANGED
|
@@ -27,6 +27,15 @@ import {MIN_NODE_VERSION, isNodeVersionSupported} from '../../foundation/env/nod
|
|
|
27
27
|
import {CLI_ROOT, findCoreDir, findInstalledPackage} from '../../foundation/fs/paths.mjs';
|
|
28
28
|
import {explainPackageManager, getCliInvocation} from '../../foundation/env/package-manager.mjs';
|
|
29
29
|
import {findConfigPath, Project} from '../../foundation/config/project.mjs';
|
|
30
|
+
import {DocsCatalog} from '../../foundation/discovery/docs-discovery.mjs';
|
|
31
|
+
import {buildDocsIndexData} from '../../foundation/discovery/docs-section-key.mjs';
|
|
32
|
+
import {
|
|
33
|
+
DOC_OUTPUT_BUDGET_BYTES,
|
|
34
|
+
docsIndexBytes,
|
|
35
|
+
oversizedDocSections,
|
|
36
|
+
} from '../../foundation/discovery/docs-output-budget.mjs';
|
|
37
|
+
import {compileTopic, overlayLanguages} from '../docs/_adapter.mjs';
|
|
38
|
+
import {detailView} from '../../foundation/doc-compiler/lenses.mjs';
|
|
30
39
|
import {semverCompare, isValidSemver, satisfiesRange} from '../../foundation/env/semver.mjs';
|
|
31
40
|
|
|
32
41
|
/**
|
|
@@ -52,6 +61,11 @@ import {semverCompare, isValidSemver, satisfiesRange} from '../../foundation/env
|
|
|
52
61
|
* @property {import('../../foundation/integrations/integrations.mjs').LoadedIntegration[]|null} [integrations]
|
|
53
62
|
* Every integration the project loaded, or null when the project could not be
|
|
54
63
|
* read at all.
|
|
64
|
+
* @property {DocsCatalog|null} [docsCatalog] - The topics a docs read sees.
|
|
65
|
+
* @property {Array<{package?: string, code: string, message: string}>} [docsCatalogIssues]
|
|
66
|
+
* `invalid_doc` issues from the project's contributed docs.
|
|
67
|
+
* @property {string|null} [docsCatalogError] - Why the project's docs catalog
|
|
68
|
+
* could not be built, when it could not.
|
|
55
69
|
* @property {Error|null} [configError] - Error thrown while resolving the config
|
|
56
70
|
* path (e.g. multiple config files present), surfaced by checkConfig as a FAIL.
|
|
57
71
|
*/
|
|
@@ -627,6 +641,201 @@ export function checkPackageManager(ctx) {
|
|
|
627
641
|
};
|
|
628
642
|
}
|
|
629
643
|
|
|
644
|
+
/**
|
|
645
|
+
* Check 6b — every contributing integration owns its provider identity.
|
|
646
|
+
*
|
|
647
|
+
* Artifact and document IDs are provider-scoped, so a package that claims a
|
|
648
|
+
* provider ID an earlier-loaded package already holds is loaded inert: its
|
|
649
|
+
* components, templates, themes, docs, and codemods are withdrawn while the
|
|
650
|
+
* earlier package keeps contributing. That can be a deliberate transition
|
|
651
|
+
* (a renamed package installed beside its predecessor), so it warns rather
|
|
652
|
+
* than fails, but it is never allowed to happen quietly.
|
|
653
|
+
*
|
|
654
|
+
* @param {DoctorContext} ctx
|
|
655
|
+
* @returns {DoctorCheck}
|
|
656
|
+
*/
|
|
657
|
+
export function checkProviderIdentity(ctx) {
|
|
658
|
+
const id = 'provider-identity';
|
|
659
|
+
const label = 'Integration provider identity';
|
|
660
|
+
|
|
661
|
+
if (ctx.integrations == null) {
|
|
662
|
+
return {
|
|
663
|
+
id,
|
|
664
|
+
label,
|
|
665
|
+
status: 'info',
|
|
666
|
+
message: 'Skipped — the project configuration could not be read.',
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const conflicts = ctx.integrations.filter(
|
|
671
|
+
integration => integration.__providerConflict,
|
|
672
|
+
);
|
|
673
|
+
if (conflicts.length > 0) {
|
|
674
|
+
return {
|
|
675
|
+
id,
|
|
676
|
+
label,
|
|
677
|
+
status: 'warn',
|
|
678
|
+
message: conflicts
|
|
679
|
+
.map(integration => integration.__providerConflict?.message)
|
|
680
|
+
.join(' '),
|
|
681
|
+
fix:
|
|
682
|
+
'Give each integration its own `providerId` in astryx.integration.*. ' +
|
|
683
|
+
"A renamed package may keep its predecessor's ID only when the " +
|
|
684
|
+
'predecessor is no longer installed.',
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const count = ctx.integrations.filter(
|
|
689
|
+
integration =>
|
|
690
|
+
integration.providerId != null && integration.__loadError == null,
|
|
691
|
+
).length;
|
|
692
|
+
if (count === 0) {
|
|
693
|
+
return {
|
|
694
|
+
id,
|
|
695
|
+
label,
|
|
696
|
+
status: 'info',
|
|
697
|
+
message: 'None — no loaded integration has a provider identity.',
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
id,
|
|
702
|
+
label,
|
|
703
|
+
status: 'pass',
|
|
704
|
+
message:
|
|
705
|
+
count === 1
|
|
706
|
+
? '1 loaded integration has its own provider ID.'
|
|
707
|
+
: `${count} loaded integrations each have their own provider ID.`,
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/** @param {number} bytes */
|
|
712
|
+
const kilobytes = bytes => `${Math.ceil(bytes / 1024)} KB`;
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* @param {string[]} problems
|
|
716
|
+
* @returns {string}
|
|
717
|
+
*/
|
|
718
|
+
function joinProblems(problems) {
|
|
719
|
+
return problems.length === 1
|
|
720
|
+
? problems[0]
|
|
721
|
+
: `${problems.length} problems: ${problems.join('; ')}`;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Every authoring self-doc is reachable from `astryx docs authoring`, loads,
|
|
726
|
+
* and fits in one read. The audit is imported here, inside the try, so a
|
|
727
|
+
* malformed self-doc is reported rather than taking Doctor down.
|
|
728
|
+
* @param {DoctorContext} [_ctx]
|
|
729
|
+
* @returns {Promise<DoctorCheck>}
|
|
730
|
+
*/
|
|
731
|
+
export async function checkAuthoringDocs(_ctx) {
|
|
732
|
+
const id = 'authoring-docs';
|
|
733
|
+
const label = 'Authoring docs';
|
|
734
|
+
try {
|
|
735
|
+
const {auditAuthoringSelfDocs} = await import(
|
|
736
|
+
'../../foundation/discovery/authoring-self-docs.mjs'
|
|
737
|
+
);
|
|
738
|
+
const audit = await auditAuthoringSelfDocs();
|
|
739
|
+
const problems = [
|
|
740
|
+
...audit.unreachable.map(
|
|
741
|
+
source => `${source} is not in \`astryx docs authoring\``,
|
|
742
|
+
),
|
|
743
|
+
...audit.failed.map(({source, error}) => `${source} failed to load: ${error}`),
|
|
744
|
+
...audit.oversized.map(
|
|
745
|
+
({key, bytes}) =>
|
|
746
|
+
`authoring section "${key}" is ${kilobytes(bytes)}, over the ${kilobytes(DOC_OUTPUT_BUDGET_BYTES)} one read may return`,
|
|
747
|
+
),
|
|
748
|
+
];
|
|
749
|
+
if (problems.length > 0) {
|
|
750
|
+
return {
|
|
751
|
+
id,
|
|
752
|
+
label,
|
|
753
|
+
status: 'fail',
|
|
754
|
+
message: joinProblems(problems),
|
|
755
|
+
fix: 'List every authoring self-doc in AUTHORING_SELF_DOCS, fix the one that fails to load, and split a section that is too large.',
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
return {
|
|
759
|
+
id,
|
|
760
|
+
label,
|
|
761
|
+
status: 'pass',
|
|
762
|
+
message: `All ${audit.sections} authoring schemas are readable in \`astryx docs authoring\`.`,
|
|
763
|
+
};
|
|
764
|
+
} catch (err) {
|
|
765
|
+
return {
|
|
766
|
+
id,
|
|
767
|
+
label,
|
|
768
|
+
status: 'fail',
|
|
769
|
+
message: `The authoring docs could not be audited: ${err instanceof Error ? err.message : String(err)}`,
|
|
770
|
+
fix: 'Reinstall @astryxdesign/cli.',
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Every topic reads progressively, in every language it ships: it loads, its
|
|
777
|
+
* section index and each of its sections fit in one read, and no contributed
|
|
778
|
+
* doc is invalid.
|
|
779
|
+
* @param {DoctorContext | Partial<DoctorContext>} ctx
|
|
780
|
+
* @returns {Promise<DoctorCheck>}
|
|
781
|
+
*/
|
|
782
|
+
export async function checkDocsProgressiveDisclosure(ctx) {
|
|
783
|
+
const id = 'docs-progressive-disclosure';
|
|
784
|
+
const label = 'Documentation navigation and size';
|
|
785
|
+
const budget = kilobytes(DOC_OUTPUT_BUDGET_BYTES);
|
|
786
|
+
/** @type {string[]} */
|
|
787
|
+
const problems = [];
|
|
788
|
+
if (ctx.docsCatalogError) {
|
|
789
|
+
problems.push(`The docs catalog could not be built: ${ctx.docsCatalogError}`);
|
|
790
|
+
}
|
|
791
|
+
for (const issue of ctx.docsCatalogIssues ?? []) {
|
|
792
|
+
problems.push(`${issue.package ?? 'a contributed doc'}: ${issue.message}`);
|
|
793
|
+
}
|
|
794
|
+
let topics = 0;
|
|
795
|
+
const catalog = ctx.docsCatalog;
|
|
796
|
+
if (catalog) {
|
|
797
|
+
for (const entry of catalog.entries()) {
|
|
798
|
+
for (const lang of [null, ...overlayLanguages(entry)]) {
|
|
799
|
+
const where = lang ? `${entry.name} [${lang}]` : entry.name;
|
|
800
|
+
try {
|
|
801
|
+
const doc = detailView(await compileTopic(catalog, entry, lang));
|
|
802
|
+
if (lang == null) topics += 1;
|
|
803
|
+
const indexBytes = docsIndexBytes(buildDocsIndexData(doc));
|
|
804
|
+
if (indexBytes > DOC_OUTPUT_BUDGET_BYTES) {
|
|
805
|
+
problems.push(
|
|
806
|
+
`${where}: its section index is ${kilobytes(indexBytes)}, over the ${budget} one read may return`,
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
for (const over of oversizedDocSections(doc.sections)) {
|
|
810
|
+
problems.push(
|
|
811
|
+
`${where} ${over.key}: ${kilobytes(over.bytes)}, over the ${budget} one read may return`,
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
} catch (err) {
|
|
815
|
+
problems.push(
|
|
816
|
+
`${where}: ${err instanceof Error ? err.message : String(err)}`,
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (problems.length > 0) {
|
|
823
|
+
return {
|
|
824
|
+
id,
|
|
825
|
+
label,
|
|
826
|
+
status: 'fail',
|
|
827
|
+
message: joinProblems(problems),
|
|
828
|
+
fix: 'Fix the doc each problem names; split a section that is too large into smaller ones, each with its own key.',
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
return {
|
|
832
|
+
id,
|
|
833
|
+
label,
|
|
834
|
+
status: 'pass',
|
|
835
|
+
message: `${topics} topics: every section index and section fits in one ${budget} read.`,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
630
839
|
/**
|
|
631
840
|
* Ordered list of synchronous check functions. Append here to add a check.
|
|
632
841
|
* (checkConfig is async and is awaited separately by {@link runChecks}.)
|
|
@@ -638,6 +847,7 @@ export const SYNC_CHECKS = [
|
|
|
638
847
|
checkVersionAlignment,
|
|
639
848
|
checkThemes,
|
|
640
849
|
checkImplicitIntegrations,
|
|
850
|
+
checkProviderIdentity,
|
|
641
851
|
checkAgentDocs,
|
|
642
852
|
checkPeerDeps,
|
|
643
853
|
checkPackageManager,
|
|
@@ -669,11 +879,28 @@ export async function runChecks(options = {}) {
|
|
|
669
879
|
let configTheme = null;
|
|
670
880
|
/** @type {import('../../foundation/integrations/integrations.mjs').LoadedIntegration[]|null} */
|
|
671
881
|
let integrations = null;
|
|
882
|
+
// A docs read falls back to the built-in topics when the project cannot be
|
|
883
|
+
// read, so the docs checks do too; the config check reports the config.
|
|
884
|
+
/** @type {DocsCatalog|null} */
|
|
885
|
+
let docsCatalog = DocsCatalog.fromBuiltins();
|
|
886
|
+
/** @type {Array<{package?: string, code: string, message: string}>} */
|
|
887
|
+
let docsCatalogIssues = [];
|
|
888
|
+
/** @type {string|null} */
|
|
889
|
+
let docsCatalogError = null;
|
|
672
890
|
try {
|
|
673
891
|
const project = await Project.load(cwd);
|
|
674
892
|
configTheme =
|
|
675
893
|
/** @type {{theme?: string}} */ (project.config ?? {}).theme ?? null;
|
|
676
894
|
integrations = project.loadedIntegrations;
|
|
895
|
+
try {
|
|
896
|
+
docsCatalog = await project.docs();
|
|
897
|
+
docsCatalogIssues = (await project.issues()).filter(
|
|
898
|
+
issue => issue.code === 'invalid_doc',
|
|
899
|
+
);
|
|
900
|
+
} catch (err) {
|
|
901
|
+
docsCatalog = null;
|
|
902
|
+
docsCatalogError = err instanceof Error ? err.message : String(err);
|
|
903
|
+
}
|
|
677
904
|
} catch {
|
|
678
905
|
// Best-effort: a missing/invalid config leaves configTheme null.
|
|
679
906
|
}
|
|
@@ -686,6 +913,9 @@ export async function runChecks(options = {}) {
|
|
|
686
913
|
configPath,
|
|
687
914
|
configTheme,
|
|
688
915
|
integrations,
|
|
916
|
+
docsCatalog,
|
|
917
|
+
docsCatalogIssues,
|
|
918
|
+
docsCatalogError,
|
|
689
919
|
configError,
|
|
690
920
|
};
|
|
691
921
|
|
|
@@ -698,6 +928,8 @@ export async function runChecks(options = {}) {
|
|
|
698
928
|
checks.push(await checkConfig(ctx));
|
|
699
929
|
}
|
|
700
930
|
}
|
|
931
|
+
checks.push(await checkAuthoringDocs(ctx));
|
|
932
|
+
checks.push(await checkDocsProgressiveDisclosure(ctx));
|
|
701
933
|
|
|
702
934
|
const summary = {pass: 0, warn: 0, fail: 0, info: 0};
|
|
703
935
|
for (const c of checks) summary[c.status] += 1;
|
|
@@ -11,9 +11,13 @@ import * as fs from 'node:fs';
|
|
|
11
11
|
import * as os from 'node:os';
|
|
12
12
|
import * as path from 'node:path';
|
|
13
13
|
import {fileURLToPath} from 'node:url';
|
|
14
|
+
import {DocsCatalog} from '../../foundation/discovery/docs-discovery.mjs';
|
|
14
15
|
import {
|
|
15
16
|
doctor,
|
|
17
|
+
checkAuthoringDocs,
|
|
18
|
+
checkDocsProgressiveDisclosure,
|
|
16
19
|
checkImplicitIntegrations,
|
|
20
|
+
checkProviderIdentity,
|
|
17
21
|
checkVersionAlignment,
|
|
18
22
|
checkPackageManager,
|
|
19
23
|
} from './doctor.mjs';
|
|
@@ -214,6 +218,77 @@ describe('checkPackageManager', () => {
|
|
|
214
218
|
});
|
|
215
219
|
});
|
|
216
220
|
|
|
221
|
+
describe('checkProviderIdentity', () => {
|
|
222
|
+
/** @param {object} [fields] */
|
|
223
|
+
const loaded = (fields = {}) => ({
|
|
224
|
+
name: '@acme/widgets',
|
|
225
|
+
providerId: '@acme/widgets',
|
|
226
|
+
version: '1.0.0',
|
|
227
|
+
__spec: '@acme/widgets',
|
|
228
|
+
__packageDir: '/abs/node_modules/@acme/widgets',
|
|
229
|
+
__manifestFile: '/abs/node_modules/@acme/widgets/astryx.integration.mjs',
|
|
230
|
+
...fields,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('skips when the project could not be read', () => {
|
|
234
|
+
expect(checkProviderIdentity({integrations: null}).status).toBe('info');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('reports none when nothing is loaded', () => {
|
|
238
|
+
const c = checkProviderIdentity({integrations: []});
|
|
239
|
+
expect(c.status).toBe('info');
|
|
240
|
+
expect(c.message).toContain('None');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('passes when each loaded integration has its own provider ID', () => {
|
|
244
|
+
const c = checkProviderIdentity({
|
|
245
|
+
integrations: [
|
|
246
|
+
loaded(),
|
|
247
|
+
loaded({
|
|
248
|
+
name: '@acme/charts',
|
|
249
|
+
providerId: '@acme/charts',
|
|
250
|
+
__spec: '@acme/charts',
|
|
251
|
+
}),
|
|
252
|
+
],
|
|
253
|
+
});
|
|
254
|
+
expect(c.status).toBe('pass');
|
|
255
|
+
expect(c.message).toContain('2 loaded integrations');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('warns and names both packages when a later claimant is set aside', () => {
|
|
259
|
+
const message =
|
|
260
|
+
'@acme/renamed@2.0.0 and @acme/widgets@1.0.0 both claim provider ID ' +
|
|
261
|
+
'"@acme/widgets". @acme/widgets@1.0.0 loads first and is used; ' +
|
|
262
|
+
'@acme/renamed@2.0.0 contributes nothing until one package changes ' +
|
|
263
|
+
'its providerId.';
|
|
264
|
+
const c = checkProviderIdentity({
|
|
265
|
+
integrations: [
|
|
266
|
+
loaded(),
|
|
267
|
+
loaded({
|
|
268
|
+
name: '@acme/renamed',
|
|
269
|
+
version: '2.0.0',
|
|
270
|
+
__spec: '@acme/renamed',
|
|
271
|
+
__providerConflict: {
|
|
272
|
+
providerId: '@acme/widgets',
|
|
273
|
+
claimedBy: '@acme/widgets',
|
|
274
|
+
message,
|
|
275
|
+
},
|
|
276
|
+
}),
|
|
277
|
+
],
|
|
278
|
+
});
|
|
279
|
+
expect(c.status).toBe('warn');
|
|
280
|
+
expect(c.message).toBe(message);
|
|
281
|
+
expect(c.fix).toContain('providerId');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('is part of the report doctor returns', async () => {
|
|
285
|
+
const r = await doctor({cwd});
|
|
286
|
+
expect(r.data.checks.map(check => check.id)).toContain(
|
|
287
|
+
'provider-identity',
|
|
288
|
+
);
|
|
289
|
+
}, SLOW);
|
|
290
|
+
});
|
|
291
|
+
|
|
217
292
|
describe('checkImplicitIntegrations', () => {
|
|
218
293
|
/** @param {object} [fields] */
|
|
219
294
|
const autolinked = (fields = {}) => ({
|
|
@@ -297,3 +372,124 @@ describe('checkImplicitIntegrations', () => {
|
|
|
297
372
|
expect(r.data.checks.map(c => c.id)).toContain('implicit-integrations');
|
|
298
373
|
}, SLOW);
|
|
299
374
|
});
|
|
375
|
+
|
|
376
|
+
describe('checkDocsProgressiveDisclosure', () => {
|
|
377
|
+
it('passes when every topic index and section fits one read', async () => {
|
|
378
|
+
const c = await checkDocsProgressiveDisclosure({
|
|
379
|
+
docsCatalog: DocsCatalog.fromBuiltins(),
|
|
380
|
+
docsCatalogIssues: [],
|
|
381
|
+
});
|
|
382
|
+
expect(c).toMatchObject({id: 'docs-progressive-disclosure', status: 'pass'});
|
|
383
|
+
expect(c.message).toMatch(/^\d+ topics: /);
|
|
384
|
+
}, SLOW);
|
|
385
|
+
|
|
386
|
+
it('fails on an invalid doc an integration contributed', async () => {
|
|
387
|
+
const c = await checkDocsProgressiveDisclosure({
|
|
388
|
+
docsCatalogIssues: [
|
|
389
|
+
{
|
|
390
|
+
package: '@acme/widgets',
|
|
391
|
+
code: 'invalid_doc',
|
|
392
|
+
severity: 'error',
|
|
393
|
+
message: 'bad.doc.mjs exports no doc',
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
});
|
|
397
|
+
expect(c.status).toBe('fail');
|
|
398
|
+
expect(c.message).toBe('@acme/widgets: bad.doc.mjs exports no doc');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('fails when the docs catalog cannot be built', async () => {
|
|
402
|
+
const c = await checkDocsProgressiveDisclosure({docsCatalogError: 'boom'});
|
|
403
|
+
expect(c.status).toBe('fail');
|
|
404
|
+
expect(c.message).toContain('boom');
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('names a section over the budget and a topic that fails to load', async () => {
|
|
408
|
+
const dir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-doctor-docs-'));
|
|
409
|
+
tmpDirs.push(dir);
|
|
410
|
+
const huge = {
|
|
411
|
+
name: 'huge',
|
|
412
|
+
title: 'Huge',
|
|
413
|
+
description: 'Too big for one read.',
|
|
414
|
+
sections: [
|
|
415
|
+
{title: 'Small', content: [{type: 'prose', text: 'Fits.'}]},
|
|
416
|
+
{title: 'Everything', content: [{type: 'prose', text: 'x'.repeat(40 * 1024)}]},
|
|
417
|
+
],
|
|
418
|
+
};
|
|
419
|
+
fs.writeFileSync(
|
|
420
|
+
path.join(dir, 'huge.doc.mjs'),
|
|
421
|
+
`export const docs = ${JSON.stringify(huge)};\n`,
|
|
422
|
+
);
|
|
423
|
+
fs.writeFileSync(path.join(dir, 'broken.doc.mjs'), 'export const docs = {;\n');
|
|
424
|
+
const c = await checkDocsProgressiveDisclosure({
|
|
425
|
+
docsCatalog: DocsCatalog.fromBuiltins({
|
|
426
|
+
huge: path.join(dir, 'huge.doc.mjs'),
|
|
427
|
+
broken: path.join(dir, 'broken.doc.mjs'),
|
|
428
|
+
}),
|
|
429
|
+
docsCatalogIssues: [],
|
|
430
|
+
});
|
|
431
|
+
expect(c.status).toBe('fail');
|
|
432
|
+
expect(c.message).toMatch(/^2 problems: /);
|
|
433
|
+
expect(c.message).toContain('huge everything: 41 KB, over the 32 KB one read may return');
|
|
434
|
+
expect(c.message).toContain('broken: ');
|
|
435
|
+
expect(c.message).not.toContain('huge small');
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
describe('checkAuthoringDocs', () => {
|
|
440
|
+
it('passes when every authoring self-doc is reachable and fits one read', async () => {
|
|
441
|
+
const c = await checkAuthoringDocs();
|
|
442
|
+
expect(c).toMatchObject({id: 'authoring-docs', status: 'pass'});
|
|
443
|
+
expect(c.message).toContain('astryx docs authoring');
|
|
444
|
+
}, SLOW);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
describe('doctor docs checks', () => {
|
|
448
|
+
it('runs both docs checks and they pass on the repo', async () => {
|
|
449
|
+
const r = await doctor({cwd});
|
|
450
|
+
const byId = Object.fromEntries(r.data.checks.map(c => [c.id, c.status]));
|
|
451
|
+
expect(byId['authoring-docs']).toBe('pass');
|
|
452
|
+
expect(byId['docs-progressive-disclosure']).toBe('pass');
|
|
453
|
+
}, SLOW);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
describe('checkDocsProgressiveDisclosure languages', () => {
|
|
457
|
+
it('checks every overlay a topic ships, not only English', async () => {
|
|
458
|
+
const dir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-doctor-lang-'));
|
|
459
|
+
tmpDirs.push(dir);
|
|
460
|
+
const deploying = {
|
|
461
|
+
name: 'deploying',
|
|
462
|
+
title: 'Deploying',
|
|
463
|
+
description: 'Ship it.',
|
|
464
|
+
sections: [{title: 'Overview', content: [{type: 'prose', text: 'Push the button.'}]}],
|
|
465
|
+
};
|
|
466
|
+
fs.writeFileSync(
|
|
467
|
+
path.join(dir, 'deploying.doc.mjs'),
|
|
468
|
+
`export const docs = ${JSON.stringify(deploying)};\n`,
|
|
469
|
+
);
|
|
470
|
+
fs.writeFileSync(
|
|
471
|
+
path.join(dir, 'deploying.doc.zh.mjs'),
|
|
472
|
+
"throw new Error('zh overlay broken');\n",
|
|
473
|
+
);
|
|
474
|
+
fs.writeFileSync(
|
|
475
|
+
path.join(dir, 'deploying.doc.dense.mjs'),
|
|
476
|
+
`export const docsDense = ${JSON.stringify({
|
|
477
|
+
sections: [
|
|
478
|
+
{
|
|
479
|
+
section: 'Overview',
|
|
480
|
+
title: 'Overview',
|
|
481
|
+
content: [{type: 'prose', text: 'x'.repeat(40 * 1024)}],
|
|
482
|
+
},
|
|
483
|
+
],
|
|
484
|
+
})};\n`,
|
|
485
|
+
);
|
|
486
|
+
const c = await checkDocsProgressiveDisclosure({
|
|
487
|
+
docsCatalog: DocsCatalog.fromBuiltins({deploying: path.join(dir, 'deploying.doc.mjs')}),
|
|
488
|
+
docsCatalogIssues: [],
|
|
489
|
+
});
|
|
490
|
+
expect(c.status).toBe('fail');
|
|
491
|
+
expect(c.message).toContain('deploying [zh]: zh overlay broken');
|
|
492
|
+
expect(c.message).toContain('deploying [dense] overview: 41 KB');
|
|
493
|
+
expect(c.message).not.toMatch(/deploying overview:/);
|
|
494
|
+
});
|
|
495
|
+
});
|
package/api/hook/hook.type.d.mts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
export type HookDoc = import("@astryxdesign/cli/authoring").HookDoc;
|
|
5
5
|
export type HookParamDoc = import("@astryxdesign/cli/authoring").HookParamDoc;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* astryx --json hook [--list] [--category X] [--detail names|compact|full]
|
|
8
8
|
*
|
|
9
9
|
* The list view emits ONE `hook.list` type across all three detail levels; the
|
|
10
10
|
* depth is carried in `data.detail` and `data.components` holds the grouped map
|
|
@@ -39,14 +39,14 @@ export type HookBriefEntry = {
|
|
|
39
39
|
import: string;
|
|
40
40
|
};
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
42
|
+
* astryx --json hook <name>
|
|
43
43
|
*/
|
|
44
44
|
export type HookDetailResponse = {
|
|
45
45
|
type: "hook.detail";
|
|
46
46
|
data: HookDoc;
|
|
47
47
|
};
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
49
|
+
* astryx --json hook <name> --params
|
|
50
50
|
*/
|
|
51
51
|
export type HookDetailParamsResponse = {
|
|
52
52
|
type: "hook.detail.params";
|
package/api/hook/hook.type.mjs
CHANGED
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Invocation -> type discriminator
|
|
13
13
|
* ------------------------------------------------------------------
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* (not found)
|
|
14
|
+
* astryx --json hook -> hook.list (data.detail='names')
|
|
15
|
+
* astryx --json hook --list -> hook.list (data.detail='names')
|
|
16
|
+
* astryx --json hook --category State -> hook.list (filtered)
|
|
17
|
+
* astryx --json hook --list --detail compact -> hook.list (data.detail='compact')
|
|
18
|
+
* astryx --json hook --list --detail full -> hook.list (data.detail='full')
|
|
19
|
+
* astryx --json hook useMediaQuery -> hook.detail
|
|
20
|
+
* astryx --json hook useMediaQuery --params -> hook.detail.params
|
|
21
|
+
* (not found) -> CLIError
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
// Re-export the authored-doc types from core so the hook leaves reference these
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
/** @typedef {import('@astryxdesign/cli/authoring').HookParamDoc} HookParamDoc */
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
31
|
+
* astryx --json hook [--list] [--category X] [--detail names|compact|full]
|
|
32
32
|
*
|
|
33
33
|
* The list view emits ONE `hook.list` type across all three detail levels; the
|
|
34
34
|
* depth is carried in `data.detail` and `data.components` holds the grouped map
|
|
@@ -56,14 +56,14 @@
|
|
|
56
56
|
*/
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
59
|
+
* astryx --json hook <name>
|
|
60
60
|
* @typedef {object} HookDetailResponse
|
|
61
61
|
* @property {'hook.detail'} type
|
|
62
62
|
* @property {HookDoc} data
|
|
63
63
|
*/
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* astryx --json hook <name> --params
|
|
67
67
|
* @typedef {object} HookDetailParamsResponse
|
|
68
68
|
* @property {'hook.detail.params'} type
|
|
69
69
|
* @property {HookParamDoc[]} data
|
package/api/hook/list/list.d.mts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
export function list({ cwd, category, detail, zh, lang }?: {
|
|
14
14
|
cwd?: string | undefined;
|
|
15
15
|
category?: string | undefined;
|
|
16
|
-
detail?: "
|
|
16
|
+
detail?: "compact" | "full" | "names" | "brief" | undefined;
|
|
17
17
|
zh?: boolean | undefined;
|
|
18
18
|
lang?: string | null | undefined;
|
|
19
19
|
}): Promise<import("../hook.type.mjs").HookListResponse>;
|
|
@@ -250,7 +250,8 @@ async function addComponent(name, options) {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
const sourcePath = projectPath(path.relative(packageDir, sourceFile));
|
|
253
|
-
const
|
|
253
|
+
const extensionlessPath = sourcePath.replace(/\.tsx?$/u, '');
|
|
254
|
+
const importSpecifier = `${owner}/${extensionlessPath}`;
|
|
254
255
|
const docContents = `export default {\n type: 'component',\n name: '${name}',\n import: ${JSON.stringify(importSpecifier)},\n description: '${name} component.',\n props: [],\n};\n`;
|
|
255
256
|
const sourceContents = `export function ${name}() {\n return <div>${name}</div>;\n}\n`;
|
|
256
257
|
|
|
@@ -263,7 +264,7 @@ async function addComponent(name, options) {
|
|
|
263
264
|
packageFile,
|
|
264
265
|
rootPath,
|
|
265
266
|
path.basename(manifestFile),
|
|
266
|
-
[{subpath:
|
|
267
|
+
[{subpath: extensionlessPath, target: sourcePath}],
|
|
267
268
|
);
|
|
268
269
|
if (pkgUpdate != null) {
|
|
269
270
|
plans.push({
|
|
@@ -576,6 +577,7 @@ async function addTemplate(name, options) {
|
|
|
576
577
|
|
|
577
578
|
const pascalName = kebabToPascal(name);
|
|
578
579
|
const sourcePath = projectPath(path.relative(packageDir, sourceFile));
|
|
580
|
+
const extensionlessPath = sourcePath.replace(/\.tsx?$/u, '');
|
|
579
581
|
const specContents = `export default {\n type: '${templateType}',\n name: '${name}',\n description: '${kebabToTitle(name)} template.',\n};\n`;
|
|
580
582
|
const sourceContents = `export default function ${pascalName}() {\n return <div>${kebabToTitle(name)}</div>;\n}\n`;
|
|
581
583
|
|
|
@@ -588,7 +590,7 @@ async function addTemplate(name, options) {
|
|
|
588
590
|
packageFile,
|
|
589
591
|
rootPath,
|
|
590
592
|
path.basename(manifestFile),
|
|
591
|
-
[{subpath:
|
|
593
|
+
[{subpath: extensionlessPath, target: sourcePath}],
|
|
592
594
|
);
|
|
593
595
|
if (pkgUpdate != null) {
|
|
594
596
|
plans.push({
|
|
@@ -118,14 +118,14 @@ describe('integrationAdd component', () => {
|
|
|
118
118
|
);
|
|
119
119
|
expect(pkg.exports).toEqual({
|
|
120
120
|
'.': './index.mjs',
|
|
121
|
-
'./components/MyWidget
|
|
121
|
+
'./components/MyWidget': './components/MyWidget.tsx',
|
|
122
122
|
});
|
|
123
123
|
expect(
|
|
124
124
|
fs.readFileSync(
|
|
125
125
|
path.join(tmpDir, 'components/MyWidget.doc.mjs'),
|
|
126
126
|
'utf-8',
|
|
127
127
|
),
|
|
128
|
-
).toContain('import: "@acme/integration/components/MyWidget
|
|
128
|
+
).toContain('import: "@acme/integration/components/MyWidget"');
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
it('does not create exports when the package has no exports map', async () => {
|
|
@@ -143,7 +143,7 @@ describe('integrationAdd component', () => {
|
|
|
143
143
|
setup({
|
|
144
144
|
exports: {
|
|
145
145
|
'.': './index.mjs',
|
|
146
|
-
'./components/MyWidget
|
|
146
|
+
'./components/MyWidget': './different.tsx',
|
|
147
147
|
},
|
|
148
148
|
});
|
|
149
149
|
|
|
@@ -421,7 +421,7 @@ describe('integrationAdd template', () => {
|
|
|
421
421
|
);
|
|
422
422
|
expect(pkg.exports).toEqual({
|
|
423
423
|
'.': './index.mjs',
|
|
424
|
-
'./templates/my-widget
|
|
424
|
+
'./templates/my-widget': './templates/my-widget.tsx',
|
|
425
425
|
});
|
|
426
426
|
});
|
|
427
427
|
|
|
@@ -26,7 +26,7 @@ export type IntegrationAddThemeOptions = IntegrationAddBaseOptions;
|
|
|
26
26
|
export type IntegrationAddOptions = {
|
|
27
27
|
cwd?: string | undefined;
|
|
28
28
|
dryRun?: boolean | undefined;
|
|
29
|
-
templateType?: "
|
|
29
|
+
templateType?: "page" | "block" | undefined;
|
|
30
30
|
replaces?: string | undefined;
|
|
31
31
|
extends?: string | undefined;
|
|
32
32
|
to?: string | undefined;
|