@astryxdesign/cli 0.7.1-canary.ff40b78 → 0.7.2-canary.c68f5e8
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/CHANGELOG.md +35 -0
- package/api/build/_adapter.d.mts +1 -0
- package/api/build/_adapter.mjs +7 -0
- package/api/build/build.mjs +4 -1
- package/api/search/search.mjs +12 -0
- package/api/template/table-filter-view-tabs.test.mjs +45 -0
- package/api/theme/build/build.core-on-demand.test.mjs +113 -0
- package/api/theme/build/build.d.mts +13 -13
- package/api/theme/build/build.mjs +72 -37
- package/assets/docs/getting-started.doc.mjs +0 -10
- package/assets/docs/styling-overview.doc.mjs +54 -15
- package/assets/docs/tree/migration-setup.doc.mjs +31 -6
- package/assets/docs/tree/responsive.doc.mjs +37 -0
- package/assets/docs/tree/tailwind.doc.mjs +12 -0
- package/assets/docs/tree/tokens-and-setup.doc.mjs +3 -2
- package/assets/docs/tree/tokens-syntax.doc.mjs +20 -0
- package/assets/docs/tree/tokens.doc.mjs +1 -1
- package/assets/templates/blocks/components/CommandPalette/CommandPaletteAsyncSearch.tsx +6 -1
- package/assets/templates/pages/table-filter/page.tsx +3 -2
- package/assets/templates/themes/butter/butterTheme.ts +53 -11
- package/assets/templates/themes/chocolate/chocolateTheme.ts +29 -5
- package/assets/templates/themes/gothic/gothicTheme.ts +44 -9
- package/assets/templates/themes/stone/stoneTheme.ts +58 -11
- package/assets/templates/themes/y2k/y2kTheme.ts +60 -10
- package/clients/cli/commands/build-theme.mjs +32 -4
- package/clients/cli/commands/build-theme.watch.test.mjs +63 -2
- package/foundation/discovery/component-discovery.mjs +27 -26
- package/foundation/discovery/hook-discovery.mjs +13 -12
- package/foundation/fs/listing-cache.d.mts +36 -0
- package/foundation/fs/listing-cache.mjs +95 -0
- package/foundation/fs/listing-cache.test.mjs +88 -0
- package/foundation/fs/paths.mjs +3 -2
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# @xds/cli
|
|
2
2
|
|
|
3
|
+
# 0.7.1
|
|
4
|
+
|
|
5
|
+
#### Fixes
|
|
6
|
+
|
|
7
|
+
- `CommandPaletteInput` now exposes the semantics and name that its visible state promises. (#7321)
|
|
8
|
+
- A standalone field with no popup attributes was `role="combobox"` with `aria-expanded="true"` and `aria-autocomplete="list"`; it is now `role="searchbox"` with no popup state. Pass `role="combobox"`, or your popup's own ARIA attributes, to keep the old semantics. A field that already passes popup attributes (`aria-expanded`, `aria-controls`, `aria-owns`, `aria-haspopup`, and the like) stays a `combobox` and keeps them, and a field inside `CommandPalette` is unchanged.
|
|
9
|
+
|
|
10
|
+
- The field's default accessible name was its placeholder (`Search…`, or the caller's `placeholder`); it is now `Search commands`, translated. Pass `label` to keep a specific name, for example `label="Search…"` for the old default. A caller whose custom `placeholder` served as the name keeps working code and hears the generic name until it passes `label`.
|
|
11
|
+
|
|
12
|
+
- The CLI's async-search CommandPalette block names its field `Search files`.
|
|
13
|
+
|
|
14
|
+
- `endContent={true}` no longer renders an empty trailing slot, and `endContent={0}` renders inside the trailing slot instead of after it.
|
|
15
|
+
- Stone, Y2K, and Gothic set `CodeBlock`'s diff row slots in their own syntax palettes, so a reader of a diff on those themes gets added and removed rows whose code, line numbers, and `+`/`-` glyphs stay readable: every syntax token keeps at least 4.5:1 on the tinted rows in Stone (light and dark) and Y2K (light and dark), and in Gothic the rows take a deep green and a deep red at the background's own luminance, so every token keeps the contrast it has on the plain background. Line numbers and glyphs on changed rows use each theme's own green and red. The CLI's bundled copies of the three themes (`astryx theme eject`) carry the same palettes. (#7428)
|
|
16
|
+
- A `secondary` Selector, MultiSelector, or ComplexSelector now matches the theme's secondary Button: under butter, chocolate, gothic, stone, and y2k it takes that Button's border, fill, text color, and hover instead of the default borderless neutral fill, so a toolbar mixing secondary buttons and selectors reads as one set of controls. Disabled and read-only triggers keep a still fill on hover, as a disabled Button does, and a Selector or MultiSelector inside an InputGroup keeps the group's neutral fill and edges. The default theme is unchanged, the selector docs now say `secondary` follows the theme's secondary Button, and themes added with the CLI get the same parity. (#7548)
|
|
17
|
+
- `astryx theme build --watch` stops as soon as it is interrupted (Ctrl-C or SIGTERM). A change saved while a rebuild was running no longer starts another rebuild after "Stopped watching.", and a rebuild in progress stops with the watcher instead of keeping the process alive. (#7508)
|
|
18
|
+
|
|
19
|
+
#### Performance
|
|
20
|
+
|
|
21
|
+
- `astryx search` and `astryx build`, and the `search` and `build` API functions, read each source directory once per call instead of once per candidate. Results are unchanged. A warm `search` drops from about 1.9 s to 0.4 s on macOS. (#7421)
|
|
22
|
+
|
|
23
|
+
#### Documentation
|
|
24
|
+
|
|
25
|
+
- The generated token docs list the five diff row slots of the syntax vocabulary — `--color-syntax-added`, `--color-syntax-removed`, `--color-syntax-diff-header`, `--color-syntax-added-marker`, `--color-syntax-removed-marker` — with their defaults, so `astryx docs tokens` describes nineteen syntax slots. (#7389)
|
|
26
|
+
- The Tailwind and migration setup docs now say that `globals.css` (or a `layers.css` holding only the `@layer` line) must be the first import in the root layout or app entry, before the `astryx-themes` module or any component that imports theme CSS; otherwise Tailwind preflight overrides Astryx base styles and theme overrides lose. The Tailwind v3 coexistence snippet moves its `@layer` line into a `layers.css` imported first, because Next.js (webpack or Turbopack) hoists CSS imports above an inline `@layer` line. (#7534)
|
|
27
|
+
- Grid's `columns` docs now lead with the shape that reflows: a numeric `columns` is exactly N columns at every width and never reflows, and `columns={{minWidth, max}}` is the form for columns that must survive a phone. The `astryx docs layout/responsive` topic gains a "Columns that reflow" section with a reflowing stat row beside a fixed week row. No behavior changes. (#7340)
|
|
28
|
+
- The styling setup docs now load the prebuilt stylesheets on every path. The "StyleX from source" path becomes "StyleX for your own code": it keeps `astryx.css` and points the StyleX compiler at your own and swizzled source only. A new "Other Astryx packages" section lists each package's stylesheet (`richtext.css`, `lab.css`, `charts.css`) and where to import it. Getting started no longer lists the two example setups that compile Astryx source; the remaining examples all load `astryx.css`. (#7587)
|
|
29
|
+
|
|
30
|
+
#### Contributors
|
|
31
|
+
|
|
32
|
+
Thanks to everyone who contributed to this release:
|
|
33
|
+
|
|
34
|
+
- @cixzhang
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
3
38
|
# 0.7.0
|
|
4
39
|
|
|
5
40
|
#### Breaking Changes
|
package/api/build/_adapter.d.mts
CHANGED
|
@@ -66,6 +66,7 @@ export function isWeightsFile(file: any): file is import("./kit/weights.mjs").We
|
|
|
66
66
|
* @returns {string[]} Setup notes (empty when the template needs nothing).
|
|
67
67
|
*/
|
|
68
68
|
export function templateSetupNotes(template: PageTemplate, cwd: string): string[];
|
|
69
|
+
export { withListingCache } from "../../foundation/fs/listing-cache.mjs";
|
|
69
70
|
/**
|
|
70
71
|
* A page template the kit can recommend starting from.
|
|
71
72
|
*/
|
package/api/build/_adapter.mjs
CHANGED
|
@@ -23,6 +23,13 @@ import {componentKeywords} from '../search/search.mjs';
|
|
|
23
23
|
import {findCoreDir} from '../../foundation/fs/paths.mjs';
|
|
24
24
|
import {analyzeTemplateNeeds} from '../../foundation/discovery/template-needs.mjs';
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Runs one build with a single directory listing per path: the kit resolves
|
|
28
|
+
* every candidate's source and docs by walking the same tree, which does not
|
|
29
|
+
* change during the call.
|
|
30
|
+
*/
|
|
31
|
+
export {withListingCache} from '../../foundation/fs/listing-cache.mjs';
|
|
32
|
+
|
|
26
33
|
/**
|
|
27
34
|
* A page template the kit can recommend starting from.
|
|
28
35
|
* @typedef {object} PageTemplate
|
package/api/build/build.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import {AstryxError} from '../error.mjs';
|
|
|
17
17
|
import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
|
|
18
18
|
import {buildHelp} from './help/help.mjs';
|
|
19
19
|
import {buildKit} from './kit/kit.mjs';
|
|
20
|
+
import {withListingCache} from './_adapter.mjs';
|
|
20
21
|
|
|
21
22
|
// Barrel: surface the leaves alongside the dispatcher for the fractal shape.
|
|
22
23
|
export {buildHelp, buildKit};
|
|
@@ -40,5 +41,7 @@ export async function build(query, options = {}) {
|
|
|
40
41
|
ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
41
42
|
);
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
+
// The kit resolves every candidate's source and docs by walking the same
|
|
45
|
+
// tree; read each directory once for this call.
|
|
46
|
+
return withListingCache(() => buildKit(query, options));
|
|
44
47
|
}
|
package/api/search/search.mjs
CHANGED
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
* queries they have nothing to do with.
|
|
80
80
|
*/
|
|
81
81
|
|
|
82
|
+
import {withListingCache} from '../../foundation/fs/listing-cache.mjs';
|
|
82
83
|
import {readDocView} from '../../foundation/doc-compiler/read.mjs';
|
|
83
84
|
import {findCoreDir} from '../../foundation/fs/paths.mjs';
|
|
84
85
|
import {
|
|
@@ -1769,6 +1770,17 @@ function toResult(c, score, reason, matchedTerms, queryTerms) {
|
|
|
1769
1770
|
* @returns {Promise<import('./search.type.mjs').SearchResponse>}
|
|
1770
1771
|
*/
|
|
1771
1772
|
export async function search(query, options = {}) {
|
|
1773
|
+
// Every candidate resolves its source and docs by walking the same tree;
|
|
1774
|
+
// read each directory once for this call.
|
|
1775
|
+
return withListingCache(() => searchOnce(query, options));
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
/**
|
|
1779
|
+
* @param {string} query
|
|
1780
|
+
* @param {NonNullable<Parameters<typeof search>[1]>} options
|
|
1781
|
+
* @returns {ReturnType<typeof search>}
|
|
1782
|
+
*/
|
|
1783
|
+
async function searchOnce(query, options) {
|
|
1772
1784
|
const {cwd = process.cwd(), type, limit = 20} = options;
|
|
1773
1785
|
|
|
1774
1786
|
if (!query || !String(query).trim()) {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Regression coverage for the table-filter page's compact view-options tabs.
|
|
5
|
+
* @input The shipped table-filter page template source.
|
|
6
|
+
* @output Assertions that the shared tabpanel is labelled by its selected tab.
|
|
7
|
+
* @position Template contract test for the tabs relationship in the narrow-view sheet.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import {fileURLToPath} from 'node:url';
|
|
13
|
+
import {describe, expect, it} from 'vitest';
|
|
14
|
+
|
|
15
|
+
const sourcePath = path.resolve(
|
|
16
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
17
|
+
'../../assets/templates/pages/table-filter/page.tsx',
|
|
18
|
+
);
|
|
19
|
+
const source = fs.readFileSync(sourcePath, 'utf8');
|
|
20
|
+
const sheet = source.match(
|
|
21
|
+
/const viewOptionsSheet = \(([\s\S]*?)\n {2}\);/,
|
|
22
|
+
)?.[1];
|
|
23
|
+
const tab = sheet?.match(/<Tab\b([\s\S]*?)\/>/)?.[1];
|
|
24
|
+
const panel = sheet?.match(/<VStack\b([^>]*?role="tabpanel"[^>]*?)>/)?.[1];
|
|
25
|
+
|
|
26
|
+
describe('table-filter view-options tabs', () => {
|
|
27
|
+
it('finds the compact sheet, its tab and its panel', () => {
|
|
28
|
+
expect(sheet).toBeDefined();
|
|
29
|
+
expect(tab).toBeDefined();
|
|
30
|
+
expect(panel).toBeDefined();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('gives every tab an id derived from the panel id and its section', () => {
|
|
34
|
+
expect(tab).toContain('id={`${viewPanelId}-tab-${section.key}`}');
|
|
35
|
+
expect(tab).toContain('panelId={viewPanelId}');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('labels the shared panel by the selected tab, not a fixed string', () => {
|
|
39
|
+
expect(panel).toContain('id={viewPanelId}');
|
|
40
|
+
expect(panel).toContain(
|
|
41
|
+
'aria-labelledby={`${viewPanelId}-tab-${viewSection}`}',
|
|
42
|
+
);
|
|
43
|
+
expect(panel).not.toContain('aria-label=');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file The theme build imports Core when a build runs, not when the CLI starts.
|
|
5
|
+
*
|
|
6
|
+
* The CLI loads the theme build module for every command, and Core's root
|
|
7
|
+
* entry pulls in all of Core and React. So a command that builds no theme must
|
|
8
|
+
* not ask for either, or `astryx --version` pays for them whenever a Core is
|
|
9
|
+
* installed. A resolve hook in a real subprocess records every request for
|
|
10
|
+
* Core or React and the module that made it, whether or not the request
|
|
11
|
+
* resolves.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {afterAll, beforeAll, describe, expect, it} from 'vitest';
|
|
15
|
+
import {spawnSync} from 'node:child_process';
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import * as os from 'node:os';
|
|
18
|
+
import * as path from 'node:path';
|
|
19
|
+
import {fileURLToPath, pathToFileURL} from 'node:url';
|
|
20
|
+
|
|
21
|
+
const BIN = fileURLToPath(
|
|
22
|
+
new URL('../../../clients/cli/bin/astryx.mjs', import.meta.url),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
/** @type {string} */
|
|
26
|
+
let dir;
|
|
27
|
+
/** @type {string} */
|
|
28
|
+
let register;
|
|
29
|
+
/** @type {string} */
|
|
30
|
+
let log;
|
|
31
|
+
|
|
32
|
+
beforeAll(() => {
|
|
33
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-core-on-demand-'));
|
|
34
|
+
log = path.join(dir, 'requests.jsonl');
|
|
35
|
+
const hooks = path.join(dir, 'hooks.mjs');
|
|
36
|
+
fs.writeFileSync(
|
|
37
|
+
hooks,
|
|
38
|
+
[
|
|
39
|
+
"import {appendFileSync} from 'node:fs';",
|
|
40
|
+
`const LOG = ${JSON.stringify(log)};`,
|
|
41
|
+
'const WATCHED = /^(?:@astryxdesign\\/core|react|react-dom)(?:\\/|$)/;',
|
|
42
|
+
'export async function resolve(specifier, context, nextResolve) {',
|
|
43
|
+
' if (WATCHED.test(specifier)) {',
|
|
44
|
+
' const parent = context.parentURL ?? null;',
|
|
45
|
+
" appendFileSync(LOG, JSON.stringify({specifier, parent}) + '\\n');",
|
|
46
|
+
' }',
|
|
47
|
+
' return nextResolve(specifier, context);',
|
|
48
|
+
'}',
|
|
49
|
+
'',
|
|
50
|
+
].join('\n'),
|
|
51
|
+
);
|
|
52
|
+
register = path.join(dir, 'register.mjs');
|
|
53
|
+
fs.writeFileSync(
|
|
54
|
+
register,
|
|
55
|
+
`import {register} from 'node:module';\nregister(${JSON.stringify(pathToFileURL(hooks).href)});\n`,
|
|
56
|
+
);
|
|
57
|
+
// A plain theme object: the file itself never imports Core, so a request for
|
|
58
|
+
// Core can only come from the build.
|
|
59
|
+
fs.writeFileSync(
|
|
60
|
+
path.join(dir, 'ocean.mjs'),
|
|
61
|
+
"export default {name: 'ocean', tokens: {'--color-bg': '#0a0a0a'}};\n",
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterAll(() => {
|
|
66
|
+
fs.rmSync(dir, {recursive: true, force: true});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Run the CLI and return each Core or React specifier it asked for, mapped to
|
|
71
|
+
* the first module that asked.
|
|
72
|
+
* @param {string[]} args
|
|
73
|
+
* @returns {{status: number | null, requested: Record<string, string | null>}}
|
|
74
|
+
*/
|
|
75
|
+
function run(args) {
|
|
76
|
+
fs.rmSync(log, {force: true});
|
|
77
|
+
const res = spawnSync(
|
|
78
|
+
process.execPath,
|
|
79
|
+
['--import', pathToFileURL(register).href, BIN, ...args],
|
|
80
|
+
{cwd: dir, encoding: 'utf-8', timeout: 20_000},
|
|
81
|
+
);
|
|
82
|
+
/** @type {Record<string, string | null>} */
|
|
83
|
+
const requested = {};
|
|
84
|
+
const lines = fs.existsSync(log)
|
|
85
|
+
? fs.readFileSync(log, 'utf-8').split('\n').filter(Boolean)
|
|
86
|
+
: [];
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
const {specifier, parent} = JSON.parse(line);
|
|
89
|
+
if (!(specifier in requested)) requested[specifier] = parent;
|
|
90
|
+
}
|
|
91
|
+
return {status: res.status, requested};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
describe('theme build imports Core on demand', () => {
|
|
95
|
+
it('astryx --version asks for neither Core nor React', () => {
|
|
96
|
+
const {status, requested} = run(['--version']);
|
|
97
|
+
expect(status).toBe(0);
|
|
98
|
+
expect(requested).toEqual({});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('theme build imports Core from the theme build module', () => {
|
|
102
|
+
const {requested} = run([
|
|
103
|
+
'theme',
|
|
104
|
+
'build',
|
|
105
|
+
'ocean.mjs',
|
|
106
|
+
'--check',
|
|
107
|
+
'--json',
|
|
108
|
+
]);
|
|
109
|
+
expect(requested['@astryxdesign/core/theme']).toMatch(
|
|
110
|
+
/\/api\/theme\/build\/build\.mjs$/,
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -163,6 +163,19 @@ export function themeBuildFamily(files: string[], options: {
|
|
|
163
163
|
checked?: undefined;
|
|
164
164
|
};
|
|
165
165
|
}>;
|
|
166
|
+
/**
|
|
167
|
+
* The bindings one Core provides: the namespaces interception spreads, the
|
|
168
|
+
* functions the build calls, and why the import failed, if it did.
|
|
169
|
+
*/
|
|
170
|
+
export type CoreBindings = {
|
|
171
|
+
themeModule: any;
|
|
172
|
+
rootModule: any;
|
|
173
|
+
importError: any;
|
|
174
|
+
defineTheme: any;
|
|
175
|
+
generateThemeRulesSplit: any;
|
|
176
|
+
generateOnMediaCSS: any;
|
|
177
|
+
generateAdaptationCSS: any;
|
|
178
|
+
};
|
|
166
179
|
/**
|
|
167
180
|
* How one `prop:value` pair on a component target is classified — the single
|
|
168
181
|
* vocabulary both the root and adaptation paths reason in.
|
|
@@ -210,16 +223,3 @@ export type ThemeBuildTrailer = {
|
|
|
210
223
|
*/
|
|
211
224
|
fontNotices: string[];
|
|
212
225
|
};
|
|
213
|
-
/**
|
|
214
|
-
* The bindings one Core provides: the namespaces interception spreads, the
|
|
215
|
-
* functions the build calls, and why the import failed, if it did.
|
|
216
|
-
*/
|
|
217
|
-
export type CoreBindings = {
|
|
218
|
-
themeModule: any;
|
|
219
|
-
rootModule: any;
|
|
220
|
-
importError: any;
|
|
221
|
-
defineTheme: any;
|
|
222
|
-
generateThemeRulesSplit: any;
|
|
223
|
-
generateOnMediaCSS: any;
|
|
224
|
-
generateAdaptationCSS: any;
|
|
225
|
-
};
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
* general compatibility scheme for every export an arbitrary older core might
|
|
38
38
|
* lack.
|
|
39
39
|
*
|
|
40
|
+
* Core is imported on first use, never when this module loads: the CLI loads
|
|
41
|
+
* this module for every command, and Core's root entry pulls in all of Core
|
|
42
|
+
* and React.
|
|
43
|
+
*
|
|
40
44
|
* With `{check: true}`, it compiles the same outputs in memory but writes
|
|
41
45
|
* nothing: it compares each generated file against what is on disk (ignoring
|
|
42
46
|
* only the volatile `@generated` `Command:` line) and returns a
|
|
@@ -91,6 +95,8 @@ import {resolveIconImports} from './icon-imports.mjs';
|
|
|
91
95
|
// action). A built, resolvable `@astryxdesign/core` is a hard requirement.
|
|
92
96
|
// A built, resolvable `@astryxdesign/core` is a hard requirement. These are
|
|
93
97
|
// populated from a dynamic import (a runtime boundary), so `any` is intentional.
|
|
98
|
+
// That import runs the first time a build needs Core (see `loadCliCore`), not
|
|
99
|
+
// when this module loads.
|
|
94
100
|
//
|
|
95
101
|
// The CLI and core are independently versioned optional peers, so this import
|
|
96
102
|
// can succeed against a core OLDER than the CLI. Only the exports every theme
|
|
@@ -120,27 +126,6 @@ import {resolveIconImports} from './icon-imports.mjs';
|
|
|
120
126
|
*/
|
|
121
127
|
/** @type {any} */ let _coreRootModule = null;
|
|
122
128
|
/** @type {any} */ let _coreImportError = null;
|
|
123
|
-
try {
|
|
124
|
-
const coreTheme = await import('@astryxdesign/core/theme');
|
|
125
|
-
_coreThemeModule = coreTheme;
|
|
126
|
-
_defineTheme = coreTheme.defineTheme;
|
|
127
|
-
_generateThemeRulesSplit = coreTheme.generateThemeRulesSplit;
|
|
128
|
-
_generateOnMediaCSS = coreTheme.generateOnMediaCSS;
|
|
129
|
-
_generateAdaptationCSS = coreTheme.generateAdaptationCSS;
|
|
130
|
-
try {
|
|
131
|
-
_coreRootModule = await import('@astryxdesign/core');
|
|
132
|
-
} catch {
|
|
133
|
-
// A core without a usable root entry still builds themes; interception
|
|
134
|
-
// falls back to the theme namespace for the root specifier.
|
|
135
|
-
}
|
|
136
|
-
} catch (e) {
|
|
137
|
-
// Capture the reason so the theme action can surface a precise, actionable
|
|
138
|
-
// error. We don't throw here: this module is imported eagerly by the CLI
|
|
139
|
-
// entrypoint for every command, and a throw at load time would break
|
|
140
|
-
// unrelated commands (the entry wraps loads in try/catch and degrades the
|
|
141
|
-
// command to a stub). The hard failure happens when `theme build` runs.
|
|
142
|
-
_coreImportError = e;
|
|
143
|
-
}
|
|
144
129
|
|
|
145
130
|
/**
|
|
146
131
|
* The bindings one Core provides: the namespaces interception spreads, the
|
|
@@ -157,19 +142,65 @@ try {
|
|
|
157
142
|
*/
|
|
158
143
|
|
|
159
144
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
145
|
+
* Import the CLI's own Core: the `@astryxdesign/core` this module resolves.
|
|
146
|
+
* Never throws. A failed import is kept as `importError`, so the build can
|
|
147
|
+
* surface a precise, actionable error when it runs.
|
|
148
|
+
* @returns {Promise<CoreBindings>}
|
|
163
149
|
*/
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
150
|
+
async function importCliCore() {
|
|
151
|
+
/** @type {CoreBindings} */
|
|
152
|
+
const core = {
|
|
153
|
+
themeModule: null,
|
|
154
|
+
rootModule: null,
|
|
155
|
+
importError: null,
|
|
156
|
+
defineTheme: null,
|
|
157
|
+
generateThemeRulesSplit: null,
|
|
158
|
+
generateOnMediaCSS: null,
|
|
159
|
+
generateAdaptationCSS: null,
|
|
160
|
+
};
|
|
161
|
+
try {
|
|
162
|
+
const coreTheme = await import('@astryxdesign/core/theme');
|
|
163
|
+
core.themeModule = coreTheme;
|
|
164
|
+
core.defineTheme = coreTheme.defineTheme;
|
|
165
|
+
core.generateThemeRulesSplit = coreTheme.generateThemeRulesSplit;
|
|
166
|
+
core.generateOnMediaCSS = coreTheme.generateOnMediaCSS;
|
|
167
|
+
core.generateAdaptationCSS = coreTheme.generateAdaptationCSS;
|
|
168
|
+
try {
|
|
169
|
+
core.rootModule = await import('@astryxdesign/core');
|
|
170
|
+
} catch {
|
|
171
|
+
// A core without a usable root entry still builds themes; interception
|
|
172
|
+
// falls back to the theme namespace for the root specifier.
|
|
173
|
+
}
|
|
174
|
+
} catch (e) {
|
|
175
|
+
// Capture the reason so the theme action can surface a precise, actionable
|
|
176
|
+
// error. The hard failure happens when `theme build` runs.
|
|
177
|
+
core.importError = e;
|
|
178
|
+
}
|
|
179
|
+
return core;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The CLI's own Core, imported once, the first time a build needs it. The CLI
|
|
184
|
+
* loads this module for every command, and Core's root entry loads all of Core
|
|
185
|
+
* and React, so an import at module load would make every command
|
|
186
|
+
* (`--version` included) pay for that whenever a Core resolves.
|
|
187
|
+
* @type {Promise<CoreBindings> | null}
|
|
188
|
+
*/
|
|
189
|
+
let _cliCorePromise = null;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The CLI's own Core, kept so each build can choose between it and the
|
|
193
|
+
* project's installed Core. The first call also points the build at it, so a
|
|
194
|
+
* step that runs before any build has chosen a Core reads the CLI's own.
|
|
195
|
+
* @returns {Promise<CoreBindings>}
|
|
196
|
+
*/
|
|
197
|
+
function loadCliCore() {
|
|
198
|
+
_cliCorePromise ??= importCliCore().then(core => {
|
|
199
|
+
setCore(core);
|
|
200
|
+
return core;
|
|
201
|
+
});
|
|
202
|
+
return _cliCorePromise;
|
|
203
|
+
}
|
|
173
204
|
|
|
174
205
|
/**
|
|
175
206
|
* Point the build at one Core: the module-level bindings every step reads.
|
|
@@ -248,13 +279,14 @@ function realOrSelf(file) {
|
|
|
248
279
|
* repository, a bare directory), the same Core, or one that does not load.
|
|
249
280
|
*
|
|
250
281
|
* @param {string} cwd
|
|
282
|
+
* @param {CoreBindings} cliCore - The CLI's own Core, from {@link loadCliCore}.
|
|
251
283
|
* @returns {Promise<CoreBindings | null>}
|
|
252
284
|
*/
|
|
253
|
-
async function loadProjectCore(cwd) {
|
|
285
|
+
async function loadProjectCore(cwd, cliCore) {
|
|
254
286
|
const dir = findInstalledPackage(cwd, '@astryxdesign/core');
|
|
255
287
|
const themeFile = dir ? importTarget(dir, './theme') : null;
|
|
256
288
|
if (!dir || !themeFile) return null;
|
|
257
|
-
if (
|
|
289
|
+
if (cliCore.themeModule) {
|
|
258
290
|
try {
|
|
259
291
|
const own = fileURLToPath(
|
|
260
292
|
import.meta.resolve('@astryxdesign/core/theme'),
|
|
@@ -301,9 +333,10 @@ const _projectCores = new Map();
|
|
|
301
333
|
* @param {string} cwd
|
|
302
334
|
*/
|
|
303
335
|
async function selectCore(cwd) {
|
|
336
|
+
const cliCore = await loadCliCore();
|
|
304
337
|
if (!_projectCores.has(cwd))
|
|
305
|
-
_projectCores.set(cwd, await loadProjectCore(cwd));
|
|
306
|
-
setCore(_projectCores.get(cwd) ??
|
|
338
|
+
_projectCores.set(cwd, await loadProjectCore(cwd, cliCore));
|
|
339
|
+
setCore(_projectCores.get(cwd) ?? cliCore);
|
|
307
340
|
}
|
|
308
341
|
|
|
309
342
|
/**
|
|
@@ -1694,6 +1727,8 @@ export async function validateThemePrivateInputs(
|
|
|
1694
1727
|
);
|
|
1695
1728
|
}
|
|
1696
1729
|
|
|
1730
|
+
// The CLI's own Core, unless a build in this process already chose one.
|
|
1731
|
+
await loadCliCore();
|
|
1697
1732
|
const interception = interceptCore(_coreThemeModule, _coreRootModule);
|
|
1698
1733
|
let theme;
|
|
1699
1734
|
try {
|
|
@@ -185,16 +185,6 @@ const overrides = stylex.create({
|
|
|
185
185
|
'Next.js + Tailwind bridge',
|
|
186
186
|
'[apps/example-nextjs-tailwind](https://github.com/facebook/astryx/tree/main/apps/example-nextjs-tailwind)',
|
|
187
187
|
],
|
|
188
|
-
[
|
|
189
|
-
'Next.js Source',
|
|
190
|
-
'Next.js importing from source',
|
|
191
|
-
'[apps/example-nextjs-source](https://github.com/facebook/astryx/tree/main/apps/example-nextjs-source)',
|
|
192
|
-
],
|
|
193
|
-
[
|
|
194
|
-
'Vite',
|
|
195
|
-
'Vite',
|
|
196
|
-
'[apps/example-vite](https://github.com/facebook/astryx/tree/main/apps/example-vite)',
|
|
197
|
-
],
|
|
198
188
|
[
|
|
199
189
|
'Vite + Tailwind',
|
|
200
190
|
'Vite + Tailwind bridge',
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* @file `astryx docs styling-overview`
|
|
5
5
|
*
|
|
6
6
|
* One question: "How do I set up CSS?"
|
|
7
|
-
* Three paths, each with its exact imports.
|
|
7
|
+
* Three paths, each with its exact imports. Every path loads the prebuilt
|
|
8
|
+
* stylesheets (spec:AST-080 FR5); none compiles Astryx package source.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
/** @type {import('@astryxdesign/cli/authoring').ReferenceDoc} */
|
|
@@ -14,7 +15,7 @@ export const docs = {
|
|
|
14
15
|
title: 'Styling overview',
|
|
15
16
|
category: 'guide',
|
|
16
17
|
description:
|
|
17
|
-
'How to set up CSS — exact imports for three paths: plain, Tailwind, and StyleX
|
|
18
|
+
'How to set up CSS — exact imports for three paths: plain, Tailwind, and StyleX for your own code — plus the stylesheet each Astryx package ships.',
|
|
18
19
|
keywords: [
|
|
19
20
|
'styling',
|
|
20
21
|
'CSS',
|
|
@@ -46,15 +47,15 @@ export const docs = {
|
|
|
46
47
|
"None beyond Tailwind's own.",
|
|
47
48
|
],
|
|
48
49
|
[
|
|
49
|
-
'StyleX
|
|
50
|
-
'
|
|
51
|
-
'Yes \u2014 a StyleX
|
|
50
|
+
'StyleX for your own code',
|
|
51
|
+
'Your own `stylex.create()` styles and swizzled (ejected) components. Astryx itself stays prebuilt.',
|
|
52
|
+
'Yes \u2014 a StyleX plugin for your own code.',
|
|
52
53
|
],
|
|
53
54
|
],
|
|
54
55
|
},
|
|
55
56
|
{
|
|
56
57
|
type: 'prose',
|
|
57
|
-
text: 'All three resolve to the same design tokens and support theming and dark mode. You can mix them in one app.',
|
|
58
|
+
text: 'All three load the same prebuilt Astryx stylesheets, resolve to the same design tokens, and support theming and dark mode. None of them compiles Astryx package source. You can mix them in one app.',
|
|
58
59
|
},
|
|
59
60
|
],
|
|
60
61
|
},
|
|
@@ -117,12 +118,12 @@ export const docs = {
|
|
|
117
118
|
],
|
|
118
119
|
},
|
|
119
120
|
{
|
|
120
|
-
id: 'stylex-
|
|
121
|
-
title: 'StyleX
|
|
121
|
+
id: 'stylex-own-code',
|
|
122
|
+
title: 'StyleX for your own code',
|
|
122
123
|
content: [
|
|
123
124
|
{
|
|
124
125
|
type: 'prose',
|
|
125
|
-
text: 'Only needed when you swizzle a component (`astryx swizzle <Component>`)
|
|
126
|
+
text: 'Only needed when you write your own StyleX (`stylex.create()`) or swizzle a component (`astryx swizzle <Component>`), which copies its StyleX source into your app. The plain path already supports `xstyle` overrides \u2014 this path is for your own StyleX code. Some built-in page templates also use StyleX for layout; if you scaffold one and see "Unexpected stylex.create call at runtime", add the compiler plugin for your bundler below.',
|
|
126
127
|
},
|
|
127
128
|
{
|
|
128
129
|
type: 'code',
|
|
@@ -134,14 +135,13 @@ export const docs = {
|
|
|
134
135
|
type: 'code',
|
|
135
136
|
lang: 'css',
|
|
136
137
|
label: 'globals.css',
|
|
137
|
-
code: `@
|
|
138
|
-
|
|
139
|
-
@import "@astryxdesign/core/reset.css";
|
|
138
|
+
code: `@import "@astryxdesign/core/reset.css";
|
|
139
|
+
@import "@astryxdesign/core/astryx.css";
|
|
140
140
|
@import "@astryxdesign/theme-neutral/theme.css";`,
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'prose',
|
|
144
|
-
text: '
|
|
144
|
+
text: 'Keep `astryx.css`: Astryx component styles always come from the prebuilt stylesheets. Point the StyleX compiler at your own source only, never at `node_modules/@astryxdesign`. Add the plugin for your bundler:',
|
|
145
145
|
},
|
|
146
146
|
{
|
|
147
147
|
type: 'table',
|
|
@@ -149,7 +149,10 @@ export const docs = {
|
|
|
149
149
|
rows: [
|
|
150
150
|
['Webpack', '@stylexjs/webpack-plugin'],
|
|
151
151
|
['Vite / Rollup', '@stylexjs/rollup-plugin'],
|
|
152
|
-
[
|
|
152
|
+
[
|
|
153
|
+
'Babel (any)',
|
|
154
|
+
'@stylexjs/babel-plugin + @stylexjs/postcss-plugin',
|
|
155
|
+
],
|
|
153
156
|
['Next.js (keep next/font)', '@stylexswc/nextjs-plugin'],
|
|
154
157
|
],
|
|
155
158
|
},
|
|
@@ -159,6 +162,37 @@ export const docs = {
|
|
|
159
162
|
},
|
|
160
163
|
],
|
|
161
164
|
},
|
|
165
|
+
{
|
|
166
|
+
id: 'other-packages',
|
|
167
|
+
title: 'Other Astryx packages',
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
type: 'prose',
|
|
171
|
+
text: 'Every Astryx package whose components carry styles ships its own prebuilt stylesheet. Import it after `astryx.css` for each package you use. All of them place their rules in the same `astryx-base` layer, so your theme overrides win over every package.',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
type: 'code',
|
|
175
|
+
lang: 'css',
|
|
176
|
+
label: 'globals.css',
|
|
177
|
+
code: `@import "@astryxdesign/core/reset.css";
|
|
178
|
+
@import "@astryxdesign/core/astryx.css";
|
|
179
|
+
@import "@astryxdesign/richtext/richtext.css"; /* Rich Text */
|
|
180
|
+
@import "@astryxdesign/lab/lab.css"; /* Lab */
|
|
181
|
+
@import "@astryxdesign/charts/charts.css"; /* Charts */
|
|
182
|
+
@import "@astryxdesign/theme-neutral/theme.css";`,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
type: 'table',
|
|
186
|
+
headers: ['Package', 'Stylesheet'],
|
|
187
|
+
rows: [
|
|
188
|
+
['@astryxdesign/core', '@astryxdesign/core/astryx.css'],
|
|
189
|
+
['@astryxdesign/richtext', '@astryxdesign/richtext/richtext.css'],
|
|
190
|
+
['@astryxdesign/lab', '@astryxdesign/lab/lab.css'],
|
|
191
|
+
['@astryxdesign/charts', '@astryxdesign/charts/charts.css'],
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
},
|
|
162
196
|
{
|
|
163
197
|
id: 'what-each-file-does',
|
|
164
198
|
title: 'What each import does',
|
|
@@ -175,7 +209,12 @@ export const docs = {
|
|
|
175
209
|
[
|
|
176
210
|
'astryx.css',
|
|
177
211
|
'astryx-base',
|
|
178
|
-
'
|
|
212
|
+
'Prebuilt component styles. Required on every path.',
|
|
213
|
+
],
|
|
214
|
+
[
|
|
215
|
+
'richtext.css, lab.css, charts.css',
|
|
216
|
+
'astryx-base',
|
|
217
|
+
'Prebuilt styles for each other Astryx package you use. Load after astryx.css.',
|
|
179
218
|
],
|
|
180
219
|
[
|
|
181
220
|
'theme.css',
|