@astryxdesign/cli 0.7.2-canary.ebcc404 → 0.7.2-canary.f2b60a0
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 +14 -0
- package/api/component/_adapter.mjs +48 -0
- package/api/component/component.mjs +10 -2
- package/api/component/integration-export-name.test.mjs +233 -8
- 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 +2 -1
- 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 +48 -3
- package/api/integration/pack-check.test.mjs +247 -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 +13 -6
- 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 +53 -17
- package/api/theme/build/build.family.test.mjs +54 -6
- package/api/theme/build/build.mjs +114 -17
- package/api/theme/themeBuild.doc.mjs +4 -4
- package/assets/docs/README.md +10 -0
- package/assets/docs/author-a-theme.doc.mjs +1 -1
- package/assets/docs/getting-started.doc.mjs +3 -3
- package/assets/docs/styling-overview.doc.mjs +1 -1
- package/assets/docs/theme.doc.dense.mjs +1 -1
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/docs/theme.doc.zh.mjs +1 -1
- package/assets/docs/tree/add-a-component.doc.mjs +11 -1
- package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
- package/assets/docs/tree/add-a-topic.doc.mjs +10 -7
- package/assets/docs/tree/agent-guidance.doc.mjs +2 -2
- package/assets/docs/tree/block-template.doc.mjs +8 -20
- package/assets/docs/tree/build-the-template.doc.mjs +1 -1
- package/assets/docs/tree/check-the-props.doc.mjs +196 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +51 -5
- package/assets/docs/tree/checks.doc.mjs +24 -16
- package/assets/docs/tree/codemods.doc.mjs +4 -4
- package/assets/docs/tree/component-family.doc.mjs +8 -6
- package/assets/docs/tree/component-imports.doc.mjs +3 -3
- 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/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 +7 -5
- package/authoring/doctypes/enum/enum.doc.mjs +7 -4
- package/authoring/doctypes/function/function.doc.mjs +9 -8
- package/authoring/doctypes/hook/hook.doc.mjs +5 -4
- package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
- package/authoring/doctypes/reference/reference.doc.mjs +11 -10
- package/authoring/doctypes/schema/schema.doc.mjs +9 -9
- package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
- package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
- package/authoring/doctypes/template/parse.d.mts +6 -2
- package/authoring/doctypes/template/parse.mjs +3 -1
- package/authoring/doctypes/template/template.doc.mjs +3 -3
- package/authoring/doctypes/template/type.ts +22 -0
- package/authoring/doctypes/theme/theme.doc.mjs +2 -2
- package/authoring/gap-report/gap-report.doc.mjs +2 -2
- package/authoring/identity/identity.doc.mjs +3 -3
- package/authoring/index.d.ts +1 -0
- package/authoring/integration/integration.doc.mjs +12 -4
- package/authoring/integration/parse.test.mjs +10 -0
- package/authoring/integration/schema.d.mts +2 -0
- package/authoring/integration/schema.mjs +1 -0
- package/authoring/integration/type.ts +4 -0
- package/clients/cli/commands/build-theme.batch-report.test.mjs +3 -1
- package/clients/cli/commands/build-theme.mjs +1 -1
- package/clients/cli/commands/build-theme.path-safety.test.mjs +155 -21
- package/clients/cli/commands/cli-postinstall.test.mjs +49 -1
- 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/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 +108 -17
- package/foundation/discovery/component-discovery.export-names.test.mjs +469 -2
- package/foundation/discovery/component-discovery.mjs +380 -47
- package/foundation/discovery/component-replacement.test.mjs +20 -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 +6 -0
- package/foundation/discovery/module-exports.mjs +15 -2
- package/foundation/discovery/package-tsconfig.d.mts +14 -0
- package/foundation/discovery/package-tsconfig.mjs +228 -0
- package/foundation/discovery/package-tsconfig.test.mjs +210 -0
- package/foundation/discovery/project-typescript.d.mts +23 -0
- package/foundation/discovery/project-typescript.mjs +61 -0
- package/foundation/discovery/prop-drift.d.mts +278 -0
- package/foundation/discovery/prop-drift.mjs +2263 -0
- package/foundation/discovery/prop-drift.test.mjs +2219 -0
- package/foundation/discovery/type-match.d.mts +53 -0
- package/foundation/discovery/type-match.mjs +660 -0
- package/foundation/discovery/type-match.test.mjs +169 -0
- package/foundation/discovery/type-text.d.mts +68 -0
- package/foundation/discovery/type-text.mjs +658 -0
- package/foundation/discovery/type-text.test.mjs +196 -0
- package/foundation/discovery/unresolved-references.d.mts +62 -0
- package/foundation/discovery/unresolved-references.mjs +790 -0
- package/foundation/discovery/unresolved-references.test.mjs +567 -0
- package/foundation/doc-compiler/doc-loads.test.mjs +6 -0
- package/foundation/doc-compiler/links.d.mts +9 -5
- package/foundation/doc-compiler/links.mjs +4 -2
- package/foundation/integrations/contribution-inventory.mjs +6 -1
- package/foundation/response/response-types.doc.mjs +7 -2
- package/package.json +9 -9
- package/scripts/postinstall.mjs +3 -3
|
@@ -75,14 +75,75 @@ async function placedGuides(route, seen = new Set()) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
describe('search domain priority — the rule', () => {
|
|
78
|
-
it('gives a doc no priority for a
|
|
78
|
+
it('gives a doc no priority for a heading match or a one-word keyword', () => {
|
|
79
79
|
const topic = {
|
|
80
80
|
domain: 'doc',
|
|
81
81
|
name: 'typography/font-setup',
|
|
82
|
-
keywords: ['
|
|
82
|
+
keywords: ['size'],
|
|
83
83
|
titles: ['Font Setup', 'Font Sizes'],
|
|
84
84
|
};
|
|
85
85
|
expect(priority('font size', topic)).toBe(0);
|
|
86
|
+
expect(priority('size', topic)).toBe(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('gives a doc priority when the query is one of its declared keywords', () => {
|
|
90
|
+
const guide = {
|
|
91
|
+
domain: 'doc',
|
|
92
|
+
name: 'guides/quality',
|
|
93
|
+
titles: ['Is my code good?'],
|
|
94
|
+
keywords: ['code review', 'review my code'],
|
|
95
|
+
_declared: ['code review', 'review my code'],
|
|
96
|
+
};
|
|
97
|
+
expect(priority('code review', guide)).toBe(1);
|
|
98
|
+
expect(priority('Code Reviews', guide)).toBe(1);
|
|
99
|
+
expect(priority('review my code', guide)).toBe(1);
|
|
100
|
+
// A section's keywords are its headings and code terms.
|
|
101
|
+
const section = {
|
|
102
|
+
...guide,
|
|
103
|
+
name: 'before-you-ship',
|
|
104
|
+
_topic: 'guides/quality',
|
|
105
|
+
_section: 'before-you-ship',
|
|
106
|
+
};
|
|
107
|
+
expect(priority('code review', section)).toBe(0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("gives a doc no priority for a code term or table cell it didn't declare", () => {
|
|
111
|
+
// A typed doc's keywords hold the first column of its tables and the code
|
|
112
|
+
// terms its text mentions; `missing_field` reads as two words.
|
|
113
|
+
const reference = {
|
|
114
|
+
domain: 'doc',
|
|
115
|
+
name: 'cli/api/functions/validate-integration',
|
|
116
|
+
titles: ['validateIntegration'],
|
|
117
|
+
keywords: ['validate-integration', 'missing_field', 'file required'],
|
|
118
|
+
_declared: [],
|
|
119
|
+
_topic: 'cli/api/functions/validate-integration',
|
|
120
|
+
};
|
|
121
|
+
expect(priority('missing field', reference)).toBe(0);
|
|
122
|
+
expect(priority('file required', reference)).toBe(0);
|
|
123
|
+
expect(
|
|
124
|
+
priority('missing field', {...reference, _declared: ['missing field']}),
|
|
125
|
+
).toBe(1);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('keeps a component the query names exactly above a doc that declares it', async () => {
|
|
129
|
+
const tokens = tokenizeQuery('empty state');
|
|
130
|
+
const component = {domain: 'component', name: 'EmptyState'};
|
|
131
|
+
const doc = {
|
|
132
|
+
domain: 'doc',
|
|
133
|
+
name: 'illustrations',
|
|
134
|
+
keywords: ['empty state'],
|
|
135
|
+
_declared: ['empty state'],
|
|
136
|
+
};
|
|
137
|
+
const c = scoreQuery('empty state', tokens, component);
|
|
138
|
+
const d = scoreQuery('empty state', tokens, doc);
|
|
139
|
+
expect(
|
|
140
|
+
domainPriority('empty state', tokens, /** @type {any} */ (component), c),
|
|
141
|
+
).toBe(1);
|
|
142
|
+
expect(
|
|
143
|
+
domainPriority('empty state', tokens, /** @type {any} */ (doc), d),
|
|
144
|
+
).toBe(1);
|
|
145
|
+
// Both have priority, so the stronger match wins: the exact name.
|
|
146
|
+
expect(c?.score).toBeGreaterThan(d?.score ?? 0);
|
|
86
147
|
});
|
|
87
148
|
|
|
88
149
|
it('gives a doc priority when the query names it', () => {
|
|
@@ -190,32 +251,61 @@ describe('search domain priority — the rule', () => {
|
|
|
190
251
|
|
|
191
252
|
describe('search domain priority — real docs and components', () => {
|
|
192
253
|
it(
|
|
193
|
-
'finds
|
|
254
|
+
'finds a guide that declares the whole phrase above a component that matches one word',
|
|
194
255
|
async () => {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
r => r.domain === '
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
256
|
+
const results = (await search('font size', {cwd})).data.results;
|
|
257
|
+
expect(results[0]).toMatchObject({domain: 'doc'});
|
|
258
|
+
expect(results[0].name).toMatch(/^typography(?:\/|$)/);
|
|
259
|
+
// The component the phrase points to stays near the top.
|
|
260
|
+
expect(
|
|
261
|
+
results
|
|
262
|
+
.slice(0, 3)
|
|
263
|
+
.some(r => r.domain === 'component' && r.name === 'Text'),
|
|
264
|
+
).toBe(true);
|
|
265
|
+
},
|
|
266
|
+
SLOW,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
it(
|
|
270
|
+
'keeps a component above a reference doc whose table or code terms hold the phrase',
|
|
271
|
+
async () => {
|
|
272
|
+
for (const q of ['missing field', 'file required']) {
|
|
273
|
+
const [top] = (await search(q, {cwd})).data.results;
|
|
274
|
+
expect(top, q).toMatchObject({domain: 'component', name: 'Field'});
|
|
206
275
|
}
|
|
207
276
|
},
|
|
208
277
|
SLOW,
|
|
209
278
|
);
|
|
210
279
|
|
|
211
280
|
it(
|
|
212
|
-
'
|
|
281
|
+
'opens the guide that answers a declared phrase, not one that only mentions it',
|
|
282
|
+
async () => {
|
|
283
|
+
const [layers] = (await search('cascade layers', {cwd})).data.results;
|
|
284
|
+
expect(layers).toMatchObject({
|
|
285
|
+
domain: 'doc',
|
|
286
|
+
name: 'migration/migration-setup',
|
|
287
|
+
});
|
|
288
|
+
const [keyboard] = (await search('keyboard navigation', {cwd})).data
|
|
289
|
+
.results;
|
|
290
|
+
expect(keyboard?.name).not.toBe('migration/migration-verification');
|
|
291
|
+
},
|
|
292
|
+
SLOW,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
it(
|
|
296
|
+
'keeps a component above docs that match only by a heading, with scores unchanged',
|
|
213
297
|
async () => {
|
|
214
298
|
const results = (await search('font size', {cwd})).data.results;
|
|
215
|
-
const
|
|
216
|
-
r => r.domain === '
|
|
299
|
+
const text = results.findIndex(
|
|
300
|
+
r => r.domain === 'component' && r.name === 'Text',
|
|
217
301
|
);
|
|
218
|
-
expect(
|
|
302
|
+
expect(text).toBeGreaterThanOrEqual(0);
|
|
303
|
+
// A doc below Text outscores it on text alone: priority moved it, not the score.
|
|
304
|
+
expect(
|
|
305
|
+
results
|
|
306
|
+
.slice(text + 1)
|
|
307
|
+
.some(r => r.domain === 'doc' && r.score > results[text].score),
|
|
308
|
+
).toBe(true);
|
|
219
309
|
},
|
|
220
310
|
SLOW,
|
|
221
311
|
);
|
|
@@ -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
|
+
});
|
package/api/search/search.mjs
CHANGED
|
@@ -57,9 +57,10 @@
|
|
|
57
57
|
* Between domains, ranking adds one rule ahead of the score: domain priority
|
|
58
58
|
* (see {@link domainPriority}). A component, hook, template, or theme whose
|
|
59
59
|
* name or keyword a query word hits ranks ahead of every doc the reader did not
|
|
60
|
-
* ask for by name or
|
|
61
|
-
*
|
|
62
|
-
* query holds,
|
|
60
|
+
* ask for by name, title, or a declared phrase, so `font size` finds `Text`
|
|
61
|
+
* before a token page whose heading holds the phrase. A doc the query names,
|
|
62
|
+
* whose title the query holds, or that declares the whole query as a keyword
|
|
63
|
+
* keeps its place.
|
|
63
64
|
*
|
|
64
65
|
* Description and guidance are separate tiers on purpose. A component's own
|
|
65
66
|
* one-line description saying "notification" is a claim about what it IS; the
|
|
@@ -92,7 +93,9 @@ import {
|
|
|
92
93
|
familyMember,
|
|
93
94
|
findComponentReadme,
|
|
94
95
|
forEachIntegrationDoc,
|
|
96
|
+
integrationMemberEntry,
|
|
95
97
|
integrationRecordHold,
|
|
98
|
+
isIntegrationMember,
|
|
96
99
|
resolveImportPath,
|
|
97
100
|
resolveIntegrationImportPath,
|
|
98
101
|
CORE_PACKAGE,
|
|
@@ -136,6 +139,9 @@ import {setResultCoverage} from './coverage.mjs';
|
|
|
136
139
|
* @property {string[]} [aliases] - Other names the candidate answers to, scored
|
|
137
140
|
* with the same name signals: a theme's display name.
|
|
138
141
|
* @property {string[]} [keywords]
|
|
142
|
+
* @property {string[]} [_declared] - A doc's own declared keywords: what its
|
|
143
|
+
* author lists in `keywords`, without the code terms and table cells its
|
|
144
|
+
* other keywords add.
|
|
139
145
|
* @property {string[]} [weakKeywords]
|
|
140
146
|
* @property {string} [description]
|
|
141
147
|
* @property {string[]} [prose]
|
|
@@ -511,19 +517,20 @@ const STRONG_TOKEN_SCORE = 70;
|
|
|
511
517
|
* typography/font-setup, `side panel` is "Side panels");
|
|
512
518
|
* - 1: a word of the query is the topic's name (`illustration` in a longer
|
|
513
519
|
* question is the illustrations guide);
|
|
520
|
+
* - 1: a query of two or more words is one of the topic's own declared
|
|
521
|
+
* keywords, whatever the plural (`code review` on a guide that declares it);
|
|
514
522
|
* - 1: the query holds one of the doc's titles whole (`resizable side panels`
|
|
515
523
|
* names "Side panels"; `light dark mode button` names "Light/Dark Mode");
|
|
516
524
|
* - 1: the doc matches every word of the query and one of them is a word of
|
|
517
525
|
* its title (`switch to a dark theme` and "Use a theme").
|
|
518
526
|
* A section answers to its topic's name.
|
|
519
527
|
*
|
|
520
|
-
* A doc that matched only by
|
|
528
|
+
* A doc that matched only by a heading holding the query, by one word, or by
|
|
521
529
|
* words spread through its text has no priority. Docs are split into many
|
|
522
|
-
* small topics,
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
* above the component. Broad reference pages match every word of many queries
|
|
530
|
+
* small topics, each with its own headings, so a common phrase such as
|
|
531
|
+
* `font size` hits a heading exactly (170) while the component the reader is
|
|
532
|
+
* after matches one word by keyword (`Text`, 98). Ranked on text alone, every
|
|
533
|
+
* split adds another doc above the component. Broad reference pages match every word of many queries
|
|
527
534
|
* in their text the same way. The score stays the text-match strength the
|
|
528
535
|
* result reports, so callers that gate on it (`build`) see the same numbers.
|
|
529
536
|
*
|
|
@@ -549,6 +556,20 @@ export function domainPriority(term, tokens, candidate, hit) {
|
|
|
549
556
|
if (named.some(n => samePhrase(term, n))) {
|
|
550
557
|
return 2;
|
|
551
558
|
}
|
|
559
|
+
// A query of two or more words that is one of the doc's own declared
|
|
560
|
+
// keywords, read as words whatever the plural, is the author naming the
|
|
561
|
+
// doc for that phrase: `code review` on a guide that declares it. Only
|
|
562
|
+
// what the author declared counts: the code terms and table cells a
|
|
563
|
+
// doc's other keywords add (`missing_field`) name what it mentions, not
|
|
564
|
+
// what it is for. One word is left out too, and a section declares
|
|
565
|
+
// nothing.
|
|
566
|
+
if (
|
|
567
|
+
phraseWords(term).length >= 2 &&
|
|
568
|
+
candidate._section == null &&
|
|
569
|
+
(candidate._declared ?? []).some(k => samePhrase(term, String(k)))
|
|
570
|
+
) {
|
|
571
|
+
return 1;
|
|
572
|
+
}
|
|
552
573
|
if (
|
|
553
574
|
words.some(
|
|
554
575
|
w =>
|
|
@@ -1144,24 +1165,31 @@ async function gatherIntegrationComponents(loadedIntegrations) {
|
|
|
1144
1165
|
const renamed = new Set(
|
|
1145
1166
|
discovered
|
|
1146
1167
|
.filter(rec => integrationRecordHold(rec) === 'doc')
|
|
1168
|
+
.filter(rec => !isIntegrationMember(rec))
|
|
1147
1169
|
.map(rec => rec.docPath),
|
|
1148
1170
|
);
|
|
1149
1171
|
forEachIntegrationDoc(integration.components, (docPath, stem) => {
|
|
1150
1172
|
if (!renamed.has(docPath)) hold(fileNamed, stem, docPath);
|
|
1151
1173
|
});
|
|
1152
1174
|
for (const rec of components) {
|
|
1153
|
-
const
|
|
1175
|
+
const loaded = await loadModuleDoc(rec.docPath);
|
|
1176
|
+
// A family member is indexed from its own entry: the family's keywords
|
|
1177
|
+
// and guidance describe the family.
|
|
1178
|
+
const member = isIntegrationMember(rec);
|
|
1179
|
+
const doc = member ? integrationMemberEntry(loaded, rec) : loaded;
|
|
1154
1180
|
// A doc that names another export of its file still answers to the
|
|
1155
1181
|
// file's name, as `component <file name>` does.
|
|
1156
1182
|
const fileName = docStem(rec.docPath);
|
|
1157
|
-
if (
|
|
1183
|
+
if (integrationRecordHold(rec) === 'doc') {
|
|
1158
1184
|
hold(documented, rec.name, rec.docPath);
|
|
1185
|
+
}
|
|
1186
|
+
if (renamed.has(rec.docPath) && !member) {
|
|
1159
1187
|
hold(documented, fileName, rec.docPath);
|
|
1160
1188
|
}
|
|
1161
1189
|
candidates.push({
|
|
1162
1190
|
domain: 'component',
|
|
1163
1191
|
name: rec.name,
|
|
1164
|
-
aliases: fileName !== rec.name ? [fileName] : [],
|
|
1192
|
+
aliases: !member && fileName !== rec.name ? [fileName] : [],
|
|
1165
1193
|
_hold: integrationRecordHold(rec),
|
|
1166
1194
|
_docPath: rec.docPath,
|
|
1167
1195
|
keywords: doc && Array.isArray(doc.keywords) ? doc.keywords : [],
|
|
@@ -1183,7 +1211,7 @@ async function gatherIntegrationComponents(loadedIntegrations) {
|
|
|
1183
1211
|
packageName: rec.package,
|
|
1184
1212
|
},
|
|
1185
1213
|
rec.name,
|
|
1186
|
-
|
|
1214
|
+
loaded?.import,
|
|
1187
1215
|
),
|
|
1188
1216
|
});
|
|
1189
1217
|
}
|
|
@@ -1319,10 +1347,13 @@ export async function componentKeywords(coreDir, cwd) {
|
|
|
1319
1347
|
const integrations = loadedIntegrations.map(async integration => {
|
|
1320
1348
|
const {components} = await discoverValidIntegrationComponents(integration);
|
|
1321
1349
|
return Promise.all(
|
|
1322
|
-
components.map(async rec =>
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1350
|
+
components.map(async rec => {
|
|
1351
|
+
const loaded = await loadModuleDoc(rec.docPath);
|
|
1352
|
+
return {
|
|
1353
|
+
name: rec.name,
|
|
1354
|
+
keywords: keywordsOf(integrationMemberEntry(loaded, rec) ?? loaded),
|
|
1355
|
+
};
|
|
1356
|
+
}),
|
|
1326
1357
|
);
|
|
1327
1358
|
});
|
|
1328
1359
|
return [
|
|
@@ -1496,6 +1527,10 @@ async function gatherDocs(cwd) {
|
|
|
1496
1527
|
...defined,
|
|
1497
1528
|
...codeTerms({content}),
|
|
1498
1529
|
],
|
|
1530
|
+
_declared: [
|
|
1531
|
+
...(Array.isArray(selfDoc?.keywords) ? selfDoc.keywords : []),
|
|
1532
|
+
...(Array.isArray(node.keywords) ? node.keywords : []),
|
|
1533
|
+
],
|
|
1499
1534
|
description: node.summary || '',
|
|
1500
1535
|
prose: sectionProse({title: node.title, content}),
|
|
1501
1536
|
titles: [node.title],
|
|
@@ -1632,6 +1667,7 @@ function topicCandidates(
|
|
|
1632
1667
|
...(doc?.title || title ? [doc?.title || title] : []),
|
|
1633
1668
|
...(Array.isArray(doc?.keywords) ? doc.keywords : []),
|
|
1634
1669
|
],
|
|
1670
|
+
_declared: Array.isArray(doc?.keywords) ? doc.keywords : [],
|
|
1635
1671
|
description: doc?.description || summary,
|
|
1636
1672
|
prose: split
|
|
1637
1673
|
? sections.map(section => section.title).filter(Boolean)
|
|
@@ -13,6 +13,7 @@ import {themeBuild, themeBuildFamily} from './build.mjs';
|
|
|
13
13
|
import {interceptCore} from './core-interception.mjs';
|
|
14
14
|
import {ensureCoreBuilt} from '../../../clients/cli/commands/ensure-core-built.mjs';
|
|
15
15
|
import {runCli} from '../../../test-utils/run-cli.mjs';
|
|
16
|
+
import {getCliInvocation} from '../../../foundation/env/package-manager.mjs';
|
|
16
17
|
vi.setConfig({testTimeout: 120_000});
|
|
17
18
|
const CLI_ROOT = path.resolve(import.meta.dirname, '../../..');
|
|
18
19
|
const FAMILY_KEY = 'ocean-family';
|
|
@@ -252,22 +253,69 @@ describe('themeBuildFamily()', () => {
|
|
|
252
253
|
expect(fs.existsSync(dtsPath)).toBe(false);
|
|
253
254
|
});
|
|
254
255
|
|
|
255
|
-
it('refuses family outputs outside cwd
|
|
256
|
+
it('refuses to write family outputs outside cwd, and checks them read-only', async () => {
|
|
256
257
|
const dir = makeDir();
|
|
257
258
|
const files = writeFamily(dir).map(file => path.join(dir, file));
|
|
258
259
|
const app = path.join(dir, 'app');
|
|
259
260
|
fs.mkdirSync(app);
|
|
260
261
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
262
|
+
await expect(
|
|
263
|
+
themeBuildFamily(files, {familyKey: FAMILY_KEY}, {cwd: app}),
|
|
264
|
+
).rejects.toMatchObject({code: 'ERR_PATH_TRAVERSAL'});
|
|
265
|
+
const checked = await themeBuildFamily(
|
|
266
|
+
files,
|
|
267
|
+
{familyKey: FAMILY_KEY, check: true},
|
|
268
|
+
{cwd: app},
|
|
269
|
+
);
|
|
270
|
+
expect(checked?.type).toBe('theme.build.check');
|
|
271
|
+
expect(checked?.data.upToDate).toBe(false);
|
|
272
|
+
// The `.css.d.ts` import stub may be absent in check mode.
|
|
273
|
+
expect(checked?.data.stale).toEqual(
|
|
274
|
+
OUTPUTS.filter(file => !file.endsWith('.css.d.ts')).map(file => ({
|
|
275
|
+
path: `../themes/${file}`,
|
|
276
|
+
reason: 'missing',
|
|
277
|
+
})),
|
|
278
|
+
);
|
|
266
279
|
for (const output of OUTPUTS) {
|
|
267
280
|
expect(fs.existsSync(path.join(dir, 'themes', output))).toBe(false);
|
|
268
281
|
}
|
|
269
282
|
});
|
|
270
283
|
|
|
284
|
+
it('ends a stale family report for outputs outside cwd with a remedy that works', async () => {
|
|
285
|
+
const dir = makeDir();
|
|
286
|
+
const files = writeFamily(dir).map(file => path.join(dir, file));
|
|
287
|
+
const app = path.join(dir, 'app');
|
|
288
|
+
fs.mkdirSync(app);
|
|
289
|
+
const family = ['--family', ...files, '--family-key', FAMILY_KEY];
|
|
290
|
+
|
|
291
|
+
const stale = await runCli(['theme', 'build', ...family, '--check'], app);
|
|
292
|
+
expect(stale.code).toBe(1);
|
|
293
|
+
// The old remedy ran the build from cwd, which refuses these outputs.
|
|
294
|
+
expect(stale.stderr).not.toContain('Rebuild with:');
|
|
295
|
+
|
|
296
|
+
const lines = stale.stderr.split('\n').map(line => line.trim());
|
|
297
|
+
const runDir = lines.find(line => line.startsWith('cd '))?.slice(3);
|
|
298
|
+
expect(runDir).toBe(fs.realpathSync(path.join(dir, 'themes')));
|
|
299
|
+
const stem = `${getCliInvocation(runDir)} theme build `;
|
|
300
|
+
const [buildArgs, checkArgs] = lines
|
|
301
|
+
.filter(line => line.startsWith(stem))
|
|
302
|
+
.map(line => line.slice(stem.length).split(' '));
|
|
303
|
+
expect(checkArgs).toEqual([...buildArgs, '--check']);
|
|
304
|
+
|
|
305
|
+
// Following the remedy builds, and the check from there passes.
|
|
306
|
+
const built = await runCli(['theme', 'build', ...buildArgs], runDir);
|
|
307
|
+
expect(built.code).toBe(0);
|
|
308
|
+
for (const output of OUTPUTS) {
|
|
309
|
+
expect(fs.existsSync(path.join(dir, 'themes', output))).toBe(true);
|
|
310
|
+
}
|
|
311
|
+
const checked = await runCli(
|
|
312
|
+
['--json', 'theme', 'build', ...checkArgs],
|
|
313
|
+
runDir,
|
|
314
|
+
);
|
|
315
|
+
expect(checked.code).toBe(0);
|
|
316
|
+
expect(JSON.parse(checked.stdout).data.upToDate).toBe(true);
|
|
317
|
+
});
|
|
318
|
+
|
|
271
319
|
it('fails without publishing a mixed trio when staging a write fails', async () => {
|
|
272
320
|
const dir = makeDir();
|
|
273
321
|
const files = writeFamily(dir);
|
|
@@ -27,8 +27,10 @@
|
|
|
27
27
|
* files; an internal callback lets the CLI make only the process status fail.
|
|
28
28
|
* Human progress is emitted through the shared `logger` (silent by default), so
|
|
29
29
|
* the CLI keeps its exact output while a programmatic caller stays quiet.
|
|
30
|
-
* Every output, explicit or beside the theme file, stays inside
|
|
31
|
-
* (`ERR_PATH_TRAVERSAL` otherwise)
|
|
30
|
+
* Every output a build writes, explicit or beside the theme file, stays inside
|
|
31
|
+
* `cwd` (`ERR_PATH_TRAVERSAL` otherwise). Check mode writes nothing, so it
|
|
32
|
+
* compares outputs outside `cwd` read-only; only a relative `out` path that
|
|
33
|
+
* escapes `cwd` is refused there too.
|
|
32
34
|
*
|
|
33
35
|
* The installed `@astryxdesign/core` is an independently versioned optional
|
|
34
36
|
* peer, so it can be older than the CLI. A theme that uses only baseline
|
|
@@ -60,7 +62,10 @@ import {isBuiltin} from 'node:module';
|
|
|
60
62
|
import {fileURLToPath, pathToFileURL} from 'node:url';
|
|
61
63
|
import {parse} from '@babel/parser';
|
|
62
64
|
import {createJiti} from 'jiti';
|
|
63
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
formatCliCommand,
|
|
67
|
+
getCliInvocation,
|
|
68
|
+
} from '../../../foundation/env/package-manager.mjs';
|
|
64
69
|
import {
|
|
65
70
|
CLI_ROOT,
|
|
66
71
|
findCoreDir,
|
|
@@ -568,6 +573,86 @@ function confineOutputs(writes, root, remedy) {
|
|
|
568
573
|
for (const write of writes) confineOutput(write.dest, root, remedy);
|
|
569
574
|
}
|
|
570
575
|
|
|
576
|
+
/** @param {string} dest absolute output path @param {string} root @returns {boolean} whether a build at `root` refuses to write `dest` */
|
|
577
|
+
function refusedOutput(dest, root) {
|
|
578
|
+
try {
|
|
579
|
+
confineOutput(dest, root, '');
|
|
580
|
+
return false;
|
|
581
|
+
} catch (err) {
|
|
582
|
+
if (
|
|
583
|
+
err instanceof AstryxError &&
|
|
584
|
+
err.code === ERROR_CODES.ERR_PATH_TRAVERSAL
|
|
585
|
+
)
|
|
586
|
+
return true;
|
|
587
|
+
throw err;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** @param {string[]} dirs absolute directories @returns {string | null} the deepest directory containing all of them, or null across roots */
|
|
592
|
+
function commonDirectory(dirs) {
|
|
593
|
+
let common = dirs[0];
|
|
594
|
+
for (const dir of dirs.slice(1)) {
|
|
595
|
+
while (
|
|
596
|
+
path.relative(common, dir).startsWith('..') ||
|
|
597
|
+
path.isAbsolute(path.relative(common, dir))
|
|
598
|
+
) {
|
|
599
|
+
const parent = path.dirname(common);
|
|
600
|
+
if (parent === common) return null;
|
|
601
|
+
common = parent;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return common;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** @param {string} value @returns {string} `value` as one shell word */
|
|
608
|
+
function shellWord(value) {
|
|
609
|
+
if (/^[A-Za-z0-9@%+=:,./_-]+$/.test(value)) return value;
|
|
610
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* How a stale check report ends when outputs sit outside the write root. A
|
|
615
|
+
* build there refuses them, and each output's `Source:` header records the
|
|
616
|
+
* directory its build ran from, so the build and the check both run from the
|
|
617
|
+
* nearest directory that contains every theme file and output.
|
|
618
|
+
* @param {Array<{dest: string}>} writes
|
|
619
|
+
* @param {string[]} sources absolute theme files the build reads
|
|
620
|
+
* @param {string} root the directory a build here keeps its writes inside
|
|
621
|
+
* @param {(relative: (file: string) => string) => string[]} args the arguments after `theme build`, given each path relative to the directory to run from
|
|
622
|
+
* @returns {string[] | null} report lines, or null when every output is inside `root`
|
|
623
|
+
*/
|
|
624
|
+
function outsideCheckRemedy(writes, sources, root, args) {
|
|
625
|
+
if (!writes.some(write => refusedOutput(write.dest, root))) return null;
|
|
626
|
+
const real = (/** @type {string} */ file) => realLocation(path.resolve(file));
|
|
627
|
+
const dir = commonDirectory([
|
|
628
|
+
...sources.map(file => path.dirname(real(file))),
|
|
629
|
+
...writes.map(write => path.dirname(real(write.dest))),
|
|
630
|
+
]);
|
|
631
|
+
if (!dir) {
|
|
632
|
+
return [
|
|
633
|
+
'These outputs are outside the working directory, so a build here refuses them. Build and check them from one directory that contains the theme files and the outputs.',
|
|
634
|
+
];
|
|
635
|
+
}
|
|
636
|
+
const command = (/** @type {string[]} */ extra) =>
|
|
637
|
+
formatCliCommand(
|
|
638
|
+
[
|
|
639
|
+
'theme',
|
|
640
|
+
'build',
|
|
641
|
+
...args(file => path.relative(dir, real(file))),
|
|
642
|
+
...extra,
|
|
643
|
+
]
|
|
644
|
+
.map(shellWord)
|
|
645
|
+
.join(' '),
|
|
646
|
+
dir,
|
|
647
|
+
);
|
|
648
|
+
return [
|
|
649
|
+
'These outputs are outside the working directory, so a build here refuses them, and each output records the directory it was built from. Build and check them from a directory that contains them:',
|
|
650
|
+
` cd ${shellWord(dir)}`,
|
|
651
|
+
` ${command([])}`,
|
|
652
|
+
` ${command(['--check'])}`,
|
|
653
|
+
];
|
|
654
|
+
}
|
|
655
|
+
|
|
571
656
|
/** @param {Array<{dest: string, content: string}>} writes */
|
|
572
657
|
function writeBuildOutputs(writes) {
|
|
573
658
|
if (writes.length === 0) return;
|
|
@@ -2859,9 +2944,13 @@ async function themeBuildInternal(
|
|
|
2859
2944
|
const writeRoot = options.__writeRoot ?? cwd;
|
|
2860
2945
|
let outPath;
|
|
2861
2946
|
if (options.out) {
|
|
2862
|
-
// Relative or absolute, the explicit output stays inside the write root.
|
|
2863
2947
|
outPath = path.resolve(cwd, options.out);
|
|
2864
|
-
|
|
2948
|
+
// A build keeps the explicit output inside the write root, relative or
|
|
2949
|
+
// absolute. Check mode writes nothing, so it refuses only a relative path
|
|
2950
|
+
// that escapes cwd.
|
|
2951
|
+
if (!options.check || !path.isAbsolute(options.out)) {
|
|
2952
|
+
confineOutput(outPath, writeRoot, OUT_REMEDY, options.out);
|
|
2953
|
+
}
|
|
2865
2954
|
} else {
|
|
2866
2955
|
outPath = path.join(path.dirname(filePath), `${baseName}.css`);
|
|
2867
2956
|
}
|
|
@@ -2957,10 +3046,12 @@ async function themeBuildInternal(
|
|
|
2957
3046
|
writes.push({dest: variantDtsPath, content: variantContent});
|
|
2958
3047
|
}
|
|
2959
3048
|
|
|
2960
|
-
// Every output stays inside cwd, including the defaults
|
|
2961
|
-
// file. Check mode
|
|
2962
|
-
//
|
|
2963
|
-
|
|
3049
|
+
// Every output a build writes stays inside cwd, including the defaults
|
|
3050
|
+
// beside the theme file. Check mode writes nothing, so it compares outputs
|
|
3051
|
+
// outside cwd read-only.
|
|
3052
|
+
if (!options.check) {
|
|
3053
|
+
confineOutputs(writes, writeRoot, options.out ? OUT_REMEDY : SOURCE_REMEDY);
|
|
3054
|
+
}
|
|
2964
3055
|
|
|
2965
3056
|
// Check mode: compare generated content against what's on disk instead of
|
|
2966
3057
|
// writing. A file is "stale" if it's missing or its content differs once the
|
|
@@ -2980,7 +3071,10 @@ async function themeBuildInternal(
|
|
|
2980
3071
|
` ${s.reason === 'missing' ? 'missing' : 'stale'}: ${s.path}`,
|
|
2981
3072
|
);
|
|
2982
3073
|
}
|
|
2983
|
-
|
|
3074
|
+
// prettier-ignore
|
|
3075
|
+
const remedy = outsideCheckRemedy(writes, [filePath], writeRoot, relative => [relative(filePath), ...(options.out ? ['--out', relative(outPath)] : []), ...(options.iconsSpecifier ? ['--icons-specifier', options.iconsSpecifier] : [])]);
|
|
3076
|
+
if (remedy) logger.error(`\n ${remedy.join('\n ')}`);
|
|
3077
|
+
else logger.error(`\n Rebuild with: ${buildCommand}`);
|
|
2984
3078
|
}
|
|
2985
3079
|
return {
|
|
2986
3080
|
type: 'theme.build.check',
|
|
@@ -3093,7 +3187,7 @@ function printBuildTrailer(trailer) {
|
|
|
3093
3187
|
logger.log(`
|
|
3094
3188
|
Install in your app (paths are relative to a file in src/; adjust if yours lives elsewhere):
|
|
3095
3189
|
|
|
3096
|
-
import { Theme } from '@astryxdesign/core';
|
|
3190
|
+
import { Theme } from '@astryxdesign/core/theme';
|
|
3097
3191
|
import { ${trailer.exportName} } from '${trailer.jsImport}';
|
|
3098
3192
|
import '${trailer.cssImport}';
|
|
3099
3193
|
|
|
@@ -3103,7 +3197,7 @@ Install in your app (paths are relative to a file in src/; adjust if yours lives
|
|
|
3103
3197
|
|
|
3104
3198
|
Or with a <link> tag:
|
|
3105
3199
|
|
|
3106
|
-
import { Theme } from '@astryxdesign/core';
|
|
3200
|
+
import { Theme } from '@astryxdesign/core/theme';
|
|
3107
3201
|
import { ${trailer.exportName} } from '${trailer.jsImport}';
|
|
3108
3202
|
|
|
3109
3203
|
<link rel="stylesheet" href="${trailer.cssImport}" />
|
|
@@ -3129,7 +3223,7 @@ export function printBatchTrailer(trailers) {
|
|
|
3129
3223
|
logger.log(`
|
|
3130
3224
|
Install in your app (paths are relative to a file in src/; adjust if yours lives elsewhere):
|
|
3131
3225
|
|
|
3132
|
-
import { Theme } from '@astryxdesign/core';
|
|
3226
|
+
import { Theme } from '@astryxdesign/core/theme';
|
|
3133
3227
|
import { ${first.exportName} } from '${first.jsImport}';
|
|
3134
3228
|
import '${first.cssImport}';
|
|
3135
3229
|
|
|
@@ -3139,7 +3233,7 @@ Install in your app (paths are relative to a file in src/; adjust if yours lives
|
|
|
3139
3233
|
|
|
3140
3234
|
Or with a <link> tag:
|
|
3141
3235
|
|
|
3142
|
-
import { Theme } from '@astryxdesign/core';
|
|
3236
|
+
import { Theme } from '@astryxdesign/core/theme';
|
|
3143
3237
|
import { ${first.exportName} } from '${first.jsImport}';
|
|
3144
3238
|
|
|
3145
3239
|
<link rel="stylesheet" href="${first.cssImport}" />
|
|
@@ -3506,8 +3600,8 @@ export async function themeBuildFamily(
|
|
|
3506
3600
|
js: path.relative(cwd, jsPath),
|
|
3507
3601
|
dts: path.relative(cwd, dtsPath),
|
|
3508
3602
|
};
|
|
3509
|
-
//
|
|
3510
|
-
confineOutputs(writes, cwd, FAMILY_REMEDY);
|
|
3603
|
+
// A family build's outputs stay inside cwd too; check mode only reads them.
|
|
3604
|
+
if (!options.check) confineOutputs(writes, cwd, FAMILY_REMEDY);
|
|
3511
3605
|
if (options.check) {
|
|
3512
3606
|
const stale = staleBuildOutputs(writes, cwd);
|
|
3513
3607
|
const upToDate = stale.length === 0;
|
|
@@ -3524,7 +3618,10 @@ export async function themeBuildFamily(
|
|
|
3524
3618
|
` ${entry.reason === 'missing' ? 'missing' : 'stale'}: ${entry.path}`,
|
|
3525
3619
|
);
|
|
3526
3620
|
}
|
|
3527
|
-
|
|
3621
|
+
// prettier-ignore
|
|
3622
|
+
const remedy = outsideCheckRemedy(writes, members.map(member => member.filePath), cwd, relative => ['--family', ...members.map(member => relative(member.filePath)), '--family-key', baseName, ...(options.iconsSpecifier ? ['--icons-specifier', options.iconsSpecifier] : [])]);
|
|
3623
|
+
if (remedy) logger.error(`\n ${remedy.join('\n ')}`);
|
|
3624
|
+
else logger.error(`\n Rebuild with: ${buildCommand}`);
|
|
3528
3625
|
}
|
|
3529
3626
|
return {
|
|
3530
3627
|
type: 'theme.build.check',
|