@astryxdesign/cli 0.6.3-canary.db4e378 → 0.6.3-canary.ddb63c3
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 +20 -1
- package/api/blog/blog.doc.mjs +1 -0
- package/api/build/build.doc.mjs +1 -0
- package/api/component/component.doc.mjs +1 -0
- package/api/discover/discover.doc.mjs +1 -0
- package/api/docs/_adapter.d.mts +3 -8
- package/api/docs/_adapter.mjs +13 -105
- package/api/docs/docs.doc.mjs +1 -0
- package/api/docs/list/list.mjs +3 -7
- package/api/doctor/doctor.d.mts +25 -3
- package/api/doctor/doctor.doc.mjs +1 -0
- package/api/doctor/doctor.mjs +185 -13
- package/api/doctor/doctor.test.mjs +326 -1
- package/api/gap-report/gap-report.doc.mjs +1 -0
- package/api/hook/_adapter.mjs +19 -5
- package/api/hook/hook.doc.mjs +1 -0
- package/api/hook/list/list.d.mts +1 -1
- package/api/hook/list/list.mjs +69 -17
- package/api/init/init.doc.mjs +1 -0
- package/api/integration/add-contribution.mjs +7 -7
- package/api/integration/add-contribution.test.mjs +37 -3
- package/api/integration/add-theme.mjs +34 -64
- package/api/integration/add-theme.test.mjs +105 -21
- package/api/integration/authoring-checks.test.mjs +8 -4
- package/api/integration/integration-block-exports.test.mjs +10 -6
- package/api/integration/integrationAdd.doc.mjs +1 -0
- package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
- package/api/integration/integrationAddCodemod.doc.mjs +1 -0
- package/api/integration/integrationAddComponent.doc.mjs +1 -0
- package/api/integration/integrationAddDoc.doc.mjs +1 -0
- package/api/integration/integrationAddTemplate.doc.mjs +1 -0
- package/api/integration/integrationAddTheme.doc.mjs +6 -5
- package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
- package/api/integration/integrationDocConflicts.doc.mjs +1 -0
- package/api/integration/integrationPackCheck.doc.mjs +1 -0
- package/api/integration/integrationTemplateConflicts.doc.mjs +1 -0
- package/api/integration/pack-check.test.mjs +17 -47
- package/api/integration/summarizeIssues.doc.mjs +1 -0
- package/api/integration/validate-integration-fixes.test.mjs +1389 -0
- package/api/integration/validate-integration.mjs +50 -102
- package/api/integration/validate-integration.test.mjs +85 -23
- package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
- package/api/integration/validateIntegration.doc.mjs +1 -0
- package/api/json/assertResponse.doc.mjs +1 -0
- package/api/json/isError.doc.mjs +1 -0
- package/api/json/parseResponse.doc.mjs +1 -0
- package/api/layout/layoutCheck.doc.mjs +1 -0
- package/api/layout/layoutExpand.doc.mjs +1 -0
- package/api/layout/layoutGrammar.doc.mjs +1 -0
- package/api/search/search.doc.mjs +1 -0
- package/api/search/search.mjs +16 -6
- package/api/swizzle/swizzle.doc.mjs +1 -0
- package/api/template/template-suffix.test.mjs +41 -21
- package/api/template/template.doc.mjs +1 -0
- package/api/theme/_adapter.d.mts +2 -3
- package/api/theme/_adapter.mjs +4 -5
- package/api/theme/add/add.binary.test.mjs +10 -17
- package/api/theme/add/add.test.mjs +14 -1
- package/api/theme/generateTonalPalette.doc.mjs +1 -0
- package/api/theme/integration-themes.test.mjs +39 -28
- package/api/theme/list/list.test.mjs +19 -20
- package/api/theme/listThemes.doc.mjs +6 -5
- package/api/theme/themeAdd.doc.mjs +4 -3
- package/api/theme/themeBuild.doc.mjs +1 -0
- package/api/theme/themeList.doc.mjs +6 -3
- package/api/theme/themeListAvailable.doc.mjs +5 -3
- package/api/theme/themePaletteGenerate.doc.mjs +1 -0
- package/api/theme/themeTargets.doc.mjs +1 -0
- package/api/theme/themeTemplate.doc.mjs +1 -0
- package/api/upgrade/_adapter.d.mts +12 -3
- package/api/upgrade/_adapter.mjs +81 -66
- package/api/upgrade/provider-agreement.test.mjs +152 -0
- package/api/upgrade/run/run.mjs +1 -0
- package/api/upgrade/upgrade.doc.mjs +1 -0
- package/assets/codemods/integration-discovery.mjs +8 -2
- package/assets/codemods/integration-discovery.test.mjs +15 -0
- package/assets/codemods/runner.mjs +115 -5
- package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +216 -0
- package/assets/codemods/transforms/next/index.mjs +11 -1
- package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
- package/assets/docs/cli-integrations.doc.mjs +17 -30
- package/assets/docs/cli.doc.mjs +15 -0
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
- package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
- package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
- package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
- package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
- package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
- package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
- package/authoring/codemod/codemod.doc.mjs +1 -1
- package/authoring/config/config.doc.mjs +1 -1
- package/authoring/debug/debug.doc.d.mts +11 -0
- package/authoring/debug/debug.doc.mjs +182 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +1 -1
- package/authoring/doctypes/command/command.doc.mjs +1 -1
- package/authoring/doctypes/command/type.ts +1 -1
- package/authoring/doctypes/component/type.ts +2 -2
- package/authoring/doctypes/doctypes-new.test.mjs +48 -6
- package/authoring/doctypes/enum/enum.doc.mjs +1 -1
- package/authoring/doctypes/enum/type.ts +1 -1
- package/authoring/doctypes/function/function.doc.mjs +1 -1
- package/authoring/doctypes/function/type.ts +1 -1
- package/authoring/doctypes/hook/type.ts +2 -2
- package/authoring/doctypes/load-contract.test.mjs +2 -1
- package/authoring/doctypes/parse.d.mts +4 -2
- package/authoring/doctypes/parse.mjs +7 -3
- package/authoring/doctypes/reference/reference.doc.mjs +1 -1
- package/authoring/doctypes/reference/type.ts +2 -2
- package/authoring/doctypes/schema/schema.doc.mjs +1 -1
- package/authoring/doctypes/schema/type.ts +1 -2
- package/authoring/doctypes/template/template.doc.mjs +3 -3
- package/authoring/doctypes/theme/parse.d.mts +35 -0
- package/authoring/doctypes/theme/parse.mjs +76 -0
- package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
- package/authoring/doctypes/theme/theme.doc.mjs +79 -0
- package/authoring/doctypes/theme/type.ts +42 -0
- package/authoring/doctypes/types.ts +2 -1
- package/authoring/gap-report/gap-report.doc.d.mts +12 -0
- package/authoring/gap-report/gap-report.doc.mjs +183 -0
- package/authoring/index.d.mts +1 -0
- package/authoring/index.d.ts +6 -4
- package/authoring/index.mjs +2 -1
- package/authoring/integration/integration.doc.mjs +2 -2
- package/authoring/integration/type.ts +3 -9
- package/clients/cli/__tests__/cliManifest.test.ts +27 -29
- package/clients/cli/commands/blog.doc.mjs +1 -1
- package/clients/cli/commands/build.doc.mjs +1 -1
- package/clients/cli/commands/component.doc.mjs +1 -1
- package/clients/cli/commands/discover.doc.mjs +1 -1
- package/clients/cli/commands/docs.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration.test.mjs +15 -7
- package/clients/cli/commands/doctor.doc.mjs +1 -1
- package/clients/cli/commands/gap-report.doc.mjs +1 -1
- package/clients/cli/commands/hook.doc.mjs +1 -1
- package/clients/cli/commands/init.doc.mjs +1 -1
- package/clients/cli/commands/integration-add.controls.test.mjs +1 -1
- package/clients/cli/commands/integration-add.doc.mjs +1 -1
- package/clients/cli/commands/integration-pack.doc.mjs +1 -1
- package/clients/cli/commands/integration-real-world.test.mjs +3 -9
- package/clients/cli/commands/integration.doc.mjs +1 -1
- package/clients/cli/commands/layout-check.doc.mjs +1 -1
- package/clients/cli/commands/layout-expand.doc.mjs +1 -1
- package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
- package/clients/cli/commands/layout.doc.mjs +1 -1
- package/clients/cli/commands/manifest.doc.mjs +1 -1
- package/clients/cli/commands/search.doc.mjs +1 -1
- package/clients/cli/commands/swizzle.doc.mjs +1 -1
- package/clients/cli/commands/template.doc.mjs +1 -1
- package/clients/cli/commands/theme-add.doc.mjs +2 -2
- package/clients/cli/commands/theme-build.doc.mjs +1 -1
- package/clients/cli/commands/theme-list.doc.mjs +2 -2
- package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -3
- package/clients/cli/commands/theme-palette.doc.mjs +1 -1
- package/clients/cli/commands/theme-targets.doc.mjs +1 -1
- package/clients/cli/commands/theme-template.doc.mjs +1 -1
- package/clients/cli/commands/theme.doc.mjs +1 -1
- package/clients/cli/commands/upgrade.doc.mjs +1 -1
- package/clients/cli/lib/hook-format.mjs +14 -5
- package/foundation/config/project-themes.test.mjs +11 -19
- package/foundation/config/project.d.mts +8 -0
- package/foundation/config/project.mjs +50 -21
- package/foundation/config/project.test.mjs +3 -16
- package/foundation/discovery/authoring-self-docs.d.mts +6 -0
- package/foundation/discovery/authoring-self-docs.mjs +17 -7
- package/foundation/discovery/authoring-surface.d.mts +74 -0
- package/foundation/discovery/authoring-surface.mjs +525 -0
- package/foundation/discovery/authoring-surface.test.mjs +392 -0
- package/foundation/discovery/cli-self-docs.d.mts +113 -0
- package/foundation/discovery/cli-self-docs.mjs +514 -0
- package/foundation/discovery/cli-self-docs.test.mjs +437 -0
- package/foundation/discovery/component-loader.d.mts +35 -38
- package/foundation/discovery/component-loader.mjs +53 -222
- package/foundation/discovery/docs-discovery.d.mts +3 -2
- package/foundation/discovery/docs-discovery.mjs +8 -12
- package/foundation/discovery/docs-discovery.test.mjs +8 -5
- package/foundation/discovery/template-adapter.d.mts +7 -0
- package/foundation/discovery/template-adapter.mjs +27 -40
- package/foundation/discovery/template-adapter.test.mjs +15 -0
- package/foundation/discovery/theme-discovery.d.mts +67 -7
- package/foundation/discovery/theme-discovery.mjs +916 -186
- package/foundation/discovery/theme-discovery.test.mjs +613 -219
- package/foundation/doc-compiler/bundle.d.mts +47 -0
- package/foundation/doc-compiler/bundle.mjs +218 -0
- package/foundation/doc-compiler/bundle.test.mjs +255 -0
- package/foundation/doc-compiler/compile.d.mts +200 -0
- package/foundation/doc-compiler/compile.mjs +254 -5
- package/foundation/doc-compiler/diagnostics.d.mts +126 -0
- package/foundation/doc-compiler/diagnostics.mjs +277 -0
- package/foundation/doc-compiler/doc-compiler.test.mjs +28 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
- package/foundation/doc-compiler/import.d.mts +24 -0
- package/foundation/doc-compiler/import.mjs +59 -0
- package/foundation/doc-compiler/inputs.d.mts +102 -0
- package/foundation/doc-compiler/inputs.mjs +286 -0
- package/foundation/doc-compiler/inputs.test.mjs +299 -0
- package/foundation/doc-compiler/ir.d.mts +13 -0
- package/foundation/doc-compiler/ir.mjs +189 -12
- package/foundation/doc-compiler/lower-doc.test.mjs +492 -0
- package/foundation/doc-compiler/overlays.d.mts +37 -0
- package/foundation/doc-compiler/overlays.mjs +206 -0
- package/foundation/doc-compiler/parse-readable.d.mts +9 -0
- package/foundation/doc-compiler/parse-readable.mjs +29 -0
- package/foundation/doc-compiler/read.d.mts +126 -0
- package/foundation/doc-compiler/read.mjs +320 -0
- package/foundation/doc-compiler/read.test.mjs +313 -0
- package/foundation/doc-compiler/source.d.mts +33 -0
- package/foundation/doc-compiler/source.mjs +128 -0
- package/foundation/integrations/autolink.d.mts +58 -1
- package/foundation/integrations/autolink.mjs +143 -57
- package/foundation/integrations/contribution-fixes.d.mts +145 -0
- package/foundation/integrations/contribution-fixes.mjs +1284 -0
- package/foundation/integrations/contribution-inventory.d.mts +1 -1
- package/foundation/integrations/contribution-inventory.mjs +17 -20
- package/foundation/integrations/contribution-inventory.test.mjs +67 -27
- package/foundation/integrations/integrations.d.mts +3 -0
- package/foundation/integrations/integrations.mjs +11 -97
- package/foundation/integrations/provider-ledger.test.mjs +275 -0
- package/foundation/integrations/provider-resolution.d.mts +152 -0
- package/foundation/integrations/provider-resolution.mjs +576 -0
- package/foundation/integrations/provider-resolution.test.mjs +369 -0
- package/foundation/integrations/theme-descriptor.d.mts +8 -0
- package/foundation/integrations/theme-descriptor.mjs +44 -0
- package/foundation/integrations/validate-contributions.mjs +104 -10
- package/foundation/response/error-codes.doc.mjs +2 -2
- package/foundation/response/error-codes.mjs +1 -1
- package/foundation/response/response-types.doc.mjs +1 -1
- package/foundation/response/response.doc.mjs +1 -1
- package/foundation/text/string-utils.mjs +22 -10
- package/package.json +9 -9
- package/assets/templates/themes/manifest.json +0 -95
|
@@ -33,8 +33,19 @@
|
|
|
33
33
|
|
|
34
34
|
import * as fs from 'node:fs';
|
|
35
35
|
import * as path from 'node:path';
|
|
36
|
-
import jscodeshift from 'jscodeshift';
|
|
37
36
|
import {assertWithin} from '../../foundation/fs/path-safety.mjs';
|
|
37
|
+
import {
|
|
38
|
+
THEME_SLUG_RE,
|
|
39
|
+
unreadThemeFolders,
|
|
40
|
+
} from '../../foundation/discovery/theme-discovery.mjs';
|
|
41
|
+
import {
|
|
42
|
+
createFixContext,
|
|
43
|
+
DOC_CANDIDATE_RE,
|
|
44
|
+
pathIsInside,
|
|
45
|
+
stampOf,
|
|
46
|
+
TEMPLATE_CANDIDATE_RE,
|
|
47
|
+
unreachableFix,
|
|
48
|
+
} from '../../foundation/integrations/contribution-fixes.mjs';
|
|
38
49
|
import {
|
|
39
50
|
findManifestPaths,
|
|
40
51
|
loadManifest,
|
|
@@ -90,106 +101,35 @@ const UNREACHABLE_SKIP_DIRS = new Set([
|
|
|
90
101
|
'.next',
|
|
91
102
|
'out',
|
|
92
103
|
]);
|
|
93
|
-
const DOC_CANDIDATE_RE = /\.doc\.(?:ts|mjs|js)$/u;
|
|
94
|
-
const TEMPLATE_CANDIDATE_RE = /\.template\.(?:ts|mjs|js)$/u;
|
|
95
|
-
const STATIC_DOC_TYPES = new Set(['component', 'generic', 'page', 'block']);
|
|
96
|
-
const STATIC_TEMPLATE_TYPES = new Set(['page', 'block']);
|
|
97
|
-
const j = jscodeshift.withParser('tsx');
|
|
98
|
-
|
|
99
|
-
/** @param {any} node @returns {any} */
|
|
100
|
-
function unwrapStaticExpression(node) {
|
|
101
|
-
let current = node;
|
|
102
|
-
while (
|
|
103
|
-
current &&
|
|
104
|
-
[
|
|
105
|
-
'TSSatisfiesExpression',
|
|
106
|
-
'TSAsExpression',
|
|
107
|
-
'TypeCastExpression',
|
|
108
|
-
'ParenthesizedExpression',
|
|
109
|
-
].includes(current.type)
|
|
110
|
-
) {
|
|
111
|
-
current = current.expression;
|
|
112
|
-
}
|
|
113
|
-
if (current?.type === 'CallExpression' && current.arguments.length > 0) {
|
|
114
|
-
current = unwrapStaticExpression(current.arguments[0]);
|
|
115
|
-
}
|
|
116
|
-
return current;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** @param {any} property @param {string} name */
|
|
120
|
-
function staticPropertyNamed(property, name) {
|
|
121
|
-
if (
|
|
122
|
-
!property ||
|
|
123
|
-
!['ObjectProperty', 'Property'].includes(property.type) ||
|
|
124
|
-
property.computed
|
|
125
|
-
) {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
return (
|
|
129
|
-
(property.key?.type === 'Identifier' && property.key.name === name) ||
|
|
130
|
-
(['Literal', 'StringLiteral'].includes(property.key?.type) &&
|
|
131
|
-
property.key.value === name)
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
104
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* @param {
|
|
141
|
-
* @
|
|
105
|
+
* A warning for each folder under the themes root that holds modules but is
|
|
106
|
+
* not read as a theme. Either branch of its fix leaves nothing to warn about:
|
|
107
|
+
* the folder becomes a theme, or a dot-folder discovery skips.
|
|
108
|
+
* @param {string} packageDir
|
|
109
|
+
* @param {import('../../foundation/integrations/integrations.mjs').LoadedIntegration} loaded
|
|
110
|
+
* @returns {Issue[]}
|
|
142
111
|
*/
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.find(j.ExportNamedDeclaration)
|
|
159
|
-
.forEach((/** @type {any} */ exportPath) => {
|
|
160
|
-
const declaration = exportPath.value.declaration;
|
|
161
|
-
if (declaration?.type !== 'VariableDeclaration') return;
|
|
162
|
-
for (const declarator of declaration.declarations) {
|
|
163
|
-
if (
|
|
164
|
-
declarator.id?.type === 'Identifier' &&
|
|
165
|
-
declarator.id.name === 'docs'
|
|
166
|
-
) {
|
|
167
|
-
candidates.push(declarator.init);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
const allowedTypes = templateOnly ? STATIC_TEMPLATE_TYPES : STATIC_DOC_TYPES;
|
|
173
|
-
for (const candidate of candidates) {
|
|
174
|
-
const object = unwrapStaticExpression(candidate);
|
|
175
|
-
if (object?.type !== 'ObjectExpression') continue;
|
|
176
|
-
const typeProperty = object.properties.find((/** @type {any} */ property) =>
|
|
177
|
-
staticPropertyNamed(property, 'type'),
|
|
178
|
-
);
|
|
179
|
-
const value = unwrapStaticExpression(typeProperty?.value);
|
|
180
|
-
if (
|
|
181
|
-
['Literal', 'StringLiteral'].includes(value?.type) &&
|
|
182
|
-
allowedTypes.has(value.value)
|
|
183
|
-
) {
|
|
184
|
-
return true;
|
|
112
|
+
function unreadThemeFolderIssues(packageDir, loaded) {
|
|
113
|
+
const themes = /** @type {string} */ (loaded.themes);
|
|
114
|
+
return unreadThemeFolders(themes, {
|
|
115
|
+
packageDir,
|
|
116
|
+
packageName: loaded.name,
|
|
117
|
+
}).map(folder => {
|
|
118
|
+
const name = path.basename(folder);
|
|
119
|
+
const shown = `${path.relative(packageDir, folder).split(path.sep).join('/')}/`;
|
|
120
|
+
const hide = `rename it to .${name} so Astryx skips it`;
|
|
121
|
+
let fix;
|
|
122
|
+
if (THEME_SLUG_RE.test(name)) {
|
|
123
|
+
const stem = `${name.replace(/-([a-z0-9])/gu, (_, character) => character.toUpperCase())}Theme`;
|
|
124
|
+
fix = `if it is a theme, add ${stem}.ts and ${stem}.doc.mjs to it (\`astryx integration add theme ${name}\` writes both) and move its code into ${stem}.ts; if not, ${hide}.`;
|
|
125
|
+
} else {
|
|
126
|
+
fix = `${hide}; a theme folder needs a lower-kebab name.`;
|
|
185
127
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
function pathIsInside(candidate, root) {
|
|
192
|
-
return candidate === root || candidate.startsWith(`${root}${path.sep}`);
|
|
128
|
+
return warning(
|
|
129
|
+
'unread_theme_folder',
|
|
130
|
+
`Folder "${shown}" holds modules but no <name>Theme source or .doc.mjs descriptor, so Astryx does not read it as a theme. Fix: ${fix}`,
|
|
131
|
+
);
|
|
132
|
+
});
|
|
193
133
|
}
|
|
194
134
|
|
|
195
135
|
/**
|
|
@@ -214,6 +154,7 @@ async function findUnreachableContributionIssues(packageDir, loaded) {
|
|
|
214
154
|
);
|
|
215
155
|
/** @type {Issue[]} */
|
|
216
156
|
const issues = [];
|
|
157
|
+
const context = createFixContext(packageDir, loaded);
|
|
217
158
|
let scanned = 0;
|
|
218
159
|
let truncated = false;
|
|
219
160
|
|
|
@@ -237,13 +178,18 @@ async function findUnreachableContributionIssues(packageDir, loaded) {
|
|
|
237
178
|
truncated = true;
|
|
238
179
|
return;
|
|
239
180
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
181
|
+
if (
|
|
182
|
+
!TEMPLATE_CANDIDATE_RE.test(entry.name) &&
|
|
183
|
+
!DOC_CANDIDATE_RE.test(entry.name)
|
|
184
|
+
) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const stamp = stampOf(context, full);
|
|
188
|
+
if (stamp == null) continue;
|
|
243
189
|
issues.push(
|
|
244
190
|
warning(
|
|
245
191
|
'unreachable_contribution',
|
|
246
|
-
`Found contribution metadata "${path.relative(packageDir, full)}" outside every declared integration root, so it contributes nothing.
|
|
192
|
+
`Found contribution metadata "${path.relative(packageDir, full)}" outside every declared integration root, so it contributes nothing. ${unreachableFix(context, full, stamp)}`,
|
|
247
193
|
),
|
|
248
194
|
);
|
|
249
195
|
}
|
|
@@ -366,6 +312,8 @@ async function validateAtPackageDir(
|
|
|
366
312
|
// Roots + contribution checks are shared with validateLoadedIntegration so
|
|
367
313
|
// the everyday-command nudge runs the exact same validators.
|
|
368
314
|
issues.push(...(await validateLoadedIntegration(loaded)));
|
|
315
|
+
if (loaded.themes)
|
|
316
|
+
issues.push(...unreadThemeFolderIssues(packageDir, loaded));
|
|
369
317
|
if (scanUnreachable) {
|
|
370
318
|
issues.push(
|
|
371
319
|
...(await findUnreachableContributionIssues(packageDir, loaded)),
|
|
@@ -193,6 +193,39 @@ describe('validate-integration API', () => {
|
|
|
193
193
|
expect(byCode(result.issues, 'invalid_codemod')).toHaveLength(1);
|
|
194
194
|
});
|
|
195
195
|
|
|
196
|
+
it('names a broken codemod of an installed package by its path inside the package', async () => {
|
|
197
|
+
const consumer = path.join(tmpDir, 'consumer');
|
|
198
|
+
fs.mkdirSync(consumer, {recursive: true});
|
|
199
|
+
fs.writeFileSync(
|
|
200
|
+
path.join(consumer, 'package.json'),
|
|
201
|
+
JSON.stringify({name: 'consumer'}),
|
|
202
|
+
);
|
|
203
|
+
const pkgDir = path.join(consumer, 'node_modules', '@acme', 'kit');
|
|
204
|
+
writePackage(pkgDir, {
|
|
205
|
+
name: '@acme/kit',
|
|
206
|
+
manifest: `export default { codemods: './codemods' };\n`,
|
|
207
|
+
});
|
|
208
|
+
fs.mkdirSync(path.join(pkgDir, 'codemods', '1.1.0'), {recursive: true});
|
|
209
|
+
fs.writeFileSync(
|
|
210
|
+
path.join(pkgDir, 'codemods', '1.1.0', 'no-default.mjs'),
|
|
211
|
+
'export const nope = 1;\n',
|
|
212
|
+
);
|
|
213
|
+
fs.writeFileSync(
|
|
214
|
+
path.join(pkgDir, 'codemods', 'stray.mjs'),
|
|
215
|
+
`export default {type: 'code', title: 'Stray', transform: file => file.source};\n`,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const result = await validateInstalledIntegration('@acme/kit', consumer);
|
|
219
|
+
|
|
220
|
+
const [broken] = byCode(result.issues, 'invalid_codemod');
|
|
221
|
+
expect(broken.message).toContain('(codemods/1.1.0/no-default.mjs)');
|
|
222
|
+
const [stray] = byCode(result.issues, 'codemod_outside_version');
|
|
223
|
+
expect(stray.message).toContain('"codemods/stray.mjs"');
|
|
224
|
+
for (const issue of result.issues) {
|
|
225
|
+
expect(issue.message).not.toContain(tmpDir);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
196
229
|
it('flags a broken template as invalid_template error', async () => {
|
|
197
230
|
const pkgDir = path.join(tmpDir, 'pkg');
|
|
198
231
|
writePackage(pkgDir, {
|
|
@@ -210,33 +243,24 @@ describe('validate-integration API', () => {
|
|
|
210
243
|
expect(byCode(result.issues, 'invalid_template')).toHaveLength(1);
|
|
211
244
|
});
|
|
212
245
|
|
|
213
|
-
it('reports no errors for a valid source-theme
|
|
246
|
+
it('reports no errors for a valid source-theme descriptor', async () => {
|
|
214
247
|
const pkgDir = path.join(tmpDir, 'pkg');
|
|
215
248
|
writePackage(pkgDir, {
|
|
216
|
-
manifest: `export default { themes: './themes' }
|
|
249
|
+
manifest: `export default { themes: './themes' };
|
|
250
|
+
`,
|
|
217
251
|
});
|
|
218
252
|
const themeDir = path.join(pkgDir, 'themes', 'ocean');
|
|
219
253
|
fs.mkdirSync(themeDir, {recursive: true});
|
|
220
254
|
fs.writeFileSync(
|
|
221
|
-
path.join(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
{
|
|
226
|
-
slug: 'ocean',
|
|
227
|
-
displayName: 'Ocean',
|
|
228
|
-
description: 'Blue and calm.',
|
|
229
|
-
maintained: true,
|
|
230
|
-
entry: 'oceanTheme.ts',
|
|
231
|
-
exportName: 'oceanTheme',
|
|
232
|
-
files: ['oceanTheme.ts'],
|
|
233
|
-
},
|
|
234
|
-
],
|
|
235
|
-
}),
|
|
255
|
+
path.join(themeDir, 'oceanTheme.doc.mjs'),
|
|
256
|
+
`/** @type {import('@astryxdesign/cli/authoring').ThemeDoc} */
|
|
257
|
+
export default {type: 'theme', name: 'ocean', displayName: 'Ocean', description: 'Blue and calm.', maintained: true};
|
|
258
|
+
`,
|
|
236
259
|
);
|
|
237
260
|
fs.writeFileSync(
|
|
238
261
|
path.join(themeDir, 'oceanTheme.ts'),
|
|
239
|
-
`export const oceanTheme = {}
|
|
262
|
+
`export const oceanTheme = {};
|
|
263
|
+
`,
|
|
240
264
|
);
|
|
241
265
|
|
|
242
266
|
const result = await validateLocalIntegration(pkgDir);
|
|
@@ -244,13 +268,17 @@ describe('validate-integration API', () => {
|
|
|
244
268
|
expect(summarizeIssues(result.issues).errors).toBe(0);
|
|
245
269
|
});
|
|
246
270
|
|
|
247
|
-
it('flags an
|
|
271
|
+
it('flags an obsolete source-theme catalog as invalid_theme', async () => {
|
|
248
272
|
const pkgDir = path.join(tmpDir, 'pkg');
|
|
249
273
|
writePackage(pkgDir, {
|
|
250
|
-
manifest: `export default { themes: './themes' }
|
|
274
|
+
manifest: `export default { themes: './themes' };
|
|
275
|
+
`,
|
|
251
276
|
});
|
|
252
277
|
fs.mkdirSync(path.join(pkgDir, 'themes'), {recursive: true});
|
|
253
|
-
fs.writeFileSync(
|
|
278
|
+
fs.writeFileSync(
|
|
279
|
+
path.join(pkgDir, 'themes', 'manifest.json'),
|
|
280
|
+
'{"version":1}',
|
|
281
|
+
);
|
|
254
282
|
|
|
255
283
|
const result = await validateLocalIntegration(pkgDir);
|
|
256
284
|
expect(byCode(result.issues, 'invalid_theme')).toHaveLength(1);
|
|
@@ -275,6 +303,11 @@ describe('validate-integration API', () => {
|
|
|
275
303
|
message: expect.stringContaining('InvisibleWidget.doc.mjs'),
|
|
276
304
|
}),
|
|
277
305
|
]);
|
|
306
|
+
expect(
|
|
307
|
+
byCode(result.issues, 'source_without_component_doc')[0].message,
|
|
308
|
+
).toContain(
|
|
309
|
+
"Fix: add InvisibleWidget.doc.mjs beside it with type: 'component'",
|
|
310
|
+
);
|
|
278
311
|
});
|
|
279
312
|
|
|
280
313
|
it('reports codemods outside semver folders and invalid folder names', async () => {
|
|
@@ -290,8 +323,12 @@ describe('validate-integration API', () => {
|
|
|
290
323
|
|
|
291
324
|
const result = await validateLocalIntegration(pkgDir);
|
|
292
325
|
|
|
293
|
-
|
|
294
|
-
expect(
|
|
326
|
+
const [stray] = byCode(result.issues, 'codemod_outside_version');
|
|
327
|
+
expect(stray.message).toContain('"codemods/forgotten.mjs"');
|
|
328
|
+
expect(stray.message).toContain('codemods/1.2.0/forgotten.mjs');
|
|
329
|
+
expect(stray.message).not.toContain(tmpDir);
|
|
330
|
+
const [folder] = byCode(result.issues, 'invalid_codemod_version');
|
|
331
|
+
expect(folder.message).toContain('Fix: rename it');
|
|
295
332
|
});
|
|
296
333
|
|
|
297
334
|
it('warns about valid contribution metadata outside every declared root', async () => {
|
|
@@ -333,6 +370,31 @@ describe('validate-integration API', () => {
|
|
|
333
370
|
const unreachable = byCode(result.issues, 'unreachable_contribution');
|
|
334
371
|
expect(unreachable).toHaveLength(1);
|
|
335
372
|
expect(unreachable[0].message).toContain('src/orphan.doc.mjs');
|
|
373
|
+
// No docs root is declared, but src/ also holds a .doc.mjs that is not a
|
|
374
|
+
// topic, which a docs root at src/ would read too; so the fix moves it.
|
|
375
|
+
expect(unreachable[0].message).toContain(
|
|
376
|
+
"Fix: move it into docs/ and set `docs: './docs'` in astryx.integration.mjs.",
|
|
377
|
+
);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('names the root that reads misplaced metadata when one is declared', async () => {
|
|
381
|
+
const pkgDir = path.join(tmpDir, 'pkg');
|
|
382
|
+
writePackage(pkgDir, {
|
|
383
|
+
manifest: `export default { templates: './templates' };\n`,
|
|
384
|
+
});
|
|
385
|
+
fs.mkdirSync(path.join(pkgDir, 'templates'));
|
|
386
|
+
fs.mkdirSync(path.join(pkgDir, 'src', 'blocks'), {recursive: true});
|
|
387
|
+
fs.writeFileSync(
|
|
388
|
+
path.join(pkgDir, 'src', 'blocks', 'Carousel.doc.mjs'),
|
|
389
|
+
`export default {type: 'block', name: 'Carousel', displayName: 'Carousel', aspectRatio: 1};\n`,
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
const result = await validateLocalIntegration(pkgDir);
|
|
393
|
+
|
|
394
|
+
const [issue] = byCode(result.issues, 'unreachable_contribution');
|
|
395
|
+
expect(issue.message).toContain(
|
|
396
|
+
"Fix: move it under templates/ (the templates root), or set `templates: './src/blocks'` in astryx.integration.mjs.",
|
|
397
|
+
);
|
|
336
398
|
});
|
|
337
399
|
|
|
338
400
|
it('never executes unreachable metadata while diagnosing it', async () => {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file `doctor integration validate` warns about a folder under the themes
|
|
5
|
+
* root that holds modules but is not read as a theme, and both branches of
|
|
6
|
+
* its fix leave nothing to warn about.
|
|
7
|
+
*
|
|
8
|
+
* @input a local integration package with a themes root
|
|
9
|
+
* @output the unread_theme_folder warnings validation reports
|
|
10
|
+
* @position packages/cli/api/integration — validation regression tests
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
14
|
+
import * as fs from 'node:fs';
|
|
15
|
+
import * as path from 'node:path';
|
|
16
|
+
import {validateLocalIntegration} from './validate-integration.mjs';
|
|
17
|
+
import {integrationAddTheme} from './add-theme.mjs';
|
|
18
|
+
|
|
19
|
+
let tmpDir;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
tmpDir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-unread-theme-'));
|
|
23
|
+
fs.writeFileSync(
|
|
24
|
+
path.join(tmpDir, 'package.json'),
|
|
25
|
+
JSON.stringify({name: '@acme/kit', version: '1.0.0'}),
|
|
26
|
+
);
|
|
27
|
+
fs.writeFileSync(
|
|
28
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
29
|
+
"export default {themes: './themes'};\n",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/** @param {Record<string, string>} files paths under themes/ */
|
|
38
|
+
function writeThemes(files) {
|
|
39
|
+
for (const [file, contents] of Object.entries(files)) {
|
|
40
|
+
const target = path.join(tmpDir, 'themes', file);
|
|
41
|
+
fs.mkdirSync(path.dirname(target), {recursive: true});
|
|
42
|
+
fs.writeFileSync(target, contents);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const OCEAN = {
|
|
47
|
+
'ocean/oceanTheme.doc.mjs':
|
|
48
|
+
"/** @type {import('@astryxdesign/cli/authoring').ThemeDoc} */\nexport default {type: 'theme', name: 'ocean', displayName: 'Ocean', description: 'Blue.', maintained: true};\n",
|
|
49
|
+
'ocean/oceanTheme.ts':
|
|
50
|
+
"import {blue} from '@acme/kit/themes/shared/palette';\nexport const oceanTheme = {blue};\n",
|
|
51
|
+
'shared/palette.ts': 'export const blue = 1;\n',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
async function unread() {
|
|
55
|
+
const {issues} = await validateLocalIntegration(tmpDir);
|
|
56
|
+
return issues.filter(issue => issue.code === 'unread_theme_folder');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('unread theme folders', () => {
|
|
60
|
+
it('warns about each folder that looks like a theme but is not read as one', async () => {
|
|
61
|
+
writeThemes({
|
|
62
|
+
...OCEAN,
|
|
63
|
+
'sand/sand.ts': 'export const sand = {};\n',
|
|
64
|
+
'sand/sand.doc.ts': "export default {type: 'theme', name: 'sand'};\n",
|
|
65
|
+
'reef/index.ts': 'export const reefTheme = {};\n',
|
|
66
|
+
'Tide_Pool/index.ts': 'export const tide = {};\n',
|
|
67
|
+
'fonts/inter.woff2': 'x',
|
|
68
|
+
'utils/format.ts': 'export const format = String;\n',
|
|
69
|
+
'.cache/index.ts': 'export {};\n',
|
|
70
|
+
});
|
|
71
|
+
const issues = await unread();
|
|
72
|
+
expect(issues.map(issue => issue.severity)).toEqual([
|
|
73
|
+
'warning',
|
|
74
|
+
'warning',
|
|
75
|
+
'warning',
|
|
76
|
+
]);
|
|
77
|
+
expect(issues.map(issue => issue.message)).toEqual([
|
|
78
|
+
'Folder "themes/Tide_Pool/" holds modules but no <name>Theme source or .doc.mjs descriptor, so Astryx does not read it as a theme. Fix: rename it to .Tide_Pool so Astryx skips it; a theme folder needs a lower-kebab name.',
|
|
79
|
+
'Folder "themes/reef/" holds modules but no <name>Theme source or .doc.mjs descriptor, so Astryx does not read it as a theme. Fix: if it is a theme, add reefTheme.ts and reefTheme.doc.mjs to it (`astryx integration add theme reef` writes both) and move its code into reefTheme.ts; if not, rename it to .reef so Astryx skips it.',
|
|
80
|
+
'Folder "themes/sand/" holds modules but no <name>Theme source or .doc.mjs descriptor, so Astryx does not read it as a theme. Fix: if it is a theme, add sandTheme.ts and sandTheme.doc.mjs to it (`astryx integration add theme sand` writes both) and move its code into sandTheme.ts; if not, rename it to .sand so Astryx skips it.',
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('does not warn about a helper folder a module imports by a relative path', async () => {
|
|
85
|
+
writeThemes({
|
|
86
|
+
...OCEAN,
|
|
87
|
+
'index.ts':
|
|
88
|
+
"export * from './ocean/oceanTheme';\nexport * from './util';\n",
|
|
89
|
+
'util/index.ts': 'export const util = 1;\n',
|
|
90
|
+
});
|
|
91
|
+
expect(await unread()).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('holds when either branch of the fix is followed', async () => {
|
|
95
|
+
writeThemes({
|
|
96
|
+
...OCEAN,
|
|
97
|
+
'sand/sand.ts': 'export const sand = {};\n',
|
|
98
|
+
'reef/index.ts': 'export const reefTheme = {};\n',
|
|
99
|
+
'Tide_Pool/index.ts': 'export const tide = {};\n',
|
|
100
|
+
});
|
|
101
|
+
await integrationAddTheme('sand', {cwd: tmpDir});
|
|
102
|
+
await integrationAddTheme('reef', {cwd: tmpDir});
|
|
103
|
+
fs.renameSync(
|
|
104
|
+
path.join(tmpDir, 'themes', 'Tide_Pool'),
|
|
105
|
+
path.join(tmpDir, 'themes', '.Tide_Pool'),
|
|
106
|
+
);
|
|
107
|
+
const {issues} = await validateLocalIntegration(tmpDir);
|
|
108
|
+
expect(issues).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
});
|
package/api/json/isError.doc.mjs
CHANGED
package/api/search/search.mjs
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
* queries they have nothing to do with.
|
|
51
51
|
*/
|
|
52
52
|
|
|
53
|
-
import {
|
|
53
|
+
import {readDocView} from '../../foundation/doc-compiler/read.mjs';
|
|
54
54
|
import {findCoreDir} from '../../foundation/fs/paths.mjs';
|
|
55
55
|
import {
|
|
56
56
|
discoverComponents,
|
|
@@ -528,16 +528,26 @@ export function scoreCandidate(
|
|
|
528
528
|
}
|
|
529
529
|
|
|
530
530
|
/**
|
|
531
|
-
*
|
|
531
|
+
* A component or hook doc, compiled, or null when it cannot be read.
|
|
532
532
|
* @param {string} docPath
|
|
533
533
|
* @param {string} [exportName]
|
|
534
|
+
* @param {'components' | 'hooks'} [root]
|
|
534
535
|
* @returns {Promise<any>}
|
|
535
536
|
*/
|
|
536
|
-
async function loadModuleDoc(
|
|
537
|
+
async function loadModuleDoc(
|
|
538
|
+
docPath,
|
|
539
|
+
exportName = 'docs',
|
|
540
|
+
root = 'components',
|
|
541
|
+
) {
|
|
537
542
|
try {
|
|
538
|
-
const mod = await import(pathToFileURL(docPath).href);
|
|
539
543
|
// Support both the stamped default export and the legacy named export.
|
|
540
|
-
return
|
|
544
|
+
return (
|
|
545
|
+
(await readDocView(docPath, {
|
|
546
|
+
root,
|
|
547
|
+
loader: 'native',
|
|
548
|
+
exports: ['default', exportName],
|
|
549
|
+
})) ?? null
|
|
550
|
+
);
|
|
541
551
|
} catch {
|
|
542
552
|
return null;
|
|
543
553
|
}
|
|
@@ -701,7 +711,7 @@ async function gatherHooks(coreDir) {
|
|
|
701
711
|
let description = '';
|
|
702
712
|
let importPath = '@astryxdesign/core/hooks';
|
|
703
713
|
if (docPath) {
|
|
704
|
-
const doc = await loadModuleDoc(docPath);
|
|
714
|
+
const doc = await loadModuleDoc(docPath, 'docs', 'hooks');
|
|
705
715
|
if (doc) {
|
|
706
716
|
keywords = Array.isArray(doc.keywords) ? doc.keywords : [];
|
|
707
717
|
description = doc.usage?.description || doc.description || '';
|