@astryxdesign/cli 0.7.2-canary.fc12a18 → 0.7.2-canary.fdd384f
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 +41 -1
- package/api/component/_adapter.mjs +93 -7
- package/api/component/component.mjs +22 -5
- package/api/component/component.test.mjs +420 -167
- package/api/component/integration-export-name.test.mjs +644 -0
- package/api/component/list/list.mjs +42 -4
- 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 +61 -107
- 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 +9 -0
- package/api/search/search.integration-check.test.mjs +24 -0
- package/api/search/search.mjs +108 -16
- package/api/search/search.test.mjs +36 -0
- package/api/swizzle/copy/copy.mjs +28 -14
- package/api/template/template.test.mjs +120 -3
- package/api/theme/build/build.family.test.mjs +64 -0
- package/api/theme/build/build.mjs +180 -24
- package/api/theme/themeBuild.doc.mjs +4 -4
- package/assets/codemods/transforms/next/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +120 -0
- package/assets/codemods/transforms/next/index.mjs +11 -1
- package/assets/codemods/transforms/next/migrate-list-edge-compensation-to-edge.mjs +198 -0
- 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 +8 -0
- 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/docs/working-with-ai.doc.mjs +43 -4
- package/assets/templates/blocks/components/Drawer/DrawerForm.doc.mjs +24 -0
- package/assets/templates/blocks/components/Drawer/DrawerForm.tsx +62 -0
- package/assets/templates/blocks/components/Drawer/DrawerScrim.doc.mjs +22 -0
- package/assets/templates/blocks/components/Drawer/DrawerScrim.tsx +68 -0
- package/assets/templates/blocks/components/Drawer/DrawerShowcase.doc.mjs +22 -0
- package/assets/templates/blocks/components/Drawer/DrawerShowcase.tsx +41 -0
- package/assets/templates/blocks/components/Drawer/DrawerStart.doc.mjs +21 -0
- package/assets/templates/blocks/components/Drawer/DrawerStart.tsx +41 -0
- package/assets/templates/blocks/components/Drawer/DrawerWidths.doc.mjs +22 -0
- package/assets/templates/blocks/components/Drawer/DrawerWidths.tsx +54 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderShowcase.doc.mjs +15 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderShowcase.tsx +73 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderSubtitleEndContent.doc.mjs +22 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderSubtitleEndContent.tsx +49 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderWithCloseButton.doc.mjs +21 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderWithCloseButton.tsx +42 -0
- package/assets/templates/pages/detail-page/page.tsx +3 -3
- package/assets/templates/pages/table-inbox/page.tsx +10 -8
- package/assets/theme.template.ts +1 -1
- package/authoring/doctypes/base/type.ts +6 -0
- package/authoring/doctypes/component/component.doc.mjs +4 -4
- package/authoring/doctypes/component/type.ts +6 -3
- 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 +224 -6
- package/clients/cli/commands/build-theme.registry.test.mjs +5 -2
- 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/clients/cli/lib/component-format.mjs +5 -1
- package/foundation/config/project.mjs +82 -0
- package/foundation/discovery/authoring-self-docs.mjs +1 -0
- package/foundation/discovery/component-discovery.d.mts +190 -10
- package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
- package/foundation/discovery/component-discovery.mjs +864 -56
- package/foundation/discovery/component-discovery.test.mjs +96 -0
- 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/template-adapter.mjs +74 -5
- 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 +11 -1
- package/foundation/doc-compiler/inputs.mjs +23 -0
- package/foundation/doc-compiler/inputs.test.mjs +0 -1
- 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/foundation/xle/registry.mjs +4 -0
- package/foundation/xle/registry.test.mjs +28 -0
- package/package.json +10 -10
|
@@ -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
|
@@ -252,4 +252,13 @@ export type Candidate = {
|
|
|
252
252
|
_kind?: "page" | "block" | undefined;
|
|
253
253
|
_resultName?: string | undefined;
|
|
254
254
|
_commandName?: string | undefined;
|
|
255
|
+
/**
|
|
256
|
+
* - How an integration component holds its
|
|
257
|
+
* name, for the owner rule.
|
|
258
|
+
*/
|
|
259
|
+
_hold?: "file" | "doc" | undefined;
|
|
260
|
+
/**
|
|
261
|
+
* - An integration component's doc.
|
|
262
|
+
*/
|
|
263
|
+
_docPath?: string | undefined;
|
|
255
264
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file A reader looking for the check a build runs finds
|
|
5
|
+
* `astryx integration check` first.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {describe, expect, it} from 'vitest';
|
|
9
|
+
import {search} from './search.mjs';
|
|
10
|
+
|
|
11
|
+
describe('search finds integration check for CI', () => {
|
|
12
|
+
it.each([
|
|
13
|
+
'ci',
|
|
14
|
+
'continuous integration',
|
|
15
|
+
'typecheck integration',
|
|
16
|
+
'integration check',
|
|
17
|
+
])('`%s` ranks it first', async query => {
|
|
18
|
+
const {results} = (await search(query)).data;
|
|
19
|
+
expect(results[0], JSON.stringify(results.slice(0, 3))).toMatchObject({
|
|
20
|
+
domain: 'doc',
|
|
21
|
+
name: 'cli/commands/integration-check',
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|