@astryxdesign/cli 0.7.12-canary.f543ec7 → 0.7.12
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 +107 -0
- package/api/build/blocks/blocks.d.mts +41 -0
- package/api/build/blocks/blocks.mjs +118 -68
- package/api/component/__fixtures__/resolutions/modules.txt +3 -3
- package/api/docs/_adapter.d.mts +22 -0
- package/api/docs/_adapter.mjs +81 -3
- package/api/docs/docs.doc.mjs +5 -5
- package/api/docs/docs.type.d.mts +21 -0
- package/api/docs/docs.type.mjs +11 -0
- package/api/docs/list/list.d.mts +5 -1
- package/api/docs/list/list.mjs +72 -6
- package/api/docs/node/node.mjs +2 -40
- package/api/doctor/doctor.d.mts +4 -4
- package/api/doctor/doctor.mjs +248 -78
- package/api/icon/_authoring.mjs +8 -1
- package/api/icon/add/add.mjs +45 -5
- package/api/icon/icon.type.d.mts +24 -0
- package/api/icon/icon.type.mjs +6 -0
- package/api/icon/iconAdd.doc.mjs +1 -1
- package/api/icon/map/map.mjs +11 -4
- package/api/index.d.mts +1 -0
- package/api/index.mjs +1 -0
- package/api/integration/add-helpers.d.mts +5 -1
- package/api/integration/add-helpers.mjs +1 -1
- package/api/render/_adapter.mjs +9 -0
- package/api/render/render.doc.mjs +3 -3
- package/api/render/render.type.d.mts +1 -1
- package/api/render/render.type.mjs +1 -1
- package/api/search/ORDERING.md +3 -0
- package/api/search/_adapter.d.mts +32 -3
- package/api/search/_adapter.mjs +50 -22
- package/api/search/_fit-order.d.mts +47 -0
- package/api/search/_fit-order.mjs +257 -0
- package/api/search/_main-searcher.mjs +15 -5
- package/api/search/exportSearchIndex.doc.d.mts +10 -0
- package/api/search/exportSearchIndex.doc.mjs +58 -0
- package/api/search/rankSearchIndex.doc.d.mts +10 -0
- package/api/search/rankSearchIndex.doc.mjs +79 -0
- package/api/search/search-index.d.mts +44 -0
- package/api/search/search-index.mjs +270 -0
- package/api/search/search.type.d.mts +28 -0
- package/api/search/search.type.mjs +17 -0
- package/api/swizzle/_moduleGraph.d.mts +7 -0
- package/api/swizzle/_moduleGraph.mjs +191 -83
- package/api/swizzle/copy/copy.mjs +28 -6
- package/api/template/skeleton/skeleton.mjs +1 -1
- package/api/template/template.mjs +1 -0
- package/assets/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +1 -1
- package/assets/codemods/transforms/v0.1.0/index.mjs +1 -1
- package/assets/discover/official-packages.json +16 -0
- package/assets/docs/styling-overview.doc.mjs +3 -3
- package/assets/docs/tree/icon-sets.doc.mjs +4 -4
- package/assets/docs/tree/render-pages.doc.mjs +3 -3
- package/assets/docs/tree/tailwind.doc.mjs +41 -1
- package/assets/docs/tree/type-scale.doc.mjs +19 -2
- package/assets/templates/blocks/components/Hooks/useInputStatusIconHookUsage.tsx +1 -0
- package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.tsx +56 -0
- package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.doc.mjs +2 -2
- package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.tsx +66 -10
- package/assets/templates/blocks/components/Link/useLinkifyHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Link/useLinkifyHookUsage.tsx +39 -0
- package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.tsx +83 -0
- package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.tsx +41 -0
- package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.tsx +97 -0
- package/assets/templates/pages/table-filter/page.tsx +15 -23
- package/assets/templates/pages/table-grouped/page.tsx +30 -30
- package/assets/templates/pages/table-inbox/page.tsx +3 -6
- package/assets/templates/themes/butter/butterTheme.ts +1 -1
- package/assets/templates/themes/gothic/gothicTheme.ts +16 -0
- package/assets/templates/themes/matcha/matchaTheme.ts +1 -1
- package/assets/templates/themes/neutral/neutralTheme.ts +6 -0
- package/assets/templates/themes/y2k/y2kPaletteRefs.generated.ts +1 -1
- package/assets/templates/themes/y2k/y2kTheme.ts +1 -1
- package/clients/cli/commands/docs.doc.mjs +5 -1
- package/clients/cli/commands/docs.mjs +30 -2
- package/clients/cli/commands/experimental/icon.mjs +25 -4
- package/clients/cli/commands/experimental-render.doc.mjs +1 -1
- package/clients/cli/commands/template.mjs +204 -181
- package/clients/cli/lib/resolve-theme.mjs +16 -7
- package/foundation/discovery/component-discovery.mjs +1 -1
- package/foundation/discovery/hook-discovery.mjs +31 -8
- package/foundation/discovery/icon-set-writer.d.mts +21 -5
- package/foundation/discovery/icon-set-writer.mjs +114 -13
- package/foundation/discovery/template-adapter.mjs +2 -2
- package/foundation/doc-compiler/import.d.mts +5 -1
- package/foundation/doc-compiler/import.mjs +40 -6
- package/foundation/fs/import-url.d.mts +20 -0
- package/foundation/fs/import-url.mjs +22 -0
- package/foundation/fs/module-loader.d.mts +24 -3
- package/foundation/fs/module-loader.mjs +69 -47
- package/foundation/render/charts.d.mts +59 -0
- package/foundation/render/charts.mjs +216 -0
- package/foundation/render/embedded-imports.d.mts +7 -7
- package/foundation/render/embedded-imports.mjs +63 -54
- package/foundation/render/page-host.d.mts +4 -0
- package/foundation/render/page-host.mjs +45 -0
- package/foundation/render/page-worker.mjs +350 -57
- package/foundation/render/render-page.d.mts +11 -6
- package/foundation/render/render-page.mjs +164 -54
- package/foundation/render/ts-paths.d.mts +63 -0
- package/foundation/render/ts-paths.mjs +138 -0
- package/foundation/response/error-codes.doc.mjs +3 -3
- package/foundation/response/error-codes.mjs +3 -2
- package/foundation/text/levenshtein.d.mts +6 -1
- package/foundation/text/levenshtein.mjs +25 -9
- package/foundation/text/suggest-order.d.mts +8 -1
- package/foundation/text/suggest-order.mjs +72 -6
- package/foundation/xle/registry-core.mjs +1 -1
- package/package.json +7 -6
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
exportValueKind,
|
|
49
49
|
followedExportNames,
|
|
50
50
|
moduleExportOrigin,
|
|
51
|
+
staticExportBindings,
|
|
51
52
|
} from './module-exports.mjs';
|
|
52
53
|
|
|
53
54
|
/** @typedef {import('./icon-set-discovery.mjs').DiscoveredIconSet} DiscoveredIconSet */
|
|
@@ -100,10 +101,19 @@ export function iconIdOf(name) {
|
|
|
100
101
|
|
|
101
102
|
/**
|
|
102
103
|
* The name an icon set exports an icon as: PascalCase ending in `Icon`
|
|
103
|
-
* (`Trash2` → `Trash2Icon`; `TrashIcon` stays).
|
|
104
|
+
* (`Trash2` → `Trash2Icon`; `TrashIcon` stays). A JavaScript name cannot
|
|
105
|
+
* start with a digit, so a name that starts with one is refused.
|
|
106
|
+
* {@link iconNaming} keeps a library prefix to prevent this (`fa0` →
|
|
107
|
+
* `Fa0Icon`).
|
|
104
108
|
* @param {string} name
|
|
105
109
|
*/
|
|
106
110
|
export function iconExportName(name) {
|
|
111
|
+
if (/^[0-9]/u.test(name)) {
|
|
112
|
+
throw new IconSetAuthoringError(
|
|
113
|
+
`The icon id "${iconIdOf(name)}" starts with a digit, and an export name cannot. Pass --id with an id that starts with a letter.`,
|
|
114
|
+
'ERR_INVALID_ARGUMENT',
|
|
115
|
+
);
|
|
116
|
+
}
|
|
107
117
|
const pascal = name[0].toUpperCase() + name.slice(1);
|
|
108
118
|
return pascal.endsWith('Icon') ? pascal : `${pascal}Icon`;
|
|
109
119
|
}
|
|
@@ -146,7 +156,11 @@ function packageDirOf(cwd) {
|
|
|
146
156
|
* `selector` names, else the only set.
|
|
147
157
|
* @param {string} cwd
|
|
148
158
|
* @param {string} [selector] a set name
|
|
149
|
-
*
|
|
159
|
+
* The set's `module` names the package as package.json names it now;
|
|
160
|
+
* `moduleRenamed` says the data file still names an old name, so a command
|
|
161
|
+
* writes the data file even when nothing else changes; `moduleUpdate` gives
|
|
162
|
+
* the old and the new name for the receipt, else null.
|
|
163
|
+
* @returns {Promise<{packageDir: string, pkg: Record<string, any>, root: string, set: DiscoveredIconSet, sets: DiscoveredIconSet[], moduleRenamed: boolean, moduleUpdate: {previous: string, module: string} | null}>}
|
|
150
164
|
*/
|
|
151
165
|
export async function findLocalIconSet(cwd, selector) {
|
|
152
166
|
const packageDir = packageDirOf(cwd);
|
|
@@ -190,12 +204,20 @@ export async function findLocalIconSet(cwd, selector) {
|
|
|
190
204
|
? found.sets.filter(set => set.name === selector)
|
|
191
205
|
: found.sets;
|
|
192
206
|
if (named.length === 1) {
|
|
207
|
+
const set = {...named[0], packageDir};
|
|
208
|
+
const module = moduleInPackage(set.data.module, pkg.name);
|
|
209
|
+
const moduleUpdate =
|
|
210
|
+
module !== set.data.module ? {previous: set.data.module, module} : null;
|
|
211
|
+
const moduleRenamed = moduleUpdate != null;
|
|
212
|
+
if (moduleRenamed) set.data = {...set.data, module};
|
|
193
213
|
return {
|
|
194
214
|
packageDir,
|
|
195
215
|
pkg,
|
|
196
216
|
root,
|
|
197
|
-
set
|
|
217
|
+
set,
|
|
198
218
|
sets: found.sets,
|
|
219
|
+
moduleRenamed,
|
|
220
|
+
moduleUpdate,
|
|
199
221
|
};
|
|
200
222
|
}
|
|
201
223
|
const broken = selector
|
|
@@ -229,6 +251,22 @@ export async function findLocalIconSet(cwd, selector) {
|
|
|
229
251
|
);
|
|
230
252
|
}
|
|
231
253
|
|
|
254
|
+
/**
|
|
255
|
+
* The data file's `module` in the package as it is named now. The data file
|
|
256
|
+
* belongs to the CLI, so after a package rename the next write gives the new
|
|
257
|
+
* name (`@acme/old/solid` → `@acme/new/solid`). A ./ path, or a package
|
|
258
|
+
* with no name, is left as it is.
|
|
259
|
+
* @param {string} module
|
|
260
|
+
* @param {unknown} name the package.json `name`
|
|
261
|
+
*/
|
|
262
|
+
function moduleInPackage(module, name) {
|
|
263
|
+
if (typeof name !== 'string' || name === '' || module.startsWith('.')) {
|
|
264
|
+
return module;
|
|
265
|
+
}
|
|
266
|
+
const owner = iconModulePackage(module);
|
|
267
|
+
return owner === name ? module : `${name}${module.slice(owner.length)}`;
|
|
268
|
+
}
|
|
269
|
+
|
|
232
270
|
/** The data file's fields, in the order the file is written. */
|
|
233
271
|
const DATA_KEYS = [
|
|
234
272
|
'schemaVersion',
|
|
@@ -599,7 +637,7 @@ const SCREAMING_CASE_RE = /^[A-Z][A-Z0-9_]+$/u;
|
|
|
599
637
|
* set declares an adapter, also data exports, camelCase included: objects,
|
|
600
638
|
* and plain strings only in a library whose candidates are all strings.
|
|
601
639
|
* Left out: `default`, `Icon`, `…Provider` and `…Context` components, SCREAMING_CASE constants, number and boolean
|
|
602
|
-
* exports, and `exclude`.
|
|
640
|
+
* exports, packs of other exports ({@link isIconPack}), and `exclude`.
|
|
603
641
|
* @param {string} file
|
|
604
642
|
* @param {{adapter?: boolean, exclude?: string[]}} [options] `exclude`: export
|
|
605
643
|
* names that are not icons
|
|
@@ -609,7 +647,9 @@ function iconCandidates(file, {adapter = false, exclude = []} = {}) {
|
|
|
609
647
|
const skipped = new Set(exclude);
|
|
610
648
|
/** @type {Array<{name: string, adapt: boolean, scalar: string | null}>} */
|
|
611
649
|
const out = [];
|
|
612
|
-
|
|
650
|
+
const names = followedExportNames(file);
|
|
651
|
+
const exported = new Set(names);
|
|
652
|
+
for (const name of names) {
|
|
613
653
|
if (name === 'default' || name === 'Icon' || skipped.has(name)) continue;
|
|
614
654
|
// A context provider or context (`LucideProvider`) is a component that
|
|
615
655
|
// draws nothing.
|
|
@@ -622,7 +662,8 @@ function iconCandidates(file, {adapter = false, exclude = []} = {}) {
|
|
|
622
662
|
} else if (
|
|
623
663
|
adapter &&
|
|
624
664
|
kind === 'data' &&
|
|
625
|
-
/^[A-Za-z][A-Za-z0-9]*$/u.test(name)
|
|
665
|
+
/^[A-Za-z][A-Za-z0-9]*$/u.test(name) &&
|
|
666
|
+
!isIconPack(file, name, exported)
|
|
626
667
|
) {
|
|
627
668
|
const scalar = exportScalarType(file, name);
|
|
628
669
|
// A number or a boolean draws nothing; a string may be SVG markup.
|
|
@@ -637,6 +678,20 @@ function iconCandidates(file, {adapter = false, exclude = []} = {}) {
|
|
|
637
678
|
: out;
|
|
638
679
|
}
|
|
639
680
|
|
|
681
|
+
/**
|
|
682
|
+
* Whether a data export is a pack: an object whose keys are all other
|
|
683
|
+
* exports of the module, such as `fas` in a library that also exports
|
|
684
|
+
* `faXmark` and `faHouse`. A pack holds icons but draws nothing.
|
|
685
|
+
* @param {string} file
|
|
686
|
+
* @param {string} name
|
|
687
|
+
* @param {Set<string>} exported every export name of the module
|
|
688
|
+
*/
|
|
689
|
+
function isIconPack(file, name, exported) {
|
|
690
|
+
const bindings = staticExportBindings(file, name);
|
|
691
|
+
if (bindings == null || bindings.size < 2) return false;
|
|
692
|
+
return [...bindings.keys()].every(key => key !== name && exported.has(key));
|
|
693
|
+
}
|
|
694
|
+
|
|
640
695
|
/**
|
|
641
696
|
* The exports of a library that are not icons and that wrapping it leaves
|
|
642
697
|
* out (`Icon`, `LucideProvider`, `createLucideIcon`, constants), so the
|
|
@@ -690,7 +745,9 @@ export function libraryPrefix(names) {
|
|
|
690
745
|
|
|
691
746
|
/**
|
|
692
747
|
* An export's id and the name the set exports it as, with the library prefix
|
|
693
|
-
* left out: `featherEye` with prefix `feather` is id `eye`, `EyeIcon`.
|
|
748
|
+
* left out: `featherEye` with prefix `feather` is id `eye`, `EyeIcon`. A
|
|
749
|
+
* name that would start with a digit keeps the prefix (`fa500px` is
|
|
750
|
+
* `fa-500px`, `Fa500pxIcon`).
|
|
694
751
|
* @param {string} name
|
|
695
752
|
* @param {string[]} prefix
|
|
696
753
|
* @returns {{id: string, name: string}}
|
|
@@ -698,9 +755,12 @@ export function libraryPrefix(names) {
|
|
|
698
755
|
export function iconNaming(name, prefix) {
|
|
699
756
|
const all = iconIdOf(name).split('-');
|
|
700
757
|
// Only a name that starts with the prefix, and has more after it, loses it.
|
|
758
|
+
// A name that would then start with a digit keeps the prefix: `fa0` is
|
|
759
|
+
// `fa-0`, `Fa0Icon`.
|
|
701
760
|
const strip =
|
|
702
761
|
all.length > prefix.length &&
|
|
703
|
-
prefix.every((word, index) => all[index] === word)
|
|
762
|
+
prefix.every((word, index) => all[index] === word) &&
|
|
763
|
+
!/^[0-9]/u.test(all[prefix.length]);
|
|
704
764
|
if (!strip) prefix = [];
|
|
705
765
|
const words = all.slice(prefix.length);
|
|
706
766
|
const id = words.join('-');
|
|
@@ -1423,10 +1483,39 @@ function terminated(statement) {
|
|
|
1423
1483
|
: `${statement};`;
|
|
1424
1484
|
}
|
|
1425
1485
|
|
|
1486
|
+
/** Syntax-tree fields that only say how a file is laid out. */
|
|
1487
|
+
const LAYOUT_KEYS = new Set([
|
|
1488
|
+
'start',
|
|
1489
|
+
'end',
|
|
1490
|
+
'loc',
|
|
1491
|
+
'range',
|
|
1492
|
+
'extra',
|
|
1493
|
+
'leadingComments',
|
|
1494
|
+
'trailingComments',
|
|
1495
|
+
'innerComments',
|
|
1496
|
+
]);
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* The top-level statements of a module as syntax trees without positions,
|
|
1500
|
+
* comments, or raw text, one string each. A formatter changes line breaks,
|
|
1501
|
+
* quotes, semicolons, trailing commas, and the wrapping of long type
|
|
1502
|
+
* arguments; none of these change the tree.
|
|
1503
|
+
* @param {string} text
|
|
1504
|
+
* @returns {string[]}
|
|
1505
|
+
*/
|
|
1506
|
+
function statementTrees(text) {
|
|
1507
|
+
const ast = parse(text, {sourceType: 'module', plugins: ['typescript']});
|
|
1508
|
+
return ast.program.body.map(node =>
|
|
1509
|
+
JSON.stringify(node, (key, value) =>
|
|
1510
|
+
LAYOUT_KEYS.has(key) ? undefined : value,
|
|
1511
|
+
),
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1426
1515
|
/**
|
|
1427
1516
|
* Whether a generated declaration file declares what its generated module
|
|
1428
1517
|
* gives, statement by statement however a formatter left either file
|
|
1429
|
-
* ({@link
|
|
1518
|
+
* ({@link statementTrees}); the header comment is ignored.
|
|
1430
1519
|
* @param {string} actual the declaration file as it is
|
|
1431
1520
|
* @param {string} expected {@link generatedTypesText} for the module
|
|
1432
1521
|
* @param {string} label the declaration file's path, for a parse error
|
|
@@ -1434,11 +1523,19 @@ function terminated(statement) {
|
|
|
1434
1523
|
export function generatedTypesMatch(actual, expected, label) {
|
|
1435
1524
|
let have;
|
|
1436
1525
|
try {
|
|
1437
|
-
have =
|
|
1526
|
+
have = statementTrees(actual);
|
|
1438
1527
|
} catch {
|
|
1439
1528
|
return false;
|
|
1440
1529
|
}
|
|
1441
|
-
|
|
1530
|
+
let want;
|
|
1531
|
+
try {
|
|
1532
|
+
want = statementTrees(expected);
|
|
1533
|
+
} catch (error) {
|
|
1534
|
+
throw new IconSetAuthoringError(
|
|
1535
|
+
`The declarations written for ${label} do not parse (${error instanceof Error ? error.message : String(error)}).`,
|
|
1536
|
+
'ERR_INVALID_ARGUMENT',
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1442
1539
|
return have.length === want.length && have.every((s, i) => s === want[i]);
|
|
1443
1540
|
}
|
|
1444
1541
|
|
|
@@ -1817,10 +1914,11 @@ export function generatedNamesOf(packageDir, generated, from, source) {
|
|
|
1817
1914
|
* differently; and a refresh changes only the icons its module supplies.
|
|
1818
1915
|
* @param {string} packageDir
|
|
1819
1916
|
* @param {string} generated
|
|
1820
|
-
* @returns {Map<string, {module: string, source: string}>}
|
|
1917
|
+
* @returns {Map<string, {module: string, source: string, adapt: boolean}>}
|
|
1918
|
+
* `adapt`: the set's adapter turns the export into a component
|
|
1821
1919
|
*/
|
|
1822
1920
|
export function generatedOrigins(packageDir, generated) {
|
|
1823
|
-
/** @type {Map<string, {module: string, source: string}>} */
|
|
1921
|
+
/** @type {Map<string, {module: string, source: string, adapt: boolean}>} */
|
|
1824
1922
|
const origins = new Map();
|
|
1825
1923
|
const file = path.join(packageDir, generated);
|
|
1826
1924
|
if (!fs.existsSync(file)) return origins;
|
|
@@ -1847,6 +1945,7 @@ export function generatedOrigins(packageDir, generated) {
|
|
|
1847
1945
|
origins.set(reexport[2] ?? reexport[1], {
|
|
1848
1946
|
module: fromRoot(reexport[3]),
|
|
1849
1947
|
source: reexport[1],
|
|
1948
|
+
adapt: false,
|
|
1850
1949
|
});
|
|
1851
1950
|
continue;
|
|
1852
1951
|
}
|
|
@@ -1857,6 +1956,7 @@ export function generatedOrigins(packageDir, generated) {
|
|
|
1857
1956
|
origins.set(adapted[2], {
|
|
1858
1957
|
module: fromRoot(adapted[3]),
|
|
1859
1958
|
source: adapted[1],
|
|
1959
|
+
adapt: true,
|
|
1860
1960
|
});
|
|
1861
1961
|
continue;
|
|
1862
1962
|
}
|
|
@@ -1867,6 +1967,7 @@ export function generatedOrigins(packageDir, generated) {
|
|
|
1867
1967
|
origins.set(adaptedDefault[1], {
|
|
1868
1968
|
module: fromRoot(adaptedDefault[2]),
|
|
1869
1969
|
source: 'default',
|
|
1970
|
+
adapt: true,
|
|
1870
1971
|
});
|
|
1871
1972
|
}
|
|
1872
1973
|
}
|
|
@@ -2100,8 +2100,8 @@ export function extractComponents(pagePath) {
|
|
|
2100
2100
|
*/
|
|
2101
2101
|
export function componentsInSource(src) {
|
|
2102
2102
|
// Match JSX opening tags, e.g. `<Section` or the legacy `<XDSSection`.
|
|
2103
|
-
// Templates author bare component names post un-prefix migration
|
|
2104
|
-
//
|
|
2103
|
+
// Templates author bare component names post un-prefix migration,
|
|
2104
|
+
// so the `XDS` prefix is optional. Anchoring on the `<`
|
|
2105
2105
|
// JSX-tag boundary keeps this precise (avoids matching imports/comments/
|
|
2106
2106
|
// identifiers) while remaining prefix-agnostic.
|
|
2107
2107
|
//
|
|
@@ -17,7 +17,11 @@ export function importDocModule(file: string): Promise<any>;
|
|
|
17
17
|
export function importTemplateModule(file: string): Promise<any>;
|
|
18
18
|
/**
|
|
19
19
|
* Import a module natively, by file URL: how the docs API, search, and the
|
|
20
|
-
* unchecked component and hook readers have always imported docs.
|
|
20
|
+
* unchecked component and hook readers have always imported docs. A `.ts`
|
|
21
|
+
* file goes through jiti, as {@link importDocModule} imports it: Node cannot
|
|
22
|
+
* strip types from a file under node_modules, and below 22.18 it does not
|
|
23
|
+
* strip types at all, so a plain import of an installed or linked
|
|
24
|
+
* integration's `.doc.ts` fails.
|
|
21
25
|
* @param {string} file
|
|
22
26
|
* @returns {Promise<any>}
|
|
23
27
|
*/
|
|
@@ -24,7 +24,12 @@
|
|
|
24
24
|
|
|
25
25
|
import {pathToFileURL} from 'node:url';
|
|
26
26
|
import {createJiti} from 'jiti';
|
|
27
|
-
import {
|
|
27
|
+
import {importUrl} from '../fs/import-url.mjs';
|
|
28
|
+
import {
|
|
29
|
+
importUserModule,
|
|
30
|
+
readFreshInTurn,
|
|
31
|
+
readInTurn,
|
|
32
|
+
} from '../fs/module-loader.mjs';
|
|
28
33
|
import {fileStamp, recordContentRead} from '../fs/read-record.mjs';
|
|
29
34
|
|
|
30
35
|
/** @type {ReturnType<typeof createJiti> | undefined} */
|
|
@@ -78,6 +83,28 @@ function freshUrl(file) {
|
|
|
78
83
|
return url.href;
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Import `file` from `url`, in its turn with every other read of the file in
|
|
88
|
+
* this process (`readInTurn`, ../fs/module-loader.mjs).
|
|
89
|
+
* @param {string} file
|
|
90
|
+
* @param {string} url
|
|
91
|
+
* @returns {Promise<any>}
|
|
92
|
+
*/
|
|
93
|
+
function importInTurn(file, url) {
|
|
94
|
+
return readInTurn(file, () => importUrl(url));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Import `file` again, for a change, as a new module. Node chooses its format
|
|
99
|
+
* as for the first import. The read takes its turn without the file's
|
|
100
|
+
* CommonJS cache entry (`readFreshInTurn`, ../fs/module-loader.mjs).
|
|
101
|
+
* @param {string} file
|
|
102
|
+
* @returns {Promise<any>}
|
|
103
|
+
*/
|
|
104
|
+
function importFresh(file) {
|
|
105
|
+
return readFreshInTurn(file, () => importUrl(freshUrl(file)));
|
|
106
|
+
}
|
|
107
|
+
|
|
81
108
|
/**
|
|
82
109
|
* Import a component, hook, topic, or self-doc module: `.ts` through jiti,
|
|
83
110
|
* anything else natively.
|
|
@@ -105,28 +132,35 @@ export function importTemplateModule(file) {
|
|
|
105
132
|
jsxJiti ??= createJiti(import.meta.url, {jsx: true});
|
|
106
133
|
return jsxJiti.import(first);
|
|
107
134
|
}
|
|
108
|
-
return
|
|
135
|
+
return importInTurn(first, `file://${first}`);
|
|
109
136
|
},
|
|
110
137
|
changed =>
|
|
111
138
|
changed.endsWith('.ts')
|
|
112
139
|
? createJiti(import.meta.url, {jsx: true, moduleCache: false}).import(
|
|
113
140
|
changed,
|
|
114
141
|
)
|
|
115
|
-
:
|
|
142
|
+
: importFresh(changed),
|
|
116
143
|
);
|
|
117
144
|
}
|
|
118
145
|
|
|
119
146
|
/**
|
|
120
147
|
* Import a module natively, by file URL: how the docs API, search, and the
|
|
121
|
-
* unchecked component and hook readers have always imported docs.
|
|
148
|
+
* unchecked component and hook readers have always imported docs. A `.ts`
|
|
149
|
+
* file goes through jiti, as {@link importDocModule} imports it: Node cannot
|
|
150
|
+
* strip types from a file under node_modules, and below 22.18 it does not
|
|
151
|
+
* strip types at all, so a plain import of an installed or linked
|
|
152
|
+
* integration's `.doc.ts` fails.
|
|
122
153
|
* @param {string} file
|
|
123
154
|
* @returns {Promise<any>}
|
|
124
155
|
*/
|
|
125
156
|
export function importNativeModule(file) {
|
|
157
|
+
if (file.endsWith('.ts')) {
|
|
158
|
+
return importDocModule(file);
|
|
159
|
+
}
|
|
126
160
|
return importCurrent(
|
|
127
161
|
file,
|
|
128
162
|
'native',
|
|
129
|
-
first =>
|
|
130
|
-
changed =>
|
|
163
|
+
first => importInTurn(first, pathToFileURL(first).href),
|
|
164
|
+
changed => importFresh(changed),
|
|
131
165
|
);
|
|
132
166
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file The dynamic import of an authored module by its file URL.
|
|
6
|
+
*
|
|
7
|
+
* @input An absolute `file:` URL, with any query its caller adds to name a
|
|
8
|
+
* fresh module.
|
|
9
|
+
* @output The module's namespace object, as a plain `import()` gives it.
|
|
10
|
+
* @position Called by the doc importers (../doc-compiler/import.mjs). The
|
|
11
|
+
* node test project loads this file natively, as the CLI does
|
|
12
|
+
* (vitest.config.ts). The doc files that a catalog command imports then load
|
|
13
|
+
* natively as well, without a request to the test runner for each of them.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Import the module at `url`.
|
|
17
|
+
* @param {string} url an absolute `file:` URL
|
|
18
|
+
* @returns {Promise<any>}
|
|
19
|
+
*/
|
|
20
|
+
export function importUrl(url: string): Promise<any>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file The dynamic import of an authored module by its file URL.
|
|
5
|
+
*
|
|
6
|
+
* @input An absolute `file:` URL, with any query its caller adds to name a
|
|
7
|
+
* fresh module.
|
|
8
|
+
* @output The module's namespace object, as a plain `import()` gives it.
|
|
9
|
+
* @position Called by the doc importers (../doc-compiler/import.mjs). The
|
|
10
|
+
* node test project loads this file natively, as the CLI does
|
|
11
|
+
* (vitest.config.ts). The doc files that a catalog command imports then load
|
|
12
|
+
* natively as well, without a request to the test runner for each of them.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Import the module at `url`.
|
|
17
|
+
* @param {string} url an absolute `file:` URL
|
|
18
|
+
* @returns {Promise<any>}
|
|
19
|
+
*/
|
|
20
|
+
export function importUrl(url) {
|
|
21
|
+
return import(url);
|
|
22
|
+
}
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* Import a user-authored module. `.ts` is loaded via jiti; `.mjs`/`.js` use
|
|
6
6
|
* native dynamic import for ordinary cached reads. A fresh `.ts` read uses a
|
|
7
|
-
* no-cache jiti instance.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* no-cache jiti instance. Any other fresh read drops the module from the
|
|
8
|
+
* require cache and imports a cache-busted file URL. Node then chooses its
|
|
9
|
+
* format as for the first read, so a fresh read gives what a first read gives.
|
|
10
10
|
* Normal loads retain module caching. `fresh` rereads a file that changed
|
|
11
11
|
* during this process: one a codemod rewrote, or an edited doc file (the doc
|
|
12
12
|
* importers in ../doc-compiler/import.mjs read each change once).
|
|
@@ -29,6 +29,27 @@ export function importUserModule(file: string, { fresh }?: {
|
|
|
29
29
|
* @returns {Promise<Record<string, unknown>>}
|
|
30
30
|
*/
|
|
31
31
|
export function importThemeArtifact(specifier: string, cwd: string): Promise<Record<string, unknown>>;
|
|
32
|
+
/**
|
|
33
|
+
* Run `read`, an import of `file`, once the imports of `file` started before
|
|
34
|
+
* it have ended. Imports of other files do not wait. A fresh read removes the
|
|
35
|
+
* file from Node's CommonJS cache. A CommonJS import still in progress needs
|
|
36
|
+
* that entry until it ends.
|
|
37
|
+
* @template T
|
|
38
|
+
* @param {string} file absolute path
|
|
39
|
+
* @param {() => Promise<T>} read
|
|
40
|
+
* @returns {Promise<T>}
|
|
41
|
+
*/
|
|
42
|
+
export function readInTurn<T>(file: string, read: () => Promise<T>): Promise<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Run `read`, a fresh import of `file`, in the file's turn (`readInTurn`),
|
|
45
|
+
* once the file is gone from Node's CommonJS cache. A new URL names a new ES
|
|
46
|
+
* module only, so a CommonJS module also needs its cache entry gone.
|
|
47
|
+
* @template T
|
|
48
|
+
* @param {string} file absolute path
|
|
49
|
+
* @param {() => Promise<T>} read
|
|
50
|
+
* @returns {Promise<T>}
|
|
51
|
+
*/
|
|
52
|
+
export function readFreshInTurn<T>(file: string, read: () => Promise<T>): Promise<T>;
|
|
32
53
|
/**
|
|
33
54
|
* Import a user-authored module, take its default export, and validate it
|
|
34
55
|
* through an authoring parser. This is the single load/validation boundary for
|
|
@@ -40,30 +40,6 @@ function getJiti() {
|
|
|
40
40
|
return jitiInstance;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/**
|
|
44
|
-
* Node treats `.js` as ESM only inside the nearest package scope whose
|
|
45
|
-
* package.json declares `"type": "module"`; no package.json defaults to
|
|
46
|
-
* CommonJS.
|
|
47
|
-
* @param {string} file
|
|
48
|
-
* @returns {boolean}
|
|
49
|
-
*/
|
|
50
|
-
function isCommonJsFile(file) {
|
|
51
|
-
let dir = path.dirname(file);
|
|
52
|
-
for (;;) {
|
|
53
|
-
const packageJson = path.join(dir, 'package.json');
|
|
54
|
-
if (fs.existsSync(packageJson)) {
|
|
55
|
-
try {
|
|
56
|
-
return JSON.parse(fs.readFileSync(packageJson, 'utf-8')).type !== 'module';
|
|
57
|
-
} catch {
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
const parent = path.dirname(dir);
|
|
62
|
-
if (parent === dir) return true;
|
|
63
|
-
dir = parent;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
43
|
let loadsInFlight = 0;
|
|
68
44
|
/** @type {typeof process.stdout.write | undefined} */
|
|
69
45
|
let realStdoutWrite;
|
|
@@ -104,9 +80,9 @@ async function withStdoutOnStderr(load) {
|
|
|
104
80
|
/**
|
|
105
81
|
* Import a user-authored module. `.ts` is loaded via jiti; `.mjs`/`.js` use
|
|
106
82
|
* native dynamic import for ordinary cached reads. A fresh `.ts` read uses a
|
|
107
|
-
* no-cache jiti instance.
|
|
108
|
-
*
|
|
109
|
-
*
|
|
83
|
+
* no-cache jiti instance. Any other fresh read drops the module from the
|
|
84
|
+
* require cache and imports a cache-busted file URL. Node then chooses its
|
|
85
|
+
* format as for the first read, so a fresh read gives what a first read gives.
|
|
110
86
|
* Normal loads retain module caching. `fresh` rereads a file that changed
|
|
111
87
|
* during this process: one a codemod rewrote, or an edited doc file (the doc
|
|
112
88
|
* importers in ../doc-compiler/import.mjs read each change once).
|
|
@@ -251,31 +227,77 @@ async function importModule(file, fresh) {
|
|
|
251
227
|
if (fresh && file.endsWith('.ts')) {
|
|
252
228
|
return await createJiti(import.meta.url, {moduleCache: false}).import(file);
|
|
253
229
|
}
|
|
254
|
-
if (fresh && file.endsWith('.js') && isCommonJsFile(file)) {
|
|
255
|
-
const resolved = requireFromHere.resolve(file);
|
|
256
|
-
delete requireFromHere.cache[resolved];
|
|
257
|
-
const value = requireFromHere(file);
|
|
258
|
-
if (
|
|
259
|
-
value != null &&
|
|
260
|
-
typeof value === 'object' &&
|
|
261
|
-
value.__esModule === true &&
|
|
262
|
-
Object.prototype.hasOwnProperty.call(value, 'default')
|
|
263
|
-
) {
|
|
264
|
-
return value;
|
|
265
|
-
}
|
|
266
|
-
return {
|
|
267
|
-
...(value != null && typeof value === 'object' ? value : {}),
|
|
268
|
-
default: value,
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
230
|
if (file.endsWith('.ts')) {
|
|
272
231
|
return await getJiti().import(file);
|
|
273
232
|
}
|
|
274
233
|
const url = pathToFileURL(file);
|
|
275
|
-
if (fresh)
|
|
276
|
-
|
|
234
|
+
if (!fresh) return await readInTurn(file, () => import(url.href));
|
|
235
|
+
// Node chooses the format again, as for the first import: a package's
|
|
236
|
+
// "type", the extension, or the source's syntax. The query names a new ES
|
|
237
|
+
// module. A CommonJS module also needs its cache entry gone.
|
|
238
|
+
url.searchParams.set('astryx-fresh', String(++freshImportNonce));
|
|
239
|
+
return await readFreshInTurn(file, () => import(url.href));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The name Node's CommonJS cache gives `file`: its real path, so a link and
|
|
244
|
+
* its target name one entry. A file that does not resolve keeps its path.
|
|
245
|
+
* @param {string} file absolute path
|
|
246
|
+
* @returns {string}
|
|
247
|
+
*/
|
|
248
|
+
function cacheKey(file) {
|
|
249
|
+
try {
|
|
250
|
+
return requireFromHere.resolve(file);
|
|
251
|
+
} catch {
|
|
252
|
+
return file;
|
|
277
253
|
}
|
|
278
|
-
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Each file's latest read, by its cache name, settled once that read ends.
|
|
258
|
+
* @type {Map<string, Promise<void>>}
|
|
259
|
+
*/
|
|
260
|
+
const readsInProgress = new Map();
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Run `read`, an import of `file`, once the imports of `file` started before
|
|
264
|
+
* it have ended. Imports of other files do not wait. A fresh read removes the
|
|
265
|
+
* file from Node's CommonJS cache. A CommonJS import still in progress needs
|
|
266
|
+
* that entry until it ends.
|
|
267
|
+
* @template T
|
|
268
|
+
* @param {string} file absolute path
|
|
269
|
+
* @param {() => Promise<T>} read
|
|
270
|
+
* @returns {Promise<T>}
|
|
271
|
+
*/
|
|
272
|
+
export function readInTurn(file, read) {
|
|
273
|
+
const key = cacheKey(file);
|
|
274
|
+
const before = readsInProgress.get(key) ?? Promise.resolve();
|
|
275
|
+
const result = before.then(read);
|
|
276
|
+
const ended = result.then(
|
|
277
|
+
() => {},
|
|
278
|
+
() => {},
|
|
279
|
+
);
|
|
280
|
+
readsInProgress.set(key, ended);
|
|
281
|
+
void ended.then(() => {
|
|
282
|
+
if (readsInProgress.get(key) === ended) readsInProgress.delete(key);
|
|
283
|
+
});
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Run `read`, a fresh import of `file`, in the file's turn (`readInTurn`),
|
|
289
|
+
* once the file is gone from Node's CommonJS cache. A new URL names a new ES
|
|
290
|
+
* module only, so a CommonJS module also needs its cache entry gone.
|
|
291
|
+
* @template T
|
|
292
|
+
* @param {string} file absolute path
|
|
293
|
+
* @param {() => Promise<T>} read
|
|
294
|
+
* @returns {Promise<T>}
|
|
295
|
+
*/
|
|
296
|
+
export function readFreshInTurn(file, read) {
|
|
297
|
+
return readInTurn(file, () => {
|
|
298
|
+
delete requireFromHere.cache[cacheKey(file)];
|
|
299
|
+
return read();
|
|
300
|
+
});
|
|
279
301
|
}
|
|
280
302
|
|
|
281
303
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The size a ResponsiveContainer starts at: its own numbers where it has
|
|
6
|
+
* them. Otherwise the width follows the height: six times it for a
|
|
7
|
+
* sparkline, which sits in a narrow card or a table cell, and two and a half
|
|
8
|
+
* times it for a taller chart. The drawing then scales to the width the
|
|
9
|
+
* page gives it, so its text stays near its own size in most layouts.
|
|
10
|
+
* @param {{width?: unknown, height?: unknown, aspect?: unknown}} props
|
|
11
|
+
* @returns {{width: number, height: number}}
|
|
12
|
+
*/
|
|
13
|
+
export function chartSize({ width, height, aspect }: {
|
|
14
|
+
width?: unknown;
|
|
15
|
+
height?: unknown;
|
|
16
|
+
aspect?: unknown;
|
|
17
|
+
}): {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Mark each container chart whose legend is vertical and on one side with
|
|
23
|
+
* that side, and whether it sits at the bottom or the middle, for
|
|
24
|
+
* {@link CHART_CSS}. A vertically centered legend has a `top` near the
|
|
25
|
+
* middle (no room is kept for it in a container chart). A vertical legend
|
|
26
|
+
* centered across takes its own row, as a horizontal one does.
|
|
27
|
+
* @param {any} root the element the charts were drawn in
|
|
28
|
+
*/
|
|
29
|
+
export function markLegends(root: any): void;
|
|
30
|
+
/**
|
|
31
|
+
* Give this thread the small document a chart draws in: no layout, no
|
|
32
|
+
* motion, and no frames to wait for.
|
|
33
|
+
* @returns {Promise<void>}
|
|
34
|
+
*/
|
|
35
|
+
export function chartDocument(): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* @file Charts in a rendered page. A recharts chart draws only after its
|
|
38
|
+
* effects run, and a ResponsiveContainer measures its parent in a browser, so
|
|
39
|
+
* a server render leaves them empty. The render worker draws each one again
|
|
40
|
+
* in a small document, at the size the container starts at, and puts the
|
|
41
|
+
* drawing in its place.
|
|
42
|
+
*
|
|
43
|
+
* @input The chart elements the server render set aside.
|
|
44
|
+
* @output Their markup, and the CSS that lets a drawn chart fit its width.
|
|
45
|
+
* @position foundation/render — read by page-worker.mjs and render-page.mjs.
|
|
46
|
+
*/
|
|
47
|
+
/** The global the render worker installs to set charts aside. SYNC: page-worker.mjs assigns it by this name. */
|
|
48
|
+
export const CHART_GLOBAL: "__astryxRenderChart";
|
|
49
|
+
/** The recharts exports that draw a chart, besides ResponsiveContainer. */
|
|
50
|
+
export const CHART_ROOTS: Set<string>;
|
|
51
|
+
/**
|
|
52
|
+
* A chart drawn at its starting size scales to the width the page gives it
|
|
53
|
+
* and keeps its shape, so it never overflows its card. Its legend stays out
|
|
54
|
+
* of the scaled picture, so its text never covers the chart however narrow
|
|
55
|
+
* the page is. A horizontal legend takes its own row under the picture, or
|
|
56
|
+
* above it when it asks for the top. A vertical legend on one side sits
|
|
57
|
+
* beside the picture, on that side.
|
|
58
|
+
*/
|
|
59
|
+
export const CHART_CSS: string;
|