@astryxdesign/cli 0.7.12-canary.ccb4a6d → 0.7.12-canary.da3dbdc
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/api/component/__fixtures__/resolutions/modules.txt +5 -5
- 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/index.d.mts +1 -0
- package/api/index.mjs +1 -0
- package/api/render/_adapter.mjs +9 -0
- package/api/render/render.doc.mjs +2 -2
- package/api/search/_adapter.d.mts +22 -1
- package/api/search/_adapter.mjs +37 -20
- 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 +100 -9
- package/api/swizzle/swizzle.doc.mjs +7 -3
- package/api/swizzle/swizzle.type.d.mts +5 -0
- package/api/swizzle/swizzle.type.mjs +1 -0
- package/api/template/template.mjs +1 -0
- package/assets/discover/official-packages.json +16 -0
- package/assets/docs/browser-support.doc.mjs +2 -2
- package/assets/docs/styling-overview.doc.mjs +6 -2
- package/assets/docs/tree/render-pages.doc.mjs +2 -2
- package/assets/docs/tree/tailwind.doc.mjs +41 -1
- package/assets/docs/tree/type-scale.doc.mjs +19 -2
- package/assets/templates/blocks/components/Calendar/useCalendarConstraintsHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Calendar/useCalendarConstraintsHookUsage.tsx +146 -0
- package/assets/templates/blocks/components/Calendar/useCalendarNavigationHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Calendar/useCalendarNavigationHookUsage.tsx +173 -0
- 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 +35 -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-inbox/page.tsx +3 -6
- package/assets/templates/themes/butter/butterTheme.ts +2 -2
- package/assets/templates/themes/stone/stoneTheme.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/swizzle.doc.mjs +4 -0
- package/clients/cli/commands/swizzle.mjs +12 -0
- package/clients/cli/lib/resolve-theme.mjs +16 -7
- package/foundation/discovery/hook-discovery.mjs +31 -8
- 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-worker.mjs +339 -52
- package/foundation/render/render-page.mjs +26 -10
- package/foundation/render/ts-paths.d.mts +63 -0
- package/foundation/render/ts-paths.mjs +138 -0
- package/foundation/response/error-codes.doc.mjs +2 -2
- 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/package.json +7 -6
|
@@ -14,6 +14,7 @@ import * as fs from 'node:fs';
|
|
|
14
14
|
import * as path from 'node:path';
|
|
15
15
|
import {isBuiltin} from 'node:module';
|
|
16
16
|
import {packageOf} from '../../foundation/render/served-packages.mjs';
|
|
17
|
+
import {resolveInstalledPackageDir} from '../../foundation/integrations/installed-package.mjs';
|
|
17
18
|
import {parse} from '@babel/parser';
|
|
18
19
|
import {
|
|
19
20
|
inspectPackageExport,
|
|
@@ -390,6 +391,8 @@ function publicSurfaceOf(packageDir, srcRoot, originsOf) {
|
|
|
390
391
|
* file the copy holds (absolute source path), with the edits its imports need
|
|
391
392
|
* @property {Map<string, Set<string>>} dependsOn file -> carried files its
|
|
392
393
|
* computed specifiers point at
|
|
394
|
+
* @property {Map<string, string>} otherPackages the source root of each other
|
|
395
|
+
* Astryx package the copy carries modules from, with its package name
|
|
393
396
|
* @property {Record<string, string>} dependencies each npm package the copied
|
|
394
397
|
* files import, other than the owner package and Node's built-in modules,
|
|
395
398
|
* with the range the owner package declares
|
|
@@ -429,19 +432,6 @@ export function planCopy({
|
|
|
429
432
|
});
|
|
430
433
|
/** @param {string} file @param {string} name */
|
|
431
434
|
const originOf = (file, name) => originsOf(file, 'type').get(name) ?? null;
|
|
432
|
-
/**
|
|
433
|
-
* The subpaths that export a module's export: as a value when the import
|
|
434
|
-
* takes a value, since a type-only export is erased at runtime.
|
|
435
|
-
* @param {string} file
|
|
436
|
-
* @param {string} name
|
|
437
|
-
* @param {boolean} typeOnly
|
|
438
|
-
*/
|
|
439
|
-
const exportersOf = (file, name, typeOnly) => {
|
|
440
|
-
const origin = originOf(file, name);
|
|
441
|
-
if (!origin) return [];
|
|
442
|
-
const kind = typeOnly ? 'type' : 'value';
|
|
443
|
-
return surface.byOrigin[kind].get(`${origin.file}#${origin.name}`) ?? [];
|
|
444
|
-
};
|
|
445
435
|
/**
|
|
446
436
|
* The name a module exports one of its own bindings under, or null.
|
|
447
437
|
* @param {{file: string, name: string}} origin
|
|
@@ -454,7 +444,6 @@ export function planCopy({
|
|
|
454
444
|
};
|
|
455
445
|
/** @type {Map<string, string|null>} */
|
|
456
446
|
const resolutions = new Map();
|
|
457
|
-
const surface = publicSurfaceOf(packageDir, srcRoot, originsOf);
|
|
458
447
|
/** @type {CopyPlan['files']} */
|
|
459
448
|
const files = new Map();
|
|
460
449
|
/** @type {CopyPlan['dependsOn']} */
|
|
@@ -472,73 +461,162 @@ export function planCopy({
|
|
|
472
461
|
};
|
|
473
462
|
/** @param {string} file */
|
|
474
463
|
const inComponent = file => file.startsWith(componentDir + path.sep);
|
|
475
|
-
/** @param {string} file */
|
|
476
|
-
const inSource = file => file.startsWith(srcRoot + path.sep);
|
|
477
464
|
|
|
478
465
|
/**
|
|
479
|
-
*
|
|
480
|
-
* definition. An exactly exported file is its own subpath.
|
|
481
|
-
* @param {string} file
|
|
482
|
-
* @returns {string|null}
|
|
466
|
+
* @typedef {ReturnType<typeof makeContext>} PackageContext
|
|
483
467
|
*/
|
|
484
|
-
const subpathCoveringModule = file => {
|
|
485
|
-
const exact = surface.byFile.get(file);
|
|
486
|
-
if (exact) return exact;
|
|
487
|
-
const names = [...originsOf(file, 'type').keys()];
|
|
488
|
-
if (names.length === 0) return null;
|
|
489
|
-
const values = originsOf(file, 'value');
|
|
490
|
-
const options = names.map(name =>
|
|
491
|
-
exportersOf(file, name, !values.has(name))
|
|
492
|
-
.filter(o => o.name === name)
|
|
493
|
-
.map(o => o.subpath),
|
|
494
|
-
);
|
|
495
|
-
const common = options.reduce((acc, list) =>
|
|
496
|
-
acc.filter(s => list.includes(s)),
|
|
497
|
-
);
|
|
498
|
-
return STYLEX_FILE.test(file) ? null : pickSubpath(common, file);
|
|
499
|
-
};
|
|
500
468
|
|
|
501
469
|
/**
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
* no public import.
|
|
506
|
-
* @param {string[]} subpaths subpaths that export the name
|
|
507
|
-
* @param {string} target the module the import names
|
|
508
|
-
* @param {string} [originFile] the module that defines the name
|
|
509
|
-
* @returns {string|null}
|
|
470
|
+
* How references inside one package's source are planned: its public
|
|
471
|
+
* surface and the subpaths a name or module is imported from.
|
|
472
|
+
* @param {{packageDir: string, srcRoot: string, ownerPackage: string}} pkg
|
|
510
473
|
*/
|
|
511
|
-
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
474
|
+
function makeContext({packageDir, srcRoot, ownerPackage}) {
|
|
475
|
+
const surface = publicSurfaceOf(packageDir, srcRoot, originsOf);
|
|
476
|
+
/** @param {string} file */
|
|
477
|
+
const inSource = file => file.startsWith(srcRoot + path.sep);
|
|
478
|
+
/**
|
|
479
|
+
* The subpaths that export a module's export: as a value when the import
|
|
480
|
+
* takes a value, since a type-only export is erased at runtime.
|
|
481
|
+
* @param {string} file
|
|
482
|
+
* @param {string} name
|
|
483
|
+
* @param {boolean} typeOnly
|
|
484
|
+
*/
|
|
485
|
+
const exportersOf = (file, name, typeOnly) => {
|
|
486
|
+
const origin = originOf(file, name);
|
|
487
|
+
if (!origin) return [];
|
|
488
|
+
const kind = typeOnly ? 'type' : 'value';
|
|
489
|
+
return surface.byOrigin[kind].get(`${origin.file}#${origin.name}`) ?? [];
|
|
490
|
+
};
|
|
491
|
+
/**
|
|
492
|
+
* The subpath that exports every name a module exports, with the same
|
|
493
|
+
* definition. An exactly exported file is its own subpath.
|
|
494
|
+
* @param {string} file
|
|
495
|
+
* @returns {string|null}
|
|
496
|
+
*/
|
|
497
|
+
const subpathCoveringModule = file => {
|
|
498
|
+
const exact = surface.byFile.get(file);
|
|
499
|
+
if (exact) return exact;
|
|
500
|
+
const names = [...originsOf(file, 'type').keys()];
|
|
501
|
+
if (names.length === 0) return null;
|
|
502
|
+
const values = originsOf(file, 'value');
|
|
503
|
+
const options = names.map(name =>
|
|
504
|
+
exportersOf(file, name, !values.has(name))
|
|
505
|
+
.filter(o => o.name === name)
|
|
506
|
+
.map(o => o.subpath),
|
|
507
|
+
);
|
|
508
|
+
const common = options.reduce((acc, list) =>
|
|
509
|
+
acc.filter(s => list.includes(s)),
|
|
510
|
+
);
|
|
511
|
+
return STYLEX_FILE.test(file) ? null : pickSubpath(common, file);
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* The public subpath to take a name from. A StyleX module is imported by
|
|
516
|
+
* its own exported path, because the StyleX compiler resolves styles and
|
|
517
|
+
* variables from the real module; when that path is not exported, there is
|
|
518
|
+
* no public import.
|
|
519
|
+
* @param {string[]} subpaths subpaths that export the name
|
|
520
|
+
* @param {string} target the module the import names
|
|
521
|
+
* @param {string} [originFile] the module that defines the name
|
|
522
|
+
* @returns {string|null}
|
|
523
|
+
*/
|
|
524
|
+
const choosePublic = (subpaths, target, originFile) => {
|
|
525
|
+
const exact = originFile ? surface.byFile.get(originFile) : undefined;
|
|
526
|
+
if (exact && subpaths.includes(exact)) return exact;
|
|
527
|
+
if (
|
|
528
|
+
STYLEX_FILE.test(target) ||
|
|
529
|
+
(originFile && STYLEX_FILE.test(originFile))
|
|
530
|
+
)
|
|
531
|
+
return null;
|
|
532
|
+
return pickSubpath(subpaths, target);
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Prefer the subpath of the module's top folder, then the package root.
|
|
537
|
+
* @param {string[]} subpaths
|
|
538
|
+
* @param {string} file
|
|
539
|
+
* @returns {string|null}
|
|
540
|
+
*/
|
|
541
|
+
const pickSubpath = (subpaths, file) => {
|
|
542
|
+
if (subpaths.length === 0) return null;
|
|
543
|
+
const top = `./${toPosix(path.relative(srcRoot, file)).split('/')[0]}`;
|
|
544
|
+
return (
|
|
545
|
+
subpaths.find(s => s === top) ??
|
|
546
|
+
subpaths.find(s => s === '.') ??
|
|
547
|
+
[...subpaths].sort((a, b) => a.length - b.length)[0]
|
|
548
|
+
);
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
/** @param {string} subpath */
|
|
552
|
+
const publicSpecifier = subpath =>
|
|
553
|
+
subpath === '.' ? ownerPackage : `${ownerPackage}/${subpath.slice(2)}`;
|
|
554
|
+
|
|
555
|
+
return {
|
|
556
|
+
packageDir,
|
|
557
|
+
srcRoot,
|
|
558
|
+
ownerPackage,
|
|
559
|
+
surface,
|
|
560
|
+
inSource,
|
|
561
|
+
exportersOf,
|
|
562
|
+
subpathCoveringModule,
|
|
563
|
+
choosePublic,
|
|
564
|
+
publicSpecifier,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const owner = makeContext({packageDir, srcRoot, ownerPackage});
|
|
568
|
+
/** @type {Map<string, PackageContext>} other packages' contexts, by source root */
|
|
569
|
+
const others = new Map();
|
|
570
|
+
/** @param {string} file */
|
|
571
|
+
const contextOf = file => {
|
|
572
|
+
if (file.startsWith(srcRoot + path.sep)) return owner;
|
|
573
|
+
for (const [root, context] of others) {
|
|
574
|
+
if (file.startsWith(root + path.sep)) return context;
|
|
575
|
+
}
|
|
576
|
+
return owner;
|
|
520
577
|
};
|
|
521
578
|
|
|
522
579
|
/**
|
|
523
|
-
*
|
|
524
|
-
*
|
|
580
|
+
* The source module a bare import of another Astryx package's
|
|
581
|
+
* `./internal/*` path names, or null. That module is carried, and its
|
|
582
|
+
* package gets a context of its own.
|
|
525
583
|
* @param {string} file
|
|
584
|
+
* @param {string} specifier
|
|
526
585
|
* @returns {string|null}
|
|
527
586
|
*/
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
);
|
|
587
|
+
const internalModuleOf = (file, specifier) => {
|
|
588
|
+
const name = packageOf(specifier);
|
|
589
|
+
const sub = specifier.slice(name.length + 1);
|
|
590
|
+
if (!name.startsWith('@astryxdesign/') || name === ownerPackage)
|
|
591
|
+
return null;
|
|
592
|
+
if (!/^internal(\/|$)/.test(sub)) return null;
|
|
593
|
+
const installed = resolveInstalledPackageDir(name, path.dirname(file));
|
|
594
|
+
if (!installed) return null;
|
|
595
|
+
const dir = fs.realpathSync(installed.packageDir);
|
|
596
|
+
/** @type {{exports?: unknown}} */
|
|
597
|
+
let pkg;
|
|
598
|
+
try {
|
|
599
|
+
pkg = JSON.parse(
|
|
600
|
+
fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'),
|
|
601
|
+
);
|
|
602
|
+
} catch {
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
const target = sourceExportTarget(pkg, dir, `./${sub}`);
|
|
606
|
+
// A StyleX module's identity is its path, so the copy keeps importing it
|
|
607
|
+
// by its exported path and agrees with the package on it.
|
|
608
|
+
if (!target || STYLEX_FILE.test(target)) return null;
|
|
609
|
+
const src = path.join(dir, 'src');
|
|
610
|
+
const root = target.startsWith(src + path.sep) ? src : dir;
|
|
611
|
+
if (!others.has(root)) {
|
|
612
|
+
others.set(
|
|
613
|
+
root,
|
|
614
|
+
makeContext({packageDir: dir, srcRoot: root, ownerPackage: name}),
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
return target;
|
|
536
618
|
};
|
|
537
619
|
|
|
538
|
-
/** @param {string} subpath */
|
|
539
|
-
const publicSpecifier = subpath =>
|
|
540
|
-
subpath === '.' ? ownerPackage : `${ownerPackage}/${subpath.slice(2)}`;
|
|
541
|
-
|
|
542
620
|
/**
|
|
543
621
|
* Carry a module in the copy.
|
|
544
622
|
* @param {string} from
|
|
@@ -560,8 +638,14 @@ export function planCopy({
|
|
|
560
638
|
const edits = [];
|
|
561
639
|
files.set(file, {code: parsed.code, edits});
|
|
562
640
|
if (!CODE_FILE.test(file)) continue;
|
|
641
|
+
const context = contextOf(file);
|
|
563
642
|
for (const ref of parsed.references) {
|
|
564
643
|
if (!ref.source.startsWith('.')) {
|
|
644
|
+
const internal = internalModuleOf(file, ref.source);
|
|
645
|
+
if (internal) {
|
|
646
|
+
edits.push(sourceEdit(parsed.code, ref, carry(file, internal)));
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
565
649
|
const builtin = ref.source.startsWith('node:') || isBuiltin(ref.source);
|
|
566
650
|
const name = packageOf(ref.source);
|
|
567
651
|
if (!builtin && name !== ownerPackage) packages.add(name);
|
|
@@ -576,44 +660,61 @@ export function planCopy({
|
|
|
576
660
|
else edits.push(sourceEdit(parsed.code, ref, carry(file, target)));
|
|
577
661
|
continue;
|
|
578
662
|
}
|
|
579
|
-
if (!CODE_FILE.test(target) || !inSource(target)) {
|
|
663
|
+
if (!CODE_FILE.test(target) || !context.inSource(target)) {
|
|
580
664
|
// An asset, such as `../../locales/en.json` or a stylesheet, that the
|
|
581
665
|
// package exports by its own path is imported from the package.
|
|
582
|
-
const subpath = `./${toPosix(path.relative(packageDir, target))}`;
|
|
666
|
+
const subpath = `./${toPosix(path.relative(context.packageDir, target))}`;
|
|
583
667
|
if (
|
|
584
|
-
subpathDeclaration(surface.exportsField, subpath).state ===
|
|
668
|
+
subpathDeclaration(context.surface.exportsField, subpath).state ===
|
|
669
|
+
'declared'
|
|
585
670
|
) {
|
|
671
|
+
if (context !== owner) packages.add(context.ownerPackage);
|
|
586
672
|
edits.push(
|
|
587
|
-
sourceEdit(
|
|
673
|
+
sourceEdit(
|
|
674
|
+
parsed.code,
|
|
675
|
+
ref,
|
|
676
|
+
`${context.ownerPackage}/${subpath.slice(2)}`,
|
|
677
|
+
),
|
|
588
678
|
);
|
|
589
679
|
continue;
|
|
590
680
|
}
|
|
591
681
|
// An unexported asset in the source is carried; one outside it is
|
|
592
682
|
// left for the build to report.
|
|
593
|
-
if (!inSource(target)) continue;
|
|
683
|
+
if (!context.inSource(target)) continue;
|
|
594
684
|
edits.push(sourceEdit(parsed.code, ref, carry(file, target)));
|
|
595
685
|
continue;
|
|
596
686
|
}
|
|
597
|
-
edits.push(planReference(file, parsed.code, ref, target));
|
|
687
|
+
edits.push(planReference(context, file, parsed.code, ref, target));
|
|
598
688
|
}
|
|
599
689
|
}
|
|
600
|
-
return {
|
|
690
|
+
return {
|
|
691
|
+
files,
|
|
692
|
+
dependsOn,
|
|
693
|
+
dependencies: declaredRanges(packageDir, packages),
|
|
694
|
+
otherPackages: new Map(
|
|
695
|
+
[...others].map(([root, c]) => [root, c.ownerPackage]),
|
|
696
|
+
),
|
|
697
|
+
};
|
|
601
698
|
|
|
602
699
|
/**
|
|
603
700
|
* Decide one reference that leaves the component folder.
|
|
701
|
+
* @param {PackageContext} context the package the importing file is in
|
|
604
702
|
* @param {string} file
|
|
605
703
|
* @param {string} code
|
|
606
704
|
* @param {ModuleReference} ref
|
|
607
705
|
* @param {string} target
|
|
608
706
|
* @returns {PlannedEdit}
|
|
609
707
|
*/
|
|
610
|
-
function planReference(file, code, ref, target) {
|
|
708
|
+
function planReference(context, file, code, ref, target) {
|
|
709
|
+
// A carried module of another package imports that package's public names
|
|
710
|
+
// from it, so the copy depends on it.
|
|
711
|
+
if (context !== owner) packages.add(context.ownerPackage);
|
|
611
712
|
if (ref.kind !== 'named' && ref.kind !== 'reexportNamed') {
|
|
612
|
-
const covering = subpathCoveringModule(target);
|
|
713
|
+
const covering = context.subpathCoveringModule(target);
|
|
613
714
|
return sourceEdit(
|
|
614
715
|
code,
|
|
615
716
|
ref,
|
|
616
|
-
covering ? publicSpecifier(covering) : carry(file, target),
|
|
717
|
+
covering ? context.publicSpecifier(covering) : carry(file, target),
|
|
617
718
|
);
|
|
618
719
|
}
|
|
619
720
|
/** @type {Array<{choice: SpecifierChoice, specs: ImportSpecifier[]}>} */
|
|
@@ -630,13 +731,20 @@ export function planCopy({
|
|
|
630
731
|
};
|
|
631
732
|
for (const spec of ref.specifiers) {
|
|
632
733
|
if (spec.imported === '*') {
|
|
633
|
-
const covering = subpathCoveringModule(target);
|
|
634
|
-
add(
|
|
734
|
+
const covering = context.subpathCoveringModule(target);
|
|
735
|
+
add(
|
|
736
|
+
covering ? context.publicSpecifier(covering) : carry(file, target),
|
|
737
|
+
spec,
|
|
738
|
+
);
|
|
635
739
|
continue;
|
|
636
740
|
}
|
|
637
741
|
const origin = originOf(target, spec.imported);
|
|
638
|
-
const publicOptions = exportersOf(
|
|
639
|
-
|
|
742
|
+
const publicOptions = context.exportersOf(
|
|
743
|
+
target,
|
|
744
|
+
spec.imported,
|
|
745
|
+
spec.typeOnly,
|
|
746
|
+
);
|
|
747
|
+
const chosen = context.choosePublic(
|
|
640
748
|
publicOptions.map(o => o.subpath),
|
|
641
749
|
target,
|
|
642
750
|
origin?.file,
|
|
@@ -645,7 +753,7 @@ export function planCopy({
|
|
|
645
753
|
const exported = /** @type {{name: string}} */ (
|
|
646
754
|
publicOptions.find(o => o.subpath === chosen)
|
|
647
755
|
).name;
|
|
648
|
-
add(publicSpecifier(chosen), {...spec, imported: exported});
|
|
756
|
+
add(context.publicSpecifier(chosen), {...spec, imported: exported});
|
|
649
757
|
} else {
|
|
650
758
|
// Take the name from the module that defines it, so the copy carries
|
|
651
759
|
// that module and not a whole barrel. An origin names a binding, so
|
|
@@ -654,7 +762,7 @@ export function planCopy({
|
|
|
654
762
|
const definingExport =
|
|
655
763
|
origin &&
|
|
656
764
|
origin.file !== target &&
|
|
657
|
-
inSource(origin.file) &&
|
|
765
|
+
context.inSource(origin.file) &&
|
|
658
766
|
!parseModule(target).declares.has(spec.imported)
|
|
659
767
|
? exportNameOf(origin)
|
|
660
768
|
: null;
|
|
@@ -361,12 +361,32 @@ export async function swizzleCopy(component, options = {}) {
|
|
|
361
361
|
);
|
|
362
362
|
/** @type {Map<string, string>} */
|
|
363
363
|
const rootDests = new Map();
|
|
364
|
+
// A module from another Astryx package goes under a folder named after that
|
|
365
|
+
// package, at its path in that package's source.
|
|
366
|
+
/** @param {string} file */
|
|
367
|
+
const otherPackageOf = file => {
|
|
368
|
+
for (const [root, name] of plan.otherPackages) {
|
|
369
|
+
if (file.startsWith(root + path.sep)) return {root, folder: name.split('/').pop() ?? name};
|
|
370
|
+
}
|
|
371
|
+
return null;
|
|
372
|
+
};
|
|
373
|
+
/** @param {string} file the version of the package a helper comes from */
|
|
374
|
+
const versionOf = file => {
|
|
375
|
+
const other = otherPackageOf(file);
|
|
376
|
+
return other
|
|
377
|
+
? packageVersionOf(findPackageRoot(other.root) ?? other.root)
|
|
378
|
+
: ownerVersion;
|
|
379
|
+
};
|
|
364
380
|
for (const file of [...allFiles].sort()) {
|
|
365
381
|
if (layoutDestOf(file) != null) continue;
|
|
366
|
-
const
|
|
367
|
-
|
|
382
|
+
const other = otherPackageOf(file);
|
|
383
|
+
const base = other ? other.folder : path.basename(packageDir);
|
|
384
|
+
const packagePath = path.relative(other ? other.root : graphRoot, file);
|
|
385
|
+
let dest = other
|
|
386
|
+
? path.join(outputBase, base, packagePath)
|
|
387
|
+
: path.join(outputBase, packagePath);
|
|
368
388
|
for (let n = 1; takenDests.has(dest); n++) {
|
|
369
|
-
const folder = n === 1 ?
|
|
389
|
+
const folder = n === 1 && !other ? base : `${base}-${n}`;
|
|
370
390
|
dest = path.join(outputBase, folder, packagePath);
|
|
371
391
|
}
|
|
372
392
|
takenDests.add(dest);
|
|
@@ -381,15 +401,17 @@ export async function swizzleCopy(component, options = {}) {
|
|
|
381
401
|
// A private attribute owner module and the unexported style modules it
|
|
382
402
|
// imports travel whole and are replaced only with --overwrite.
|
|
383
403
|
const ownerGroup = new Set();
|
|
404
|
+
/** @param {string} file the source root a carried module sits under */
|
|
405
|
+
const rootOf = file => otherPackageOf(file)?.root ?? srcRoot;
|
|
384
406
|
const ownerQueue = helpers.filter(f =>
|
|
385
|
-
OWNER_MODULE.test(toModulePath(
|
|
407
|
+
OWNER_MODULE.test(toModulePath(rootOf(f), f)),
|
|
386
408
|
);
|
|
387
409
|
while (ownerQueue.length > 0) {
|
|
388
410
|
const file = /** @type {string} */ (ownerQueue.shift());
|
|
389
411
|
if (ownerGroup.has(file)) continue;
|
|
390
412
|
ownerGroup.add(file);
|
|
391
413
|
for (const dep of plan.dependsOn.get(file) ?? []) {
|
|
392
|
-
if (!isComponentFile(dep) && STYLEX_MODULE.test(toModulePath(
|
|
414
|
+
if (!isComponentFile(dep) && STYLEX_MODULE.test(toModulePath(rootOf(dep), dep))) {
|
|
393
415
|
ownerQueue.push(dep);
|
|
394
416
|
}
|
|
395
417
|
}
|
|
@@ -431,7 +453,7 @@ export async function swizzleCopy(component, options = {}) {
|
|
|
431
453
|
attempts.set(file, attempt);
|
|
432
454
|
redirected.set(
|
|
433
455
|
file,
|
|
434
|
-
versionedPath(baseDestOf(file),
|
|
456
|
+
versionedPath(baseDestOf(file), versionOf(file), attempt),
|
|
435
457
|
);
|
|
436
458
|
moved = true;
|
|
437
459
|
}
|
|
@@ -473,16 +495,51 @@ export async function swizzleCopy(component, options = {}) {
|
|
|
473
495
|
);
|
|
474
496
|
}
|
|
475
497
|
|
|
498
|
+
// The marker tells @astryxdesign/build that the output folder holds Astryx
|
|
499
|
+
// source, so the copy compiles below the theme as the package does.
|
|
500
|
+
const markerDest = confineWrite(path.join(outputBase, SWIZZLE_MARKER), cwd, {
|
|
501
|
+
allowAbsolute: true,
|
|
502
|
+
label: 'output file',
|
|
503
|
+
});
|
|
504
|
+
const markerExisting = readExisting(markerDest);
|
|
505
|
+
const markerContent = swizzleMarkerContent(
|
|
506
|
+
markerExisting,
|
|
507
|
+
owner.ownerPackage,
|
|
508
|
+
ownerVersion,
|
|
509
|
+
);
|
|
510
|
+
// Every module under the marker compiles as Astryx source, so swizzle marks
|
|
511
|
+
// only a folder it owns: one the copy creates, an empty one, or one it
|
|
512
|
+
// marked before. Any other folder is reported with what it holds.
|
|
513
|
+
const foundInOutputBase =
|
|
514
|
+
markerExisting == null &&
|
|
515
|
+
fs.existsSync(outputBase) &&
|
|
516
|
+
fs.statSync(outputBase).isDirectory()
|
|
517
|
+
? fs.readdirSync(outputBase).sort()
|
|
518
|
+
: [];
|
|
519
|
+
const marksOutputBase = foundInOutputBase.length === 0;
|
|
520
|
+
const writes = marksOutputBase
|
|
521
|
+
? [
|
|
522
|
+
...outputs,
|
|
523
|
+
{
|
|
524
|
+
dest: markerDest,
|
|
525
|
+
content: markerContent,
|
|
526
|
+
marker: true,
|
|
527
|
+
unchanged: markerExisting === markerContent,
|
|
528
|
+
},
|
|
529
|
+
]
|
|
530
|
+
: outputs;
|
|
531
|
+
|
|
476
532
|
const outputDirExisted = fs.existsSync(outputDir);
|
|
477
533
|
/** @type {string[]} folders this run created, outermost first */
|
|
478
534
|
const createdDirs = [];
|
|
479
535
|
/** @type {Array<{dest: string, original: Buffer|null}>} */
|
|
480
536
|
const written = [];
|
|
481
537
|
let copied = 0;
|
|
482
|
-
for (const output of
|
|
538
|
+
for (const output of writes) {
|
|
539
|
+
const counts = !('marker' in output);
|
|
483
540
|
// An identical file is already in place: it is part of this copy, unwritten.
|
|
484
541
|
if (output.unchanged) {
|
|
485
|
-
copied++;
|
|
542
|
+
if (counts) copied++;
|
|
486
543
|
continue;
|
|
487
544
|
}
|
|
488
545
|
try {
|
|
@@ -491,7 +548,7 @@ export async function swizzleCopy(component, options = {}) {
|
|
|
491
548
|
if (firstCreated != null) createdDirs.push(firstCreated);
|
|
492
549
|
written.push({dest: output.dest, original});
|
|
493
550
|
fs.writeFileSync(output.dest, output.content);
|
|
494
|
-
copied++;
|
|
551
|
+
if (counts) copied++;
|
|
495
552
|
} catch (err) {
|
|
496
553
|
const unrestored = undoCopy(written);
|
|
497
554
|
for (const dir of [...createdDirs].reverse()) {
|
|
@@ -516,10 +573,44 @@ export async function swizzleCopy(component, options = {}) {
|
|
|
516
573
|
usesStyleX: outputs.some(o => o.content.includes('@stylexjs/stylex')),
|
|
517
574
|
dependencies: plan.dependencies,
|
|
518
575
|
};
|
|
576
|
+
data.marker = marksOutputBase
|
|
577
|
+
? {path: toPosix(path.relative(cwd, markerDest)), written: true}
|
|
578
|
+
: {
|
|
579
|
+
path: toPosix(path.relative(cwd, markerDest)),
|
|
580
|
+
written: false,
|
|
581
|
+
found: foundInOutputBase.slice(0, 5),
|
|
582
|
+
};
|
|
519
583
|
if (feedback) data.feedback = feedback;
|
|
520
584
|
return {type: 'swizzle.copy', package: data.package, data};
|
|
521
585
|
}
|
|
522
586
|
|
|
587
|
+
/** The file that marks a swizzle output folder as Astryx source. */
|
|
588
|
+
const SWIZZLE_MARKER = 'astryx-swizzle.json';
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* The marker's content after a copy from `ownerPackage`: the packages the
|
|
592
|
+
* folder's copies came from, each with its version. Unreadable content is
|
|
593
|
+
* replaced.
|
|
594
|
+
* @param {string|null} existing
|
|
595
|
+
* @param {string} ownerPackage
|
|
596
|
+
* @param {string} version
|
|
597
|
+
* @returns {string}
|
|
598
|
+
*/
|
|
599
|
+
function swizzleMarkerContent(existing, ownerPackage, version) {
|
|
600
|
+
/** @type {Record<string, string>} */
|
|
601
|
+
let packages = {};
|
|
602
|
+
try {
|
|
603
|
+
const parsed = existing == null ? null : JSON.parse(existing);
|
|
604
|
+
if (parsed?.packages && typeof parsed.packages === 'object') {
|
|
605
|
+
packages = {...parsed.packages};
|
|
606
|
+
}
|
|
607
|
+
} catch {
|
|
608
|
+
// Unreadable content is rewritten below.
|
|
609
|
+
}
|
|
610
|
+
packages[ownerPackage] = version;
|
|
611
|
+
return `${JSON.stringify({packages}, null, 2)}\n`;
|
|
612
|
+
}
|
|
613
|
+
|
|
523
614
|
/**
|
|
524
615
|
* Undo the writes of a copy that failed part-way, newest first: delete the
|
|
525
616
|
* files the copy created and put back the bytes of the files it replaced. A
|
|
@@ -21,8 +21,12 @@ export const doc = {
|
|
|
21
21
|
'the owner package exports: names the package exports are imported from its ' +
|
|
22
22
|
'public paths, and every internal module the component needs is ' +
|
|
23
23
|
"copied beside it in the package's folder layout. Tests, stories and docs are not " +
|
|
24
|
-
'copied.
|
|
25
|
-
'
|
|
24
|
+
'copied. An output folder swizzle owns (one the copy creates, an empty one, or one it ' +
|
|
25
|
+
'marked before) gets an astryx-swizzle.json marker naming each package its copies came from, ' +
|
|
26
|
+
"with its version, so @astryxdesign/build's Babel, PostCSS and Vite integrations, and " +
|
|
27
|
+
'withAstryx() under webpack, compile the copy below the theme. The receipt flags whether ' +
|
|
28
|
+
'any copied file uses StyleX and lists each npm package the copy imports with the range ' +
|
|
29
|
+
'the owner package declares. With no name ' +
|
|
26
30
|
'(or list) it returns the swizzlable component names instead. An integration ' +
|
|
27
31
|
'component that replaces a Core component is what that Core name copies; ' +
|
|
28
32
|
"options.package '@astryxdesign/core' copies the original. The list names Core " +
|
|
@@ -81,7 +85,7 @@ export const doc = {
|
|
|
81
85
|
{
|
|
82
86
|
type: 'swizzle.copy',
|
|
83
87
|
description:
|
|
84
|
-
'A receipt after copying the component into the project: the component name, owning package, output directory, files-copied count, the copied file names (relative to the component folder; the internal modules copied beside it start with `../`, e.g. `../InputGroup/groupStyles.ts`), whether any file uses StyleX, and, when the owner has an issues URL, feedback ({issuesUrl, ghCommand?}): where to report the gap that led to swizzling.',
|
|
88
|
+
'A receipt after copying the component into the project: the component name, owning package, output directory, files-copied count, the copied file names (relative to the component folder; the internal modules copied beside it start with `../`, e.g. `../InputGroup/groupStyles.ts`), whether any file uses StyleX, the npm packages the copy imports with the ranges the owner declares (dependencies), the output folder\'s astryx-swizzle.json (marker: {path, written, found?}; written is false for a folder swizzle does not own, and found names up to five entries it already holds), and, when the owner has an issues URL, feedback ({issuesUrl, ghCommand?}): where to report the gap that led to swizzling.',
|
|
85
89
|
},
|
|
86
90
|
],
|
|
87
91
|
throws: [
|
|
@@ -42,6 +42,11 @@ export type SwizzleCopyResponse = {
|
|
|
42
42
|
files: string[];
|
|
43
43
|
usesStyleX: boolean;
|
|
44
44
|
dependencies: Record<string, string>;
|
|
45
|
+
marker?: {
|
|
46
|
+
path: string;
|
|
47
|
+
written: boolean;
|
|
48
|
+
found?: string[];
|
|
49
|
+
} | undefined;
|
|
45
50
|
feedback?: SwizzleFeedback | undefined;
|
|
46
51
|
};
|
|
47
52
|
};
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
* @property {string[]} data.files Copied file names, relative to the component folder; the internal modules copied beside it start with `../` (spec:AST-110 FR2).
|
|
38
38
|
* @property {boolean} data.usesStyleX Whether any copied file uses StyleX (requires build-time setup).
|
|
39
39
|
* @property {Record<string, string>} data.dependencies Each npm package the copied files import, other than the owner package, with the version range the owner package declares (`*` when it declares none). The copy builds once the product installs them (spec:AST-110 FR1).
|
|
40
|
+
* @property {{path: string, written: boolean, found?: string[]}} [data.marker] The output folder's astryx-swizzle.json, relative to the project (spec:AST-110 FR5). Swizzle writes it only into a folder it owns: one the copy creates, an empty one, or one it marked before. When it does not, `written` is false and `found` names up to five entries already in the folder.
|
|
40
41
|
* @property {SwizzleFeedback} [data.feedback]
|
|
41
42
|
*/
|
|
42
43
|
|
|
@@ -193,6 +193,7 @@ export async function template(name, options = {}) {
|
|
|
193
193
|
`Unknown template "${name}"`,
|
|
194
194
|
orderByAffinity(name, templates, t => [t.dirName, t.name], {
|
|
195
195
|
closest: true,
|
|
196
|
+
typos: true,
|
|
196
197
|
}).map(t => ({
|
|
197
198
|
name: t.dirName,
|
|
198
199
|
reason: `${t.type} template`,
|
|
@@ -100,6 +100,10 @@
|
|
|
100
100
|
"kind": "component",
|
|
101
101
|
"name": "ChatUnreadDivider"
|
|
102
102
|
},
|
|
103
|
+
{
|
|
104
|
+
"kind": "component",
|
|
105
|
+
"name": "ChatVirtualizer"
|
|
106
|
+
},
|
|
103
107
|
{
|
|
104
108
|
"kind": "component",
|
|
105
109
|
"name": "CircleCheckIndicator"
|
|
@@ -128,6 +132,10 @@
|
|
|
128
132
|
"kind": "component",
|
|
129
133
|
"name": "CommentThread"
|
|
130
134
|
},
|
|
135
|
+
{
|
|
136
|
+
"kind": "component",
|
|
137
|
+
"name": "DialogHeroHeader"
|
|
138
|
+
},
|
|
131
139
|
{
|
|
132
140
|
"kind": "component",
|
|
133
141
|
"name": "DiffViewer"
|
|
@@ -136,6 +144,10 @@
|
|
|
136
144
|
"kind": "component",
|
|
137
145
|
"name": "EmojiPicker"
|
|
138
146
|
},
|
|
147
|
+
{
|
|
148
|
+
"kind": "component",
|
|
149
|
+
"name": "LineClamp"
|
|
150
|
+
},
|
|
139
151
|
{
|
|
140
152
|
"kind": "component",
|
|
141
153
|
"name": "ListInput"
|
|
@@ -252,6 +264,10 @@
|
|
|
252
264
|
"kind": "template",
|
|
253
265
|
"name": "CommentThreadShowcase"
|
|
254
266
|
},
|
|
267
|
+
{
|
|
268
|
+
"kind": "template",
|
|
269
|
+
"name": "DialogHeroHeaderShowcase"
|
|
270
|
+
},
|
|
255
271
|
{
|
|
256
272
|
"kind": "template",
|
|
257
273
|
"name": "DiffViewerShowcase"
|