@astryxdesign/cli 0.7.2-canary.caec7ca → 0.7.2-canary.caf5673
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/component.test.mjs +80 -0
- 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/doctor.mjs +27 -3
- package/api/doctor/doctor.test.mjs +43 -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 +43 -6
- 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 +19 -3
- package/api/search/search.d.mts +81 -12
- package/api/search/search.domain-priority.test.mjs +108 -18
- package/api/search/search.integration-check.test.mjs +24 -0
- package/api/search/search.mjs +608 -53
- package/api/search/search.test.mjs +175 -0
- package/api/swizzle/copy/copy.mjs +28 -14
- 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/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/migration-setup.doc.mjs +1 -0
- package/assets/docs/tree/migration-verification.doc.mjs +0 -1
- package/assets/docs/tree/migration.doc.mjs +0 -1
- 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/docs/working-with-ai.doc.mjs +43 -4
- 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 +224 -6
- package/clients/cli/commands/build-theme.registry.test.mjs +5 -2
- package/clients/cli/commands/cli-postinstall.test.mjs +49 -1
- package/clients/cli/commands/doctor-integration-components.doc.mjs +17 -4
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +13 -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/setup-nudge.test.mjs +79 -1
- package/clients/cli/commands/swizzle.mjs +5 -3
- package/clients/cli/commands/swizzle.routing.test.mjs +18 -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 +30 -5
- package/foundation/agent-docs/agent-doc-state.d.mts +14 -0
- package/foundation/agent-docs/agent-doc-state.mjs +38 -0
- package/foundation/agent-docs/agent-docs.d.mts +2 -1
- package/foundation/agent-docs/agent-docs.mjs +8 -6
- package/foundation/config/project.mjs +82 -0
- package/foundation/discovery/authoring-self-docs.mjs +2 -1
- 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 +700 -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 +32 -0
- package/foundation/discovery/project-typescript.mjs +89 -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 +2272 -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 +14 -0
- 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 +9 -9
- package/scripts/postinstall.mjs +3 -3
|
@@ -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,27 @@ 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` (or, where `typescript` is missing or has no JavaScript compiler ' +
|
|
30
|
+
'API, as TypeScript 7 does, the `@typescript/typescript6` it installs): an unknown ' +
|
|
31
|
+
'field, a missing required field, or a value outside ' +
|
|
32
|
+
"a field's type is a warning in `docTypes` naming the field and a fix. A test " +
|
|
33
|
+
'fixture under `__tests__` in the components or docs root is counted, not checked. ' +
|
|
34
|
+
'Without TypeScript, `docTypes` is `skipped` with a reason.',
|
|
27
35
|
importPath: '@astryxdesign/cli/api',
|
|
28
36
|
signature:
|
|
29
37
|
'validateIntegration(pkg?: string, options?: ValidateIntegrationOptions): Promise<ValidateIntegrationResponse>',
|
|
30
|
-
keywords: [
|
|
38
|
+
keywords: [
|
|
39
|
+
'integration',
|
|
40
|
+
'validate',
|
|
41
|
+
'manifest',
|
|
42
|
+
'lint',
|
|
43
|
+
'check',
|
|
44
|
+
'types',
|
|
45
|
+
'typecheck',
|
|
46
|
+
],
|
|
31
47
|
params: [
|
|
32
48
|
{
|
|
33
49
|
name: 'pkg',
|
|
@@ -46,7 +62,7 @@ export const doc = {
|
|
|
46
62
|
{
|
|
47
63
|
type: 'integration.validate',
|
|
48
64
|
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}.',
|
|
65
|
+
'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
66
|
},
|
|
51
67
|
],
|
|
52
68
|
examples: [
|
package/api/search/search.d.mts
CHANGED
|
@@ -16,6 +16,48 @@ export function stem(w: string): string;
|
|
|
16
16
|
* @returns {boolean}
|
|
17
17
|
*/
|
|
18
18
|
export function sameWord(a: string, b: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* A query with its camelCase and PascalCase identifiers spelled as words, so
|
|
21
|
+
* `SidebarToggle` reads "sidebar toggle" and `useTablePaginationState` "use
|
|
22
|
+
* table pagination state". Plain words, and identifiers joined with `_` or `-`, are
|
|
23
|
+
* unchanged: a joined token that matches nothing stays unmatched.
|
|
24
|
+
* @param {string} query - The query as typed, before lowercasing.
|
|
25
|
+
* @returns {string} Lowercased.
|
|
26
|
+
*/
|
|
27
|
+
export function spellIdentifiers(query: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* The strongest hit a run of two or more adjacent query words has when it is
|
|
30
|
+
* read as one joined word, and the query words it covers. Readers type a
|
|
31
|
+
* compound name as separate words (`table header cell` is TableHeaderCell,
|
|
32
|
+
* `number field` the keyword `numberfield`), and no single word of the run
|
|
33
|
+
* matches on its own.
|
|
34
|
+
*
|
|
35
|
+
* - 100: the run joins to a component, hook, or template name, or its plural
|
|
36
|
+
* (`table header cells` is TableHeaderCell), or a hook name without its
|
|
37
|
+
* `use` prefix (`table sticky columns` is useTableStickyColumns).
|
|
38
|
+
* - 90: the run joins to a one-word keyword (`number field` is
|
|
39
|
+
* `numberfield`).
|
|
40
|
+
* - 95: the run joins to most of a name, cut short (`dropdown menu radio` is
|
|
41
|
+
* DropdownMenuRadioItem); 92: it is a name's leading words, covering at least half
|
|
42
|
+
* of it (`top nav mega` is TopNavMegaMenu); 70: it is two or more adjacent
|
|
43
|
+
* words of a name that cover at least half of it (`header cell` in
|
|
44
|
+
* TableHeaderCell), the same coverage a word prefix needs. These three
|
|
45
|
+
* are for components and hooks only.
|
|
46
|
+
*
|
|
47
|
+
* A doc's name is a route or key, so docs are left out.
|
|
48
|
+
* @param {string[]} words - The query's words, stopwords included.
|
|
49
|
+
* @param {Candidate} candidate
|
|
50
|
+
* @returns {{score: number, reason: string, covered: Set<string>, alias?: 'partial' | 'keyword', aliasWords?: number} | null}
|
|
51
|
+
* `covered` holds the run's words and every stretch of them joined. `alias`
|
|
52
|
+
* marks a hit on part of a name or on a keyword, not on the whole name.
|
|
53
|
+
*/
|
|
54
|
+
export function compoundHit(words: string[], candidate: Candidate): {
|
|
55
|
+
score: number;
|
|
56
|
+
reason: string;
|
|
57
|
+
covered: Set<string>;
|
|
58
|
+
alias?: "partial" | "keyword";
|
|
59
|
+
aliasWords?: number;
|
|
60
|
+
} | null;
|
|
19
61
|
/**
|
|
20
62
|
* Split a query into meaningful content tokens (lowercased, stopwords + very
|
|
21
63
|
* short words removed). Empty for single-word queries (callers fall back to
|
|
@@ -40,19 +82,20 @@ export function tokenizeQuery(term: string): string[];
|
|
|
40
82
|
* typography/font-setup, `side panel` is "Side panels");
|
|
41
83
|
* - 1: a word of the query is the topic's name (`illustration` in a longer
|
|
42
84
|
* question is the illustrations guide);
|
|
85
|
+
* - 1: a query of two or more words is one of the topic's own declared
|
|
86
|
+
* keywords, whatever the plural (`code review` on a guide that declares it);
|
|
43
87
|
* - 1: the query holds one of the doc's titles whole (`resizable side panels`
|
|
44
88
|
* names "Side panels"; `light dark mode button` names "Light/Dark Mode");
|
|
45
89
|
* - 1: the doc matches every word of the query and one of them is a word of
|
|
46
90
|
* its title (`switch to a dark theme` and "Use a theme").
|
|
47
91
|
* A section answers to its topic's name.
|
|
48
92
|
*
|
|
49
|
-
* A doc that matched only by
|
|
93
|
+
* A doc that matched only by a heading holding the query, by one word, or by
|
|
50
94
|
* 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
|
|
95
|
+
* small topics, each with its own headings, so a common phrase such as
|
|
96
|
+
* `font size` hits a heading exactly (170) while the component the reader is
|
|
97
|
+
* after matches one word by keyword (`Text`, 98). Ranked on text alone, every
|
|
98
|
+
* split adds another doc above the component. Broad reference pages match every word of many queries
|
|
56
99
|
* in their text the same way. The score stays the text-match strength the
|
|
57
100
|
* result reports, so callers that gate on it (`build`) see the same numbers.
|
|
58
101
|
*
|
|
@@ -67,7 +110,7 @@ export function domainPriority(term: string, tokens: string[], candidate: Candid
|
|
|
67
110
|
score: number;
|
|
68
111
|
matched: number;
|
|
69
112
|
total: number;
|
|
70
|
-
}): 0 | 1 | 2;
|
|
113
|
+
}, queryWords?: string[]): 0 | 1 | 2;
|
|
71
114
|
/**
|
|
72
115
|
* The first title or heading that holds every word of the query, in order and
|
|
73
116
|
* side by side, or null.
|
|
@@ -89,18 +132,26 @@ export function titleInQuery(term: string, titles: string[] | undefined): string
|
|
|
89
132
|
* @param {string} term - Lowercased full query.
|
|
90
133
|
* @param {string[]} tokens - Content tokens from tokenizeQuery(term).
|
|
91
134
|
* @param {Candidate} candidate
|
|
92
|
-
* @
|
|
135
|
+
* @param {string[]} [words] - The query's words for compound names (see
|
|
136
|
+
* compoundHit); empty when the query names an entry outright.
|
|
137
|
+
* @param {{plain?: boolean}} [opts] - `plain`: score without matches on part
|
|
138
|
+
* of a name or a joined keyword.
|
|
139
|
+
* @returns {{score: number, reason: string, matched: number, total: number, alias?: 'partial' | 'keyword', aliasWords?: number} | null}
|
|
93
140
|
* `matched`/`total` are the query concepts this candidate answered, out of
|
|
94
141
|
* the concepts the query had. Callers that must distinguish "matched one word
|
|
95
142
|
* of three" from "matched all three" — `build`, which gates its pages group
|
|
96
143
|
* on coverage — cannot recover that from the score, because a single strong
|
|
97
144
|
* hit and a broad weak one land on the same number.
|
|
98
145
|
*/
|
|
99
|
-
export function scoreQuery(term: string, tokens: string[], candidate: Candidate
|
|
146
|
+
export function scoreQuery(term: string, tokens: string[], candidate: Candidate, words?: string[], { plain }?: {
|
|
147
|
+
plain?: boolean;
|
|
148
|
+
}): {
|
|
100
149
|
score: number;
|
|
101
150
|
reason: string;
|
|
102
151
|
matched: number;
|
|
103
152
|
total: number;
|
|
153
|
+
alias?: "partial" | "keyword";
|
|
154
|
+
aliasWords?: number;
|
|
104
155
|
} | null;
|
|
105
156
|
/**
|
|
106
157
|
* Score a single candidate against the search term across name, keywords,
|
|
@@ -118,8 +169,9 @@ export function scoreQuery(term: string, tokens: string[], candidate: Candidate)
|
|
|
118
169
|
* @param {string} [candidate.description]
|
|
119
170
|
* @param {string[]} [candidate.prose] - Extra free-text blobs (doc section text, best practices).
|
|
120
171
|
* @param {string[]} [candidate.guidance] - Usage guidance (features, best practices) — scored a tier below description.
|
|
121
|
-
* @param {{fuzzy?: boolean}} [opts] - `fuzzy`: allow edit-distance (typo) matches. Default true; multi-word queries pass false.
|
|
122
|
-
* @returns {{score: number, reason: string} | null}
|
|
172
|
+
* @param {{fuzzy?: boolean, joins?: boolean}} [opts] - `fuzzy`: allow edit-distance (typo) matches. Default true; multi-word queries pass false. `joins`: read a spaced query joined into a name or keyword (default true); off when the query names an entry outright.
|
|
173
|
+
* @returns {{score: number, reason: string, alias?: 'partial' | 'keyword'} | null}
|
|
174
|
+
* `alias` marks a hit that read the query's words joined.
|
|
123
175
|
*/
|
|
124
176
|
export function scoreCandidate(term: string, { name, aliases, domain, keywords, weakKeywords, description, prose, guidance, }: {
|
|
125
177
|
name: string;
|
|
@@ -130,11 +182,13 @@ export function scoreCandidate(term: string, { name, aliases, domain, keywords,
|
|
|
130
182
|
description?: string | undefined;
|
|
131
183
|
prose?: string[] | undefined;
|
|
132
184
|
guidance?: string[] | undefined;
|
|
133
|
-
}, { fuzzy }?: {
|
|
185
|
+
}, { fuzzy, joins }?: {
|
|
134
186
|
fuzzy?: boolean;
|
|
187
|
+
joins?: boolean;
|
|
135
188
|
}): {
|
|
136
189
|
score: number;
|
|
137
190
|
reason: string;
|
|
191
|
+
alias?: "partial" | "keyword";
|
|
138
192
|
} | null;
|
|
139
193
|
/**
|
|
140
194
|
* The components (name and keywords) a search response was scored against, or
|
|
@@ -213,6 +267,12 @@ export type Candidate = {
|
|
|
213
267
|
*/
|
|
214
268
|
aliases?: string[] | undefined;
|
|
215
269
|
keywords?: string[] | undefined;
|
|
270
|
+
/**
|
|
271
|
+
* - A doc's own declared keywords: what its
|
|
272
|
+
* author lists in `keywords`, without the code terms and table cells its
|
|
273
|
+
* other keywords add.
|
|
274
|
+
*/
|
|
275
|
+
_declared?: string[] | undefined;
|
|
216
276
|
weakKeywords?: string[] | undefined;
|
|
217
277
|
description?: string | undefined;
|
|
218
278
|
prose?: string[] | undefined;
|
|
@@ -252,4 +312,13 @@ export type Candidate = {
|
|
|
252
312
|
_kind?: "page" | "block" | undefined;
|
|
253
313
|
_resultName?: string | undefined;
|
|
254
314
|
_commandName?: string | undefined;
|
|
315
|
+
/**
|
|
316
|
+
* - How an integration component holds its
|
|
317
|
+
* name, for the owner rule.
|
|
318
|
+
*/
|
|
319
|
+
_hold?: "file" | "doc" | undefined;
|
|
320
|
+
/**
|
|
321
|
+
* - An integration component's doc.
|
|
322
|
+
*/
|
|
323
|
+
_docPath?: string | undefined;
|
|
255
324
|
};
|