@astryxdesign/cli 0.7.2-canary.e2909cf → 0.7.2-canary.e4bdd70
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 +15 -14
- package/api/build/help/help.mjs +1 -1
- package/api/component/_adapter.d.mts +41 -1
- package/api/component/_adapter.mjs +93 -7
- package/api/component/component.mjs +22 -5
- package/api/component/integration-export-name.test.mjs +644 -0
- 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 +67 -4
- 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 +51 -97
- package/api/integration/pack-check.test.mjs +359 -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.d.mts +22 -6
- package/api/search/search.domain-priority.test.mjs +93 -18
- package/api/search/search.integration-check.test.mjs +24 -0
- package/api/search/search.mjs +133 -22
- package/api/swizzle/copy/copy.mjs +28 -14
- 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/styling-overview.doc.mjs +1 -1
- 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 +11 -1
- package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
- package/assets/docs/tree/add-a-topic.doc.mjs +10 -7
- package/assets/docs/tree/agent-guidance.doc.mjs +2 -2
- package/assets/docs/tree/block-template.doc.mjs +8 -20
- package/assets/docs/tree/build-the-template.doc.mjs +1 -1
- package/assets/docs/tree/check-the-props.doc.mjs +196 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +51 -5
- package/assets/docs/tree/checks.doc.mjs +24 -16
- package/assets/docs/tree/codemods.doc.mjs +4 -4
- package/assets/docs/tree/component-family.doc.mjs +8 -6
- package/assets/docs/tree/component-imports.doc.mjs +10 -2
- package/assets/docs/tree/components.doc.mjs +1 -1
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +7 -18
- package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
- package/assets/docs/tree/describe-the-component.doc.mjs +2 -2
- package/assets/docs/tree/docs.doc.mjs +3 -3
- package/assets/docs/tree/document-the-template.doc.mjs +1 -1
- package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
- package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
- package/assets/docs/tree/extend-or-replace.doc.mjs +6 -6
- package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
- package/assets/docs/tree/generate-a-palette.doc.mjs +5 -3
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +1 -1
- package/assets/docs/tree/integrations.doc.mjs +8 -0
- package/assets/docs/tree/links.doc.mjs +6 -5
- package/assets/docs/tree/package-and-test.doc.mjs +1 -1
- package/assets/docs/tree/page-template.doc.mjs +4 -8
- package/assets/docs/tree/publishing.doc.mjs +1 -1
- package/assets/docs/tree/quick-start.doc.mjs +37 -21
- package/assets/docs/tree/replace-a-core-component.doc.mjs +4 -6
- package/assets/docs/tree/replace-a-core-template.doc.mjs +6 -8
- package/assets/docs/tree/sections-and-placement.doc.mjs +4 -6
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +4 -2
- package/assets/docs/tree/short-and-findable.doc.mjs +6 -5
- package/assets/docs/tree/single-component.doc.mjs +15 -6
- package/assets/docs/tree/start-a-template.doc.mjs +3 -3
- package/assets/docs/tree/subcomponent.doc.mjs +6 -6
- package/assets/docs/tree/template-assets.doc.mjs +2 -2
- package/assets/docs/tree/template-doc-overview.doc.mjs +4 -15
- package/assets/docs/tree/template-fonts.doc.mjs +1 -1
- package/assets/docs/tree/template-icons.doc.mjs +1 -1
- package/assets/docs/tree/template-images-media.doc.mjs +1 -1
- package/assets/docs/tree/template-styles.doc.mjs +2 -2
- package/assets/docs/tree/templates.doc.mjs +1 -1
- package/assets/docs/tree/test-in-an-app.doc.mjs +3 -3
- package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
- package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
- package/assets/docs/tree/troubleshooting.doc.mjs +13 -5
- package/assets/docs/tree/type-scale.doc.mjs +3 -3
- package/assets/docs/tree/upgrading.doc.mjs +3 -3
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +3 -1
- package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
- package/assets/docs/tree/versioning.doc.mjs +7 -6
- package/assets/docs/tree/write-the-template-file.doc.mjs +4 -3
- package/assets/docs/use-a-theme.doc.mjs +2 -2
- package/assets/theme.template.ts +1 -1
- package/authoring/codemod/codemod.doc.mjs +7 -3
- package/authoring/config/config.doc.mjs +9 -6
- package/authoring/config/type.ts +4 -2
- package/authoring/discover/discover.doc.mjs +1 -1
- package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
- package/authoring/doctypes/command/command.doc.mjs +14 -12
- package/authoring/doctypes/component/component.doc.mjs +29 -26
- package/authoring/doctypes/component/type.ts +13 -8
- package/authoring/doctypes/enum/enum.doc.mjs +7 -4
- package/authoring/doctypes/function/function.doc.mjs +9 -8
- package/authoring/doctypes/hook/hook.doc.mjs +5 -4
- package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
- package/authoring/doctypes/reference/reference.doc.mjs +11 -10
- package/authoring/doctypes/schema/schema.doc.mjs +9 -9
- 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 +3 -3
- package/authoring/doctypes/template/type.ts +22 -0
- package/authoring/doctypes/theme/theme.doc.mjs +2 -2
- package/authoring/gap-report/gap-report.doc.mjs +2 -2
- package/authoring/identity/identity.doc.mjs +3 -3
- package/authoring/index.d.ts +1 -0
- package/authoring/integration/integration.doc.mjs +12 -4
- 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 +2 -1
- package/foundation/discovery/component-discovery.d.mts +178 -10
- package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
- package/foundation/discovery/component-discovery.mjs +659 -62
- package/foundation/discovery/component-replacement.mjs +18 -0
- package/foundation/discovery/component-replacement.test.mjs +40 -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 +27 -0
- package/foundation/discovery/module-exports.mjs +356 -31
- package/foundation/discovery/module-exports.test.mjs +172 -0
- 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
|
@@ -754,6 +754,285 @@ describe('integrationPackCheck', () => {
|
|
|
754
754
|
);
|
|
755
755
|
});
|
|
756
756
|
|
|
757
|
+
it('checks the export a component doc names when its file is named otherwise', async () => {
|
|
758
|
+
fs.writeFileSync(
|
|
759
|
+
path.join(tmpDir, 'package.json'),
|
|
760
|
+
`${JSON.stringify({
|
|
761
|
+
name: '@acme/widgets',
|
|
762
|
+
version: '1.0.0',
|
|
763
|
+
files: ['astryx.integration.mjs', 'components'],
|
|
764
|
+
exports: {'./components/Slides': './components/Slides.tsx'},
|
|
765
|
+
})}\n`,
|
|
766
|
+
);
|
|
767
|
+
fs.writeFileSync(
|
|
768
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
769
|
+
"export default {components: './components'};\n",
|
|
770
|
+
);
|
|
771
|
+
fs.mkdirSync(path.join(tmpDir, 'components'));
|
|
772
|
+
// Slides.tsx exports AcmeCarousel and nothing named Slides.
|
|
773
|
+
fs.writeFileSync(
|
|
774
|
+
path.join(tmpDir, 'components', 'Slides.tsx'),
|
|
775
|
+
'export function AcmeCarousel() { return null; }\n',
|
|
776
|
+
);
|
|
777
|
+
fs.writeFileSync(
|
|
778
|
+
path.join(tmpDir, 'components', 'Slides.doc.mjs'),
|
|
779
|
+
"export default {type: 'component', name: 'AcmeCarousel', import: '@acme/widgets/components/Slides', description: 'x', props: []};\n",
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
783
|
+
expect(result.data.packable).toBe(true);
|
|
784
|
+
expect(result.data.contributions.packed.components).toEqual([
|
|
785
|
+
'AcmeCarousel',
|
|
786
|
+
]);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
it('checks each family member against the module its family doc imports', async () => {
|
|
790
|
+
fs.writeFileSync(
|
|
791
|
+
path.join(tmpDir, 'package.json'),
|
|
792
|
+
`${JSON.stringify({
|
|
793
|
+
name: '@acme/widgets',
|
|
794
|
+
version: '1.0.0',
|
|
795
|
+
files: ['astryx.integration.mjs', 'components'],
|
|
796
|
+
exports: {'./tabs': './components/tabs/index.ts'},
|
|
797
|
+
})}\n`,
|
|
798
|
+
);
|
|
799
|
+
fs.writeFileSync(
|
|
800
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
801
|
+
"export default {components: './components'};\n",
|
|
802
|
+
);
|
|
803
|
+
const dir = path.join(tmpDir, 'components', 'tabs');
|
|
804
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
805
|
+
fs.writeFileSync(
|
|
806
|
+
path.join(dir, 'AcmeTabs.tsx'),
|
|
807
|
+
'export function AcmeTabs() { return null; }\nexport function AcmeTabsLegacy() { return null; }\n',
|
|
808
|
+
);
|
|
809
|
+
fs.writeFileSync(
|
|
810
|
+
path.join(dir, 'tab.tsx'),
|
|
811
|
+
'export function AcmeTab() { return null; }\n',
|
|
812
|
+
);
|
|
813
|
+
// The module apps import exports AcmeTabs and AcmeTab, not AcmeTabsLegacy.
|
|
814
|
+
fs.writeFileSync(
|
|
815
|
+
path.join(dir, 'index.ts'),
|
|
816
|
+
"export {AcmeTabs} from './AcmeTabs';\nexport {AcmeTab} from './tab';\n",
|
|
817
|
+
);
|
|
818
|
+
fs.writeFileSync(
|
|
819
|
+
path.join(dir, 'AcmeTabs.doc.mjs'),
|
|
820
|
+
"export default {type: 'component', name: 'AcmeTabs', import: '@acme/widgets/tabs', description: 'x', components: [{name: 'AcmeTabs', description: 'x', props: []}, {name: 'AcmeTab', description: 'x', props: []}, {name: 'AcmeTabsLegacy', description: 'x', props: []}]};\n",
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
824
|
+
|
|
825
|
+
expect(result.data.issues).toEqual([]);
|
|
826
|
+
expect(result.data.packable).toBe(true);
|
|
827
|
+
expect(result.data.contributions.packed.components).toEqual([
|
|
828
|
+
'AcmeTab',
|
|
829
|
+
'AcmeTabs',
|
|
830
|
+
]);
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
it.each([
|
|
834
|
+
'a clean checkout',
|
|
835
|
+
'a stale build',
|
|
836
|
+
'a stale build that exports more',
|
|
837
|
+
])(
|
|
838
|
+
'reads members a family imports from output its pack lifecycle builds, from %s',
|
|
839
|
+
async start => {
|
|
840
|
+
fs.writeFileSync(
|
|
841
|
+
path.join(tmpDir, 'package.json'),
|
|
842
|
+
`${JSON.stringify({
|
|
843
|
+
name: '@acme/widgets',
|
|
844
|
+
version: '1.0.0',
|
|
845
|
+
files: ['astryx.integration.mjs', 'components', 'dist'],
|
|
846
|
+
exports: {'./tabs': './dist/tabs/index.mjs'},
|
|
847
|
+
scripts: {prepack: 'node build.mjs'},
|
|
848
|
+
})}\n`,
|
|
849
|
+
);
|
|
850
|
+
fs.writeFileSync(
|
|
851
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
852
|
+
"export default {components: './components'};\n",
|
|
853
|
+
);
|
|
854
|
+
// The build writes the module the family imports; a clean checkout has none.
|
|
855
|
+
fs.writeFileSync(
|
|
856
|
+
path.join(tmpDir, 'build.mjs'),
|
|
857
|
+
"import fs from 'node:fs';\nfs.mkdirSync('dist/tabs', {recursive: true});\nfs.writeFileSync('dist/tabs/index.mjs', \"export {AcmeTabs, AcmeTab} from '../../components/tabs/AcmeTabs.tsx';\\n\");\n",
|
|
858
|
+
);
|
|
859
|
+
const dir = path.join(tmpDir, 'components', 'tabs');
|
|
860
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
861
|
+
fs.writeFileSync(
|
|
862
|
+
path.join(dir, 'AcmeTabs.tsx'),
|
|
863
|
+
'export function AcmeTabs() { return null; }\nexport function AcmeTab() { return null; }\nexport function AcmeTabLegacy() { return null; }\n',
|
|
864
|
+
);
|
|
865
|
+
fs.writeFileSync(
|
|
866
|
+
path.join(dir, 'AcmeTabs.doc.mjs'),
|
|
867
|
+
"export default {type: 'component', name: 'AcmeTabs', import: '@acme/widgets/tabs', description: 'x', components: [{name: 'AcmeTabs', description: 'x', props: []}, {name: 'AcmeTab', description: 'x', props: []}, {name: 'AcmeTabLegacy', description: 'x', props: []}]};\n",
|
|
868
|
+
);
|
|
869
|
+
|
|
870
|
+
if (start !== 'a clean checkout') {
|
|
871
|
+
// An earlier build: before AcmeTab was exported, or while AcmeTabLegacy
|
|
872
|
+
// still was.
|
|
873
|
+
const names =
|
|
874
|
+
start === 'a stale build'
|
|
875
|
+
? 'AcmeTabs'
|
|
876
|
+
: 'AcmeTabs, AcmeTab, AcmeTabLegacy';
|
|
877
|
+
fs.mkdirSync(path.join(tmpDir, 'dist', 'tabs'), {recursive: true});
|
|
878
|
+
fs.writeFileSync(
|
|
879
|
+
path.join(tmpDir, 'dist', 'tabs', 'index.mjs'),
|
|
880
|
+
`export {${names}} from '../../components/tabs/AcmeTabs.tsx';\n`,
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
885
|
+
|
|
886
|
+
expect(result.data.issues).toEqual([]);
|
|
887
|
+
expect(result.data.packable).toBe(true);
|
|
888
|
+
expect(result.data.contributions.local.components).toEqual(
|
|
889
|
+
result.data.contributions.packed.components,
|
|
890
|
+
);
|
|
891
|
+
},
|
|
892
|
+
);
|
|
893
|
+
|
|
894
|
+
it.each(['a clean checkout', 'an incremental build'])(
|
|
895
|
+
'reads members built output re-exports from compiled files, from %s',
|
|
896
|
+
async start => {
|
|
897
|
+
fs.writeFileSync(
|
|
898
|
+
path.join(tmpDir, 'package.json'),
|
|
899
|
+
`${JSON.stringify({
|
|
900
|
+
name: '@acme/widgets',
|
|
901
|
+
version: '1.0.0',
|
|
902
|
+
files: ['astryx.integration.mjs', 'components', 'dist'],
|
|
903
|
+
exports: {'./tabs': './dist/tabs/index.mjs'},
|
|
904
|
+
scripts: {prepack: 'node build.mjs'},
|
|
905
|
+
})}\n`,
|
|
906
|
+
);
|
|
907
|
+
fs.writeFileSync(
|
|
908
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
909
|
+
"export default {components: './components'};\n",
|
|
910
|
+
);
|
|
911
|
+
// The build compiles each source to its own file and, like an
|
|
912
|
+
// incremental build, rewrites only the files whose output changed.
|
|
913
|
+
const output = {
|
|
914
|
+
'index.mjs':
|
|
915
|
+
"export {AcmeTabs} from './tabs.mjs';\nexport * from './tab.mjs';\n",
|
|
916
|
+
'tabs.mjs': 'export function AcmeTabs() { return null; }\n',
|
|
917
|
+
'tab.mjs': 'export function AcmeTab() { return null; }\n',
|
|
918
|
+
};
|
|
919
|
+
fs.writeFileSync(
|
|
920
|
+
path.join(tmpDir, 'build.mjs'),
|
|
921
|
+
`import fs from 'node:fs';\nconst output = ${JSON.stringify(output)};\nfs.mkdirSync('dist/tabs', {recursive: true});\nfor (const [file, text] of Object.entries(output)) {\n const target = 'dist/tabs/' + file;\n if (!fs.existsSync(target) || fs.readFileSync(target, 'utf-8') !== text) fs.writeFileSync(target, text);\n}\n`,
|
|
922
|
+
);
|
|
923
|
+
const dir = path.join(tmpDir, 'components', 'tabs');
|
|
924
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
925
|
+
fs.writeFileSync(
|
|
926
|
+
path.join(dir, 'AcmeTabs.tsx'),
|
|
927
|
+
'export function AcmeTabs() { return null; }\n',
|
|
928
|
+
);
|
|
929
|
+
fs.writeFileSync(
|
|
930
|
+
path.join(dir, 'AcmeTab.tsx'),
|
|
931
|
+
'export function AcmeTab() { return null; }\n',
|
|
932
|
+
);
|
|
933
|
+
fs.writeFileSync(
|
|
934
|
+
path.join(dir, 'AcmeTabs.doc.mjs'),
|
|
935
|
+
"export default {type: 'component', name: 'AcmeTabs', import: '@acme/widgets/tabs', description: 'x', components: [{name: 'AcmeTabs', description: 'x', props: []}, {name: 'AcmeTab', description: 'x', props: []}]};\n",
|
|
936
|
+
);
|
|
937
|
+
if (start === 'an incremental build') {
|
|
938
|
+
// An earlier build from before AcmeTab existed: the file the import
|
|
939
|
+
// names is already current, so the build leaves it untouched.
|
|
940
|
+
fs.mkdirSync(path.join(tmpDir, 'dist', 'tabs'), {recursive: true});
|
|
941
|
+
for (const [file, text] of Object.entries({...output, 'tab.mjs': ''})) {
|
|
942
|
+
fs.writeFileSync(path.join(tmpDir, 'dist', 'tabs', file), text);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
947
|
+
|
|
948
|
+
expect(result.data.issues).toEqual([]);
|
|
949
|
+
expect(result.data.packable).toBe(true);
|
|
950
|
+
expect(result.data.contributions.packed.components).toEqual([
|
|
951
|
+
'AcmeTab',
|
|
952
|
+
'AcmeTabs',
|
|
953
|
+
]);
|
|
954
|
+
expect(result.data.contributions.local.components).toEqual(
|
|
955
|
+
result.data.contributions.packed.components,
|
|
956
|
+
);
|
|
957
|
+
},
|
|
958
|
+
);
|
|
959
|
+
|
|
960
|
+
it('passes a member another folder declares, which has no source', async () => {
|
|
961
|
+
fs.writeFileSync(
|
|
962
|
+
path.join(tmpDir, 'package.json'),
|
|
963
|
+
`${JSON.stringify({
|
|
964
|
+
name: '@acme/widgets',
|
|
965
|
+
version: '1.0.0',
|
|
966
|
+
files: ['astryx.integration.mjs', 'components'],
|
|
967
|
+
exports: {'./menu': './components/menu/index.ts'},
|
|
968
|
+
})}\n`,
|
|
969
|
+
);
|
|
970
|
+
fs.writeFileSync(
|
|
971
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
972
|
+
"export default {components: './components'};\n",
|
|
973
|
+
);
|
|
974
|
+
const menu = path.join(tmpDir, 'components', 'menu');
|
|
975
|
+
const shared = path.join(tmpDir, 'components', 'shared');
|
|
976
|
+
fs.mkdirSync(menu, {recursive: true});
|
|
977
|
+
fs.mkdirSync(shared, {recursive: true});
|
|
978
|
+
fs.writeFileSync(
|
|
979
|
+
path.join(menu, 'AcmeMenu.doc.mjs'),
|
|
980
|
+
"export default {type: 'component', name: 'AcmeMenu', import: '@acme/widgets/menu', description: 'x', components: [{name: 'AcmeMenuItem', description: 'x', props: []}]};\n",
|
|
981
|
+
);
|
|
982
|
+
fs.writeFileSync(
|
|
983
|
+
path.join(menu, 'AcmeMenu.tsx'),
|
|
984
|
+
'export function AcmeMenu() { return null; }\n',
|
|
985
|
+
);
|
|
986
|
+
fs.writeFileSync(
|
|
987
|
+
path.join(menu, 'index.ts'),
|
|
988
|
+
"export {AcmeMenu} from './AcmeMenu';\nexport {AcmeMenuItem} from '../shared/AcmeMenuItem';\n",
|
|
989
|
+
);
|
|
990
|
+
fs.writeFileSync(
|
|
991
|
+
path.join(shared, 'AcmeMenuItem.tsx'),
|
|
992
|
+
'export function AcmeMenuItem() { return null; }\n',
|
|
993
|
+
);
|
|
994
|
+
|
|
995
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
996
|
+
|
|
997
|
+
expect(result.data.issues).toEqual([]);
|
|
998
|
+
expect(result.data.packable).toBe(true);
|
|
999
|
+
expect(result.data.contributions.packed.components).toEqual([
|
|
1000
|
+
'AcmeMenu',
|
|
1001
|
+
'AcmeMenuItem',
|
|
1002
|
+
]);
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
it('reports a family doc the loader rejects once, not once per member', async () => {
|
|
1006
|
+
fs.writeFileSync(
|
|
1007
|
+
path.join(tmpDir, 'package.json'),
|
|
1008
|
+
`${JSON.stringify({
|
|
1009
|
+
name: '@acme/widgets',
|
|
1010
|
+
version: '1.0.0',
|
|
1011
|
+
files: ['astryx.integration.mjs', 'components'],
|
|
1012
|
+
})}\n`,
|
|
1013
|
+
);
|
|
1014
|
+
fs.writeFileSync(
|
|
1015
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
1016
|
+
"export default {components: './components'};\n",
|
|
1017
|
+
);
|
|
1018
|
+
fs.mkdirSync(path.join(tmpDir, 'components'));
|
|
1019
|
+
fs.writeFileSync(
|
|
1020
|
+
path.join(tmpDir, 'components', 'AcmeTabs.tsx'),
|
|
1021
|
+
'export function AcmeTabs() { return null; }\nexport function AcmeTab() { return null; }\nexport function AcmeTabPanel() { return null; }\n',
|
|
1022
|
+
);
|
|
1023
|
+
// A displayName that is not a string makes the loader reject the doc.
|
|
1024
|
+
fs.writeFileSync(
|
|
1025
|
+
path.join(tmpDir, 'components', 'AcmeTabs.doc.mjs'),
|
|
1026
|
+
"export default {type: 'component', name: 'AcmeTabs', displayName: 1, description: 'x', components: [{name: 'AcmeTab', description: 'x', props: []}, {name: 'AcmeTabPanel', description: 'x', props: []}]};\n",
|
|
1027
|
+
);
|
|
1028
|
+
|
|
1029
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
1030
|
+
const codes = result.data.issues.map(issue => issue.code);
|
|
1031
|
+
|
|
1032
|
+
expect(codes).toContain('component_doc_unloadable');
|
|
1033
|
+
expect(codes.length).toBe(new Set(codes).size);
|
|
1034
|
+
});
|
|
1035
|
+
|
|
757
1036
|
it('passes a generated component through its exact packed export', async () => {
|
|
758
1037
|
fs.writeFileSync(
|
|
759
1038
|
path.join(tmpDir, 'package.json'),
|
|
@@ -811,6 +1090,86 @@ describe('integrationPackCheck', () => {
|
|
|
811
1090
|
);
|
|
812
1091
|
});
|
|
813
1092
|
|
|
1093
|
+
it('does not count a type-only re-export as the component', async () => {
|
|
1094
|
+
fs.writeFileSync(
|
|
1095
|
+
path.join(tmpDir, 'package.json'),
|
|
1096
|
+
`${JSON.stringify({
|
|
1097
|
+
name: '@acme/widgets',
|
|
1098
|
+
version: '1.0.0',
|
|
1099
|
+
files: ['astryx.integration.mjs', 'src'],
|
|
1100
|
+
exports: {'.': './src/index.ts'},
|
|
1101
|
+
})}\n`,
|
|
1102
|
+
);
|
|
1103
|
+
fs.writeFileSync(
|
|
1104
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
1105
|
+
"export default {components: './src'};\n",
|
|
1106
|
+
);
|
|
1107
|
+
fs.mkdirSync(path.join(tmpDir, 'src'));
|
|
1108
|
+
fs.writeFileSync(
|
|
1109
|
+
path.join(tmpDir, 'src', 'index.ts'),
|
|
1110
|
+
"export type {AcmeWidget} from './AcmeWidget';\n",
|
|
1111
|
+
);
|
|
1112
|
+
fs.writeFileSync(
|
|
1113
|
+
path.join(tmpDir, 'src', 'AcmeWidget.tsx'),
|
|
1114
|
+
'export function AcmeWidget() { return null; }\n',
|
|
1115
|
+
);
|
|
1116
|
+
fs.writeFileSync(
|
|
1117
|
+
path.join(tmpDir, 'src', 'AcmeWidget.doc.mjs'),
|
|
1118
|
+
"export default {type: 'component', name: 'AcmeWidget', import: '@acme/widgets', description: 'Widget.', props: []};\n",
|
|
1119
|
+
);
|
|
1120
|
+
|
|
1121
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
1122
|
+
|
|
1123
|
+
expect(result.data.issues).toContainEqual(
|
|
1124
|
+
expect.objectContaining({code: 'component_export_missing'}),
|
|
1125
|
+
);
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
it('follows a re-export of a folder to its index file', async () => {
|
|
1129
|
+
// `./primitives` names a folder: TypeScript and bundlers load its index file.
|
|
1130
|
+
fs.writeFileSync(
|
|
1131
|
+
path.join(tmpDir, 'package.json'),
|
|
1132
|
+
`${JSON.stringify({
|
|
1133
|
+
name: '@acme/widgets',
|
|
1134
|
+
version: '1.0.0',
|
|
1135
|
+
files: ['astryx.integration.mjs', 'src'],
|
|
1136
|
+
exports: {'.': './src/index.ts'},
|
|
1137
|
+
})}\n`,
|
|
1138
|
+
);
|
|
1139
|
+
fs.writeFileSync(
|
|
1140
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
1141
|
+
"export default {components: './src'};\n",
|
|
1142
|
+
);
|
|
1143
|
+
const primitives = path.join(tmpDir, 'src', 'primitives');
|
|
1144
|
+
fs.mkdirSync(primitives, {recursive: true});
|
|
1145
|
+
fs.writeFileSync(
|
|
1146
|
+
path.join(tmpDir, 'src', 'index.ts'),
|
|
1147
|
+
"export * from './primitives';\n",
|
|
1148
|
+
);
|
|
1149
|
+
fs.writeFileSync(
|
|
1150
|
+
path.join(primitives, 'index.ts'),
|
|
1151
|
+
"export * from './AcmeWidget';\n",
|
|
1152
|
+
);
|
|
1153
|
+
fs.writeFileSync(
|
|
1154
|
+
path.join(primitives, 'AcmeWidget.tsx'),
|
|
1155
|
+
'export function AcmeWidget() { return null; }\n',
|
|
1156
|
+
);
|
|
1157
|
+
fs.writeFileSync(
|
|
1158
|
+
path.join(primitives, 'AcmeWidget.doc.mjs'),
|
|
1159
|
+
"export default {type: 'component', name: 'AcmeWidget', import: '@acme/widgets', description: 'Widget.', props: []};\n",
|
|
1160
|
+
);
|
|
1161
|
+
|
|
1162
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
1163
|
+
|
|
1164
|
+
expect(result.data.issues).not.toContainEqual(
|
|
1165
|
+
expect.objectContaining({code: 'component_export_missing'}),
|
|
1166
|
+
);
|
|
1167
|
+
expect(
|
|
1168
|
+
result.data.packable,
|
|
1169
|
+
JSON.stringify(result.data.issues, null, 2),
|
|
1170
|
+
).toBe(true);
|
|
1171
|
+
});
|
|
1172
|
+
|
|
814
1173
|
it('fails when packed template source is hidden by package exports', async () => {
|
|
815
1174
|
writePackage({
|
|
816
1175
|
manifest: "export default {templates: './templates'};\n",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
import * as fs from 'node:fs';
|
|
35
35
|
import * as path from 'node:path';
|
|
36
36
|
import {assertWithin} from '../../foundation/fs/path-safety.mjs';
|
|
37
|
+
import {integrationDocTypes} from '../docs/_adapter.mjs';
|
|
37
38
|
import {
|
|
38
39
|
THEME_SLUG_RE,
|
|
39
40
|
unreadThemeFolders,
|
|
@@ -461,6 +462,12 @@ export async function validateIntegration(pkg, options = {}) {
|
|
|
461
462
|
const result = pkg
|
|
462
463
|
? await validateInstalledIntegration(pkg, cwd)
|
|
463
464
|
: await validateLocalIntegration(cwd);
|
|
465
|
+
// Each doc the package ships, against the published authoring type for its
|
|
466
|
+
// kind, read with the project's own compiler. Findings are warnings only.
|
|
467
|
+
const integration = result.found ? result.integration : undefined;
|
|
468
|
+
const docTypes = integration
|
|
469
|
+
? await integrationDocTypes(integration, cwd)
|
|
470
|
+
: undefined;
|
|
464
471
|
return {
|
|
465
472
|
type: 'integration.validate',
|
|
466
473
|
data: {
|
|
@@ -472,6 +479,7 @@ export async function validateIntegration(pkg, options = {}) {
|
|
|
472
479
|
issues: result.reason === 'no_package_json'
|
|
473
480
|
? [{code: 'no_package', severity: 'error', message: 'No package.json found. Run this command inside a package directory.'}]
|
|
474
481
|
: result.issues,
|
|
482
|
+
...(docTypes ? {docTypes} : {}),
|
|
475
483
|
},
|
|
476
484
|
};
|
|
477
485
|
}
|
|
@@ -176,6 +176,17 @@ describe('validate-integration API', () => {
|
|
|
176
176
|
expect(agentDocsIssues[0].severity).toBe('error');
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
+
it('reports nothing for a docsiteUrl', async () => {
|
|
180
|
+
const pkgDir = path.join(tmpDir, 'pkg');
|
|
181
|
+
writePackage(pkgDir, {
|
|
182
|
+
manifest: `export default {docsiteUrl: 'https://widgets.example.com'};\n`,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const result = await validateLocalIntegration(pkgDir);
|
|
186
|
+
|
|
187
|
+
expect(result.issues).toEqual([]);
|
|
188
|
+
});
|
|
189
|
+
|
|
179
190
|
it('flags a broken codemod as invalid_codemod error', async () => {
|
|
180
191
|
const pkgDir = path.join(tmpDir, 'pkg');
|
|
181
192
|
writePackage(pkgDir, {
|
|
@@ -39,5 +39,13 @@ export type ValidateIntegrationResponse = {
|
|
|
39
39
|
name: string | null;
|
|
40
40
|
version: string | null;
|
|
41
41
|
issues: import("../../foundation/integrations/issue").AstryxIntegrationIssue[];
|
|
42
|
+
docTypes?: DocTypesReport;
|
|
42
43
|
};
|
|
43
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* `fixtures`: how many test fixture docs (under `__tests__` in the
|
|
47
|
+
* components or docs root, which those readers skip) were not checked
|
|
48
|
+
*/
|
|
49
|
+
export type DocTypesReport = import("../../foundation/discovery/doc-types.mjs").DocTypesReport & {
|
|
50
|
+
fixtures?: number;
|
|
51
|
+
};
|
|
@@ -38,7 +38,18 @@
|
|
|
38
38
|
* "healthy" — the two are otherwise byte-identical.
|
|
39
39
|
* @typedef {object} ValidateIntegrationResponse
|
|
40
40
|
* @property {'integration.validate'} type
|
|
41
|
-
* @property {{validated: boolean, name: string | null, version: string | null, issues: import('../../foundation/integrations/issue').AstryxIntegrationIssue[]}} data
|
|
41
|
+
* @property {{validated: boolean, name: string | null, version: string | null, issues: import('../../foundation/integrations/issue').AstryxIntegrationIssue[], docTypes?: DocTypesReport}} data
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Each doc the package ships, checked against the published authoring type
|
|
46
|
+
* for its kind. Present when the manifest loads. `status: 'skipped'`
|
|
47
|
+
* means nothing was checked (no TypeScript in the project) and `reason` says
|
|
48
|
+
* why. Every finding is a warning.
|
|
49
|
+
*
|
|
50
|
+
* @typedef {import('../../foundation/discovery/doc-types.mjs').DocTypesReport & {fixtures?: number}} DocTypesReport
|
|
51
|
+
* `fixtures`: how many test fixture docs (under `__tests__` in the
|
|
52
|
+
* components or docs root, which those readers skip) were not checked
|
|
42
53
|
*/
|
|
43
54
|
|
|
44
55
|
export {};
|
|
@@ -23,11 +23,25 @@ export const doc = {
|
|
|
23
23
|
'declared contribution root exists, and runs the codemod/template/component/' +
|
|
24
24
|
'docs/theme validators, returning every finding as an AstryxIntegrationIssue. A missing ' +
|
|
25
25
|
'local manifest is guidance (name null, no issues), not an error, so callers ' +
|
|
26
|
-
'can stay exit-0 in a non-integration directory.'
|
|
26
|
+
'can stay exit-0 in a non-integration directory. It also checks every doc the ' +
|
|
27
|
+
'package ships under its roots (component, template, docs topic, theme, and translation, served or not) against ' +
|
|
28
|
+
"the published authoring type for its kind, as `tsc` would, with the project's " +
|
|
29
|
+
'own `typescript`: an unknown field, a missing required field, or a value outside ' +
|
|
30
|
+
"a field's type is a warning in `docTypes` naming the field and a fix. A test " +
|
|
31
|
+
'fixture under `__tests__` in the components or docs root is counted, not checked. ' +
|
|
32
|
+
'Without TypeScript, `docTypes` is `skipped` with a reason.',
|
|
27
33
|
importPath: '@astryxdesign/cli/api',
|
|
28
34
|
signature:
|
|
29
35
|
'validateIntegration(pkg?: string, options?: ValidateIntegrationOptions): Promise<ValidateIntegrationResponse>',
|
|
30
|
-
keywords: [
|
|
36
|
+
keywords: [
|
|
37
|
+
'integration',
|
|
38
|
+
'validate',
|
|
39
|
+
'manifest',
|
|
40
|
+
'lint',
|
|
41
|
+
'check',
|
|
42
|
+
'types',
|
|
43
|
+
'typecheck',
|
|
44
|
+
],
|
|
31
45
|
params: [
|
|
32
46
|
{
|
|
33
47
|
name: 'pkg',
|
|
@@ -46,7 +60,7 @@ export const doc = {
|
|
|
46
60
|
{
|
|
47
61
|
type: 'integration.validate',
|
|
48
62
|
description:
|
|
49
|
-
'The result envelope: `data.validated`, false when nothing was validated — with no `package.json` at all, `data.issues` carries one `no_package` error-severity issue so the command exits 1; with a `package.json` but no integration manifest, `data.issues` is empty, which means "not looked at", not "healthy"; `data.name` and `data.version` of the validated package (both null when `data.validated` is false; `data.name` is `(local package)` when the local package.json has no readable name), plus `data.issues`, an AstryxIntegrationIssue[] of {code, severity: `warning` | `error`, message}.',
|
|
63
|
+
'The result envelope: `data.validated`, false when nothing was validated — with no `package.json` at all, `data.issues` carries one `no_package` error-severity issue so the command exits 1; with a `package.json` but no integration manifest, `data.issues` is empty, which means "not looked at", not "healthy"; `data.name` and `data.version` of the validated package (both null when `data.validated` is false; `data.name` is `(local package)` when the local package.json has no readable name), plus `data.issues`, an AstryxIntegrationIssue[] of {code, severity: `warning` | `error`, message}. When the manifest loads, `data.docTypes` holds the doc type check: `status` (`checked` | `skipped`), `reason` when skipped, `typescript`, `docs` (doc values checked), `findings` ({doc, line, export, type, field, problem: `unknown_field` | `missing_field` | `wrong_type`, severity: `warning`, message, fix}), `unchecked` ({doc, reason}: a doc that could not be fully checked, or a root its reader cannot read), and `fixtures` when the components or docs root holds test fixture docs under `__tests__`: how many, none of them checked.',
|
|
50
64
|
},
|
|
51
65
|
],
|
|
52
66
|
examples: [
|
package/api/search/search.d.mts
CHANGED
|
@@ -40,19 +40,20 @@ export function tokenizeQuery(term: string): string[];
|
|
|
40
40
|
* typography/font-setup, `side panel` is "Side panels");
|
|
41
41
|
* - 1: a word of the query is the topic's name (`illustration` in a longer
|
|
42
42
|
* question is the illustrations guide);
|
|
43
|
+
* - 1: a query of two or more words is one of the topic's own declared
|
|
44
|
+
* keywords, whatever the plural (`code review` on a guide that declares it);
|
|
43
45
|
* - 1: the query holds one of the doc's titles whole (`resizable side panels`
|
|
44
46
|
* names "Side panels"; `light dark mode button` names "Light/Dark Mode");
|
|
45
47
|
* - 1: the doc matches every word of the query and one of them is a word of
|
|
46
48
|
* its title (`switch to a dark theme` and "Use a theme").
|
|
47
49
|
* A section answers to its topic's name.
|
|
48
50
|
*
|
|
49
|
-
* A doc that matched only by
|
|
51
|
+
* A doc that matched only by a heading holding the query, by one word, or by
|
|
50
52
|
* words spread through its text has no priority. Docs are split into many
|
|
51
|
-
* small topics,
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* above the component. Broad reference pages match every word of many queries
|
|
53
|
+
* small topics, each with its own headings, so a common phrase such as
|
|
54
|
+
* `font size` hits a heading exactly (170) while the component the reader is
|
|
55
|
+
* after matches one word by keyword (`Text`, 98). Ranked on text alone, every
|
|
56
|
+
* split adds another doc above the component. Broad reference pages match every word of many queries
|
|
56
57
|
* in their text the same way. The score stays the text-match strength the
|
|
57
58
|
* result reports, so callers that gate on it (`build`) see the same numbers.
|
|
58
59
|
*
|
|
@@ -213,6 +214,12 @@ export type Candidate = {
|
|
|
213
214
|
*/
|
|
214
215
|
aliases?: string[] | undefined;
|
|
215
216
|
keywords?: string[] | undefined;
|
|
217
|
+
/**
|
|
218
|
+
* - A doc's own declared keywords: what its
|
|
219
|
+
* author lists in `keywords`, without the code terms and table cells its
|
|
220
|
+
* other keywords add.
|
|
221
|
+
*/
|
|
222
|
+
_declared?: string[] | undefined;
|
|
216
223
|
weakKeywords?: string[] | undefined;
|
|
217
224
|
description?: string | undefined;
|
|
218
225
|
prose?: string[] | undefined;
|
|
@@ -252,4 +259,13 @@ export type Candidate = {
|
|
|
252
259
|
_kind?: "page" | "block" | undefined;
|
|
253
260
|
_resultName?: string | undefined;
|
|
254
261
|
_commandName?: string | undefined;
|
|
262
|
+
/**
|
|
263
|
+
* - How an integration component holds its
|
|
264
|
+
* name, for the owner rule.
|
|
265
|
+
*/
|
|
266
|
+
_hold?: "file" | "doc" | undefined;
|
|
267
|
+
/**
|
|
268
|
+
* - An integration component's doc.
|
|
269
|
+
*/
|
|
270
|
+
_docPath?: string | undefined;
|
|
255
271
|
};
|