nxds-cli 0.2.0 → 0.3.0
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/dist/index.js +235 -88
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command, CommanderError, Option } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import path12 from "path";
|
|
6
6
|
|
|
7
7
|
// ../catalog/scripts/catalog-schema.ts
|
|
8
8
|
import path from "path";
|
|
@@ -49,6 +49,11 @@ var setupStepSchema = z.enum([
|
|
|
49
49
|
"wrap-root-with-provider"
|
|
50
50
|
]);
|
|
51
51
|
var npmPackagesSchema = z.record(npmNameSchema, rangeSchema);
|
|
52
|
+
var reactMajorSchema = z.union([z.literal(18), z.literal(19)]);
|
|
53
|
+
var react18Schema = z.object({
|
|
54
|
+
files: z.array(catalogFileSchema),
|
|
55
|
+
packages: z.object({ npm: npmPackagesSchema })
|
|
56
|
+
});
|
|
52
57
|
function variantWith(packages) {
|
|
53
58
|
return z.object({
|
|
54
59
|
/** Other items this one needs, installed first. */
|
|
@@ -57,12 +62,35 @@ function variantWith(packages) {
|
|
|
57
62
|
files: z.array(catalogFileSchema),
|
|
58
63
|
/** Files in `files` the nxds CSS entry imports. */
|
|
59
64
|
css: z.array(relativePathSchema),
|
|
60
|
-
steps: z.array(setupStepSchema)
|
|
65
|
+
steps: z.array(setupStepSchema),
|
|
66
|
+
react18: react18Schema.optional()
|
|
61
67
|
}).refine(
|
|
62
68
|
({ css, files }) => css.every((entry) => files.some((file) => file.path === entry)),
|
|
63
69
|
{ path: ["css"], message: "must name files in `files`" }
|
|
70
|
+
).refine(
|
|
71
|
+
({ files, react18 }) => (react18?.files ?? []).every(
|
|
72
|
+
(file) => files.some((own) => own.path === file.path)
|
|
73
|
+
),
|
|
74
|
+
{
|
|
75
|
+
path: ["react18", "files"],
|
|
76
|
+
message: "must replace files in `files`"
|
|
77
|
+
}
|
|
64
78
|
);
|
|
65
79
|
}
|
|
80
|
+
function variantForReact(variant, react) {
|
|
81
|
+
if (react === 19 || !variant.react18) return variant;
|
|
82
|
+
const replaced = new Map(
|
|
83
|
+
variant.react18.files.map((file) => [file.path, file])
|
|
84
|
+
);
|
|
85
|
+
return {
|
|
86
|
+
...variant,
|
|
87
|
+
files: variant.files.map((file) => replaced.get(file.path) ?? file),
|
|
88
|
+
packages: {
|
|
89
|
+
...variant.packages,
|
|
90
|
+
npm: { ...variant.packages.npm, ...variant.react18.packages.npm }
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
66
94
|
var variantSchema = variantWith(z.object({ npm: npmPackagesSchema }));
|
|
67
95
|
var setupVariantSchema = variantWith(
|
|
68
96
|
z.object({
|
|
@@ -97,6 +125,8 @@ var catalogSchema = z.object({
|
|
|
97
125
|
version: versionSchema,
|
|
98
126
|
/** The frameworks the CLI installs. The docs copy also carries variants for others; a release catalog drops them. Names this version doesn't know are dropped too. */
|
|
99
127
|
frameworks: z.array(z.string()).transform((frameworks) => frameworks.filter(isFramework)),
|
|
128
|
+
/** The React majors this release has files for. Releases from before React 18 support leave it out, and have React 19 files only. */
|
|
129
|
+
reactMajors: z.array(reactMajorSchema).nonempty().default([19]),
|
|
100
130
|
items: z.array(catalogItemSchema)
|
|
101
131
|
});
|
|
102
132
|
function lines(issues, prefix) {
|
|
@@ -214,7 +244,7 @@ async function fetchRelease(registry, version) {
|
|
|
214
244
|
if (isNewerSchema(value)) {
|
|
215
245
|
const release = version === "latest" ? "The latest nxds release" : `nxds ${version}`;
|
|
216
246
|
throw new CliError(
|
|
217
|
-
`${release} needs a newer nxds-cli than ${"0.
|
|
247
|
+
`${release} needs a newer nxds-cli than ${"0.3.0"}. Run \`${CLI_COMMAND}\` to get the newest one.`
|
|
218
248
|
);
|
|
219
249
|
}
|
|
220
250
|
const invalid = (problems) => new CliError(
|
|
@@ -229,11 +259,18 @@ Check \`--registry\`, or run the command again later.`
|
|
|
229
259
|
}
|
|
230
260
|
return parsed.data;
|
|
231
261
|
}
|
|
232
|
-
async function loadRelease(registry, version) {
|
|
262
|
+
async function loadRelease(registry, version, react) {
|
|
233
263
|
const catalog = await fetchRelease(registry, version);
|
|
234
|
-
if (catalog)
|
|
264
|
+
if (!catalog) {
|
|
265
|
+
throw new CliError(
|
|
266
|
+
version === "latest" ? `${registry} has no nxds release.` : `${registry} has no nxds ${version}, the release this app is on. Run \`${CLI_COMMAND} update\` to move to the latest release.`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
if (catalog.reactMajors.includes(react)) return catalog;
|
|
270
|
+
const latest = version === "latest" ? null : await fetchRelease(registry, "latest");
|
|
271
|
+
const fix = latest?.reactMajors.includes(react) ? `Upgrade the app to React 19, or run \`${CLI_COMMAND} update\` to move to nxds ${latest.version}, which has React ${react} files.` : "Upgrade the app to React 19.";
|
|
235
272
|
throw new CliError(
|
|
236
|
-
|
|
273
|
+
`nxds ${catalog.version} has no React ${react} files. ${fix}`
|
|
237
274
|
);
|
|
238
275
|
}
|
|
239
276
|
function findItem(catalog, name) {
|
|
@@ -248,12 +285,15 @@ function variantOf(item, framework) {
|
|
|
248
285
|
if (variant) return variant;
|
|
249
286
|
throw new CliError(`${item.title} is not available for ${framework} yet.`);
|
|
250
287
|
}
|
|
251
|
-
function
|
|
288
|
+
function variantFor(item, target) {
|
|
289
|
+
return variantForReact(variantOf(item, target.framework), target.react);
|
|
290
|
+
}
|
|
291
|
+
function setupVariant(catalog, target) {
|
|
252
292
|
const setup = catalog.items.find((item) => item.name === SETUP);
|
|
253
293
|
if (setup?.kind !== "setup") {
|
|
254
294
|
throw new Error(`nxds ${catalog.version} has no "${SETUP}" setup item.`);
|
|
255
295
|
}
|
|
256
|
-
return
|
|
296
|
+
return variantFor(setup, target);
|
|
257
297
|
}
|
|
258
298
|
function resolveItems(catalog, framework, names) {
|
|
259
299
|
const ordered = [];
|
|
@@ -268,10 +308,10 @@ function resolveItems(catalog, framework, names) {
|
|
|
268
308
|
names.forEach(visit);
|
|
269
309
|
return ordered;
|
|
270
310
|
}
|
|
271
|
-
function releaseFiles(catalog,
|
|
311
|
+
function releaseFiles(catalog, target) {
|
|
272
312
|
return new Map(
|
|
273
|
-
catalog.items.flatMap(
|
|
274
|
-
(item) => (item
|
|
313
|
+
catalog.items.filter((item) => item.variants[target.framework]).flatMap(
|
|
314
|
+
(item) => variantFor(item, target).files.map(
|
|
275
315
|
(file) => [file.path, file.content]
|
|
276
316
|
)
|
|
277
317
|
)
|
|
@@ -285,6 +325,10 @@ function splitInstalled(catalog, installed) {
|
|
|
285
325
|
};
|
|
286
326
|
}
|
|
287
327
|
|
|
328
|
+
// src/commands/browse.ts
|
|
329
|
+
import { existsSync as existsSync5 } from "fs";
|
|
330
|
+
import path6 from "path";
|
|
331
|
+
|
|
288
332
|
// src/config.ts
|
|
289
333
|
import { existsSync as existsSync2, writeFileSync } from "fs";
|
|
290
334
|
import path3 from "path";
|
|
@@ -293,6 +337,8 @@ var CONFIG_FILE = "nxds.json";
|
|
|
293
337
|
var configSchema = z2.looseObject({
|
|
294
338
|
/** The CLI installs React only. */
|
|
295
339
|
framework: z2.literal("react"),
|
|
340
|
+
/** The React major the installed files are for. */
|
|
341
|
+
react: reactMajorSchema,
|
|
296
342
|
/** The nxds release every installed file comes from. */
|
|
297
343
|
version: versionSchema,
|
|
298
344
|
/** The nxds folder, relative to the project root. */
|
|
@@ -333,7 +379,7 @@ function writeConfig(root, config) {
|
|
|
333
379
|
// src/project.ts
|
|
334
380
|
import { existsSync as existsSync4 } from "fs";
|
|
335
381
|
import path5 from "path";
|
|
336
|
-
import { major, minVersion as minVersion2, validRange as validRange3 } from "semver";
|
|
382
|
+
import { major, minVersion as minVersion2, parse, validRange as validRange3 } from "semver";
|
|
337
383
|
|
|
338
384
|
// src/packages.ts
|
|
339
385
|
import { spawnSync } from "child_process";
|
|
@@ -448,6 +494,27 @@ function readProject(root) {
|
|
|
448
494
|
function findFile(project, candidates) {
|
|
449
495
|
return firstExisting(project.root, candidates);
|
|
450
496
|
}
|
|
497
|
+
function reactMajor(project) {
|
|
498
|
+
const declared = project.dependencies.react;
|
|
499
|
+
if (declared === void 0) {
|
|
500
|
+
throw new CliError(
|
|
501
|
+
"This app has no react dependency. nxds needs React 18.3 or 19."
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
const installed = installedVersion(project, "react");
|
|
505
|
+
const range = validRange3(declared) === null ? null : minVersion2(declared);
|
|
506
|
+
const version = installed === null ? range : parse(installed);
|
|
507
|
+
if (version === null) {
|
|
508
|
+
throw new CliError(
|
|
509
|
+
`This app declares react ${declared}, which names no version, and none is installed. Install your dependencies, then run the command again.`
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
if (version.major >= 19) return 19;
|
|
513
|
+
if (version.major === 18 && version.minor >= 3) return 18;
|
|
514
|
+
throw new CliError(
|
|
515
|
+
`nxds needs React 18.3 or 19, and this app uses react ${version.version}. Upgrade React, then run the command again.`
|
|
516
|
+
);
|
|
517
|
+
}
|
|
451
518
|
function majorVersion(project, name) {
|
|
452
519
|
const declared = project.dependencies[name];
|
|
453
520
|
if (declared === void 0) return null;
|
|
@@ -457,8 +524,8 @@ function majorVersion(project, name) {
|
|
|
457
524
|
}
|
|
458
525
|
|
|
459
526
|
// src/commands/browse.ts
|
|
460
|
-
function appRelease(
|
|
461
|
-
return loadRelease(registry,
|
|
527
|
+
function appRelease(registry, config, react) {
|
|
528
|
+
return loadRelease(registry, config?.version ?? "latest", react);
|
|
462
529
|
}
|
|
463
530
|
function summary(item) {
|
|
464
531
|
return {
|
|
@@ -478,7 +545,12 @@ function summaryLines(items) {
|
|
|
478
545
|
);
|
|
479
546
|
}
|
|
480
547
|
async function list(cwd, registry, kind) {
|
|
481
|
-
const
|
|
548
|
+
const catalog = await appRelease(
|
|
549
|
+
registry,
|
|
550
|
+
readConfig(cwd),
|
|
551
|
+
projectReact(cwd)
|
|
552
|
+
);
|
|
553
|
+
const items = catalog.items.filter(
|
|
482
554
|
(item) => item.name !== SETUP && (kind === void 0 || item.kind === kind)
|
|
483
555
|
);
|
|
484
556
|
return { data: items.map(summary), lines: summaryLines(items), ok: true };
|
|
@@ -491,7 +563,12 @@ async function search(cwd, registry, query) {
|
|
|
491
563
|
if (item.description.toLowerCase().includes(needle)) return 2;
|
|
492
564
|
return void 0;
|
|
493
565
|
};
|
|
494
|
-
const
|
|
566
|
+
const catalog = await appRelease(
|
|
567
|
+
registry,
|
|
568
|
+
readConfig(cwd),
|
|
569
|
+
projectReact(cwd)
|
|
570
|
+
);
|
|
571
|
+
const items = catalog.items.filter((item) => item.name !== SETUP).flatMap((item) => {
|
|
495
572
|
const score = rank(item);
|
|
496
573
|
return score === void 0 ? [] : [{ item, score }];
|
|
497
574
|
}).sort((a, b) => a.score - b.score).map(({ item }) => item);
|
|
@@ -501,19 +578,28 @@ async function search(cwd, registry, query) {
|
|
|
501
578
|
ok: true
|
|
502
579
|
};
|
|
503
580
|
}
|
|
504
|
-
function projectFramework(
|
|
505
|
-
|
|
506
|
-
if (configured) return configured;
|
|
581
|
+
function projectFramework(config, catalog) {
|
|
582
|
+
if (config) return config.framework;
|
|
507
583
|
const [only, ...others] = catalog.frameworks;
|
|
508
584
|
if (only && others.length === 0) return only;
|
|
509
585
|
throw new CliError(
|
|
510
586
|
`This release has several frameworks. Run \`${CLI_COMMAND} init\` first, so nxds knows which one your app uses.`
|
|
511
587
|
);
|
|
512
588
|
}
|
|
589
|
+
function projectReact(cwd) {
|
|
590
|
+
if (!existsSync5(path6.join(cwd, "package.json"))) return 19;
|
|
591
|
+
const project = readProject(cwd);
|
|
592
|
+
return "react" in project.dependencies ? reactMajor(project) : 19;
|
|
593
|
+
}
|
|
513
594
|
async function view(cwd, registry, name) {
|
|
514
|
-
const
|
|
595
|
+
const config = readConfig(cwd);
|
|
596
|
+
const react = projectReact(cwd);
|
|
597
|
+
const catalog = await appRelease(registry, config, react);
|
|
515
598
|
const item = findItem(catalog, name);
|
|
516
|
-
const variant =
|
|
599
|
+
const variant = variantFor(item, {
|
|
600
|
+
framework: projectFramework(config, catalog),
|
|
601
|
+
react
|
|
602
|
+
});
|
|
517
603
|
const packages = Object.entries(variant.packages.npm).map(
|
|
518
604
|
([pkg, range]) => `${pkg}@${range}`
|
|
519
605
|
);
|
|
@@ -573,13 +659,13 @@ function info(cwd) {
|
|
|
573
659
|
|
|
574
660
|
// src/commands/check.ts
|
|
575
661
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
576
|
-
import { existsSync as
|
|
577
|
-
import
|
|
578
|
-
import { satisfies as satisfies2 } from "semver";
|
|
662
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
|
|
663
|
+
import path9 from "path";
|
|
664
|
+
import { major as major2, satisfies as satisfies2 } from "semver";
|
|
579
665
|
|
|
580
666
|
// src/files.ts
|
|
581
|
-
import { existsSync as
|
|
582
|
-
import
|
|
667
|
+
import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
668
|
+
import path7 from "path";
|
|
583
669
|
import { merge } from "node-diff3";
|
|
584
670
|
function baseOf(previous, file) {
|
|
585
671
|
if (!previous) return void 0;
|
|
@@ -604,13 +690,13 @@ function hasConflictMarkers(content) {
|
|
|
604
690
|
}
|
|
605
691
|
function conflictedFiles(root, config, files) {
|
|
606
692
|
return [...files].filter((file) => {
|
|
607
|
-
const target =
|
|
608
|
-
return
|
|
693
|
+
const target = path7.join(root, config.dir, file);
|
|
694
|
+
return existsSync6(target) && hasConflictMarkers(readFileSync2(target, "utf8"));
|
|
609
695
|
});
|
|
610
696
|
}
|
|
611
697
|
function fileState(root, config, file, previous) {
|
|
612
|
-
const target =
|
|
613
|
-
if (!
|
|
698
|
+
const target = path7.join(root, config.dir, file.path);
|
|
699
|
+
if (!existsSync6(target)) return "missing";
|
|
614
700
|
const onDisk = readFileSync2(target, "utf8");
|
|
615
701
|
if (hasConflictMarkers(onDisk)) return "conflicted";
|
|
616
702
|
if (sameContent(onDisk, file.content)) return "current";
|
|
@@ -634,8 +720,8 @@ function writeFiles(root, config, files, previous, { overwrite, dryRun, version
|
|
|
634
720
|
};
|
|
635
721
|
const write = (file, content) => {
|
|
636
722
|
if (dryRun) return;
|
|
637
|
-
const target =
|
|
638
|
-
mkdirSync(
|
|
723
|
+
const target = path7.join(root, config.dir, file);
|
|
724
|
+
mkdirSync(path7.dirname(target), { recursive: true });
|
|
639
725
|
writeFileSync2(target, content);
|
|
640
726
|
};
|
|
641
727
|
for (const file of files) {
|
|
@@ -658,7 +744,7 @@ function writeFiles(root, config, files, previous, { overwrite, dryRun, version
|
|
|
658
744
|
result.skipped.push(file.path);
|
|
659
745
|
continue;
|
|
660
746
|
}
|
|
661
|
-
const target =
|
|
747
|
+
const target = path7.join(root, config.dir, file.path);
|
|
662
748
|
const ours = readFileSync2(target, "utf8");
|
|
663
749
|
const merged = mergeFile(ours, base, file.content, version);
|
|
664
750
|
if (sameContent(ours, merged.content)) {
|
|
@@ -671,7 +757,7 @@ function writeFiles(root, config, files, previous, { overwrite, dryRun, version
|
|
|
671
757
|
return result;
|
|
672
758
|
}
|
|
673
759
|
function relativeImport(fromFile, toFile) {
|
|
674
|
-
const relative =
|
|
760
|
+
const relative = path7.relative(path7.dirname(fromFile), toFile).split(path7.sep).join("/");
|
|
675
761
|
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
676
762
|
}
|
|
677
763
|
|
|
@@ -687,11 +773,11 @@ import {
|
|
|
687
773
|
isObjectProperty,
|
|
688
774
|
isSpreadElement
|
|
689
775
|
} from "@babel/types";
|
|
690
|
-
import { existsSync as
|
|
691
|
-
import
|
|
776
|
+
import { existsSync as existsSync7, readFileSync as readFileSync3 } from "fs";
|
|
777
|
+
import path8 from "path";
|
|
692
778
|
|
|
693
779
|
// src/setup/source-edit.ts
|
|
694
|
-
import { parse } from "@babel/parser";
|
|
780
|
+
import { parse as parse2 } from "@babel/parser";
|
|
695
781
|
import {
|
|
696
782
|
isImportDeclaration,
|
|
697
783
|
isJSXAttribute,
|
|
@@ -701,7 +787,7 @@ import {
|
|
|
701
787
|
} from "@babel/types";
|
|
702
788
|
function parseModule(source, file) {
|
|
703
789
|
try {
|
|
704
|
-
return
|
|
790
|
+
return parse2(source, {
|
|
705
791
|
sourceType: "module",
|
|
706
792
|
plugins: ["typescript", "jsx"]
|
|
707
793
|
}).program;
|
|
@@ -794,7 +880,7 @@ function tailwindPackages(setup, range) {
|
|
|
794
880
|
}
|
|
795
881
|
function appDir(project) {
|
|
796
882
|
const dir = APP_DIRS.find(
|
|
797
|
-
(candidate) =>
|
|
883
|
+
(candidate) => existsSync7(path8.join(project.root, candidate, "layout.tsx"))
|
|
798
884
|
);
|
|
799
885
|
if (!dir)
|
|
800
886
|
throw new CliError("No app/layout.tsx or src/app/layout.tsx found.");
|
|
@@ -810,7 +896,7 @@ function cssEntryPath(project) {
|
|
|
810
896
|
return `${entryDir(project)}/nxds.css`;
|
|
811
897
|
}
|
|
812
898
|
function read(project, file) {
|
|
813
|
-
return readFileSync3(
|
|
899
|
+
return readFileSync3(path8.join(project.root, file), "utf8");
|
|
814
900
|
}
|
|
815
901
|
function cssEntry(config, withPreflight) {
|
|
816
902
|
const tokens = relativeImport(config.css, `${config.dir}/tailwind/nxds.css`);
|
|
@@ -1021,9 +1107,9 @@ var NXDS_VITE_PLUGIN = {
|
|
|
1021
1107
|
var VITE_ALIAS = "{ '@': '/src' }";
|
|
1022
1108
|
function wireViteConfig(project, addTailwind, steps) {
|
|
1023
1109
|
const configFile = findFile(project, VITE_CONFIGS) ?? "vite.config.ts";
|
|
1024
|
-
const addScript = steps.has("add-head-script") && !
|
|
1110
|
+
const addScript = steps.has("add-head-script") && !existsSync7(path8.join(project.root, "nxds.vite.ts"));
|
|
1025
1111
|
const changes = addScript ? [{ path: "nxds.vite.ts", content: VITE_PLUGIN, created: true }] : [];
|
|
1026
|
-
const source =
|
|
1112
|
+
const source = existsSync7(path8.join(project.root, configFile)) ? read(project, configFile) : "";
|
|
1027
1113
|
const program2 = source ? parseModule(source, configFile) : void 0;
|
|
1028
1114
|
const object = program2 && configObject(program2);
|
|
1029
1115
|
const plugins = object && pluginsArray(object);
|
|
@@ -1184,7 +1270,7 @@ function wireNextTailwind(project) {
|
|
|
1184
1270
|
};
|
|
1185
1271
|
}
|
|
1186
1272
|
function createdUnlessExists(project, file, content) {
|
|
1187
|
-
if (
|
|
1273
|
+
if (existsSync7(path8.join(project.root, file))) return NO_CHANGES;
|
|
1188
1274
|
return { changes: [{ path: file, content, created: true }], manual: [] };
|
|
1189
1275
|
}
|
|
1190
1276
|
function planWiring(project, config, addTailwind, setupSteps) {
|
|
@@ -1221,9 +1307,10 @@ function planWiring(project, config, addTailwind, setupSteps) {
|
|
|
1221
1307
|
// src/commands/check.ts
|
|
1222
1308
|
async function diff(cwd, registry, names) {
|
|
1223
1309
|
const config = requireConfig(cwd);
|
|
1224
|
-
const
|
|
1310
|
+
const target = { ...config, react: reactMajor(readProject(cwd)) };
|
|
1311
|
+
const latest = await loadRelease(registry, "latest", target.react);
|
|
1225
1312
|
const previous = await fetchRelease(registry, config.version);
|
|
1226
|
-
const previousFiles = previous && releaseFiles(previous, config
|
|
1313
|
+
const previousFiles = previous && releaseFiles(previous, config);
|
|
1227
1314
|
const { known, removed } = splitInstalled(latest, config.items);
|
|
1228
1315
|
const items = resolveItems(
|
|
1229
1316
|
latest,
|
|
@@ -1248,7 +1335,7 @@ async function diff(cwd, registry, names) {
|
|
|
1248
1335
|
return `has your edits; ${update2} merges nxds's changes into it, ${overwrite} replaces it`;
|
|
1249
1336
|
}
|
|
1250
1337
|
const files = items.flatMap(
|
|
1251
|
-
(item) =>
|
|
1338
|
+
(item) => variantFor(item, target).files.map((file) => {
|
|
1252
1339
|
const state = fileState(cwd, config, file, previousFiles);
|
|
1253
1340
|
return {
|
|
1254
1341
|
item: item.name,
|
|
@@ -1286,14 +1373,14 @@ function tailwindMessage(project) {
|
|
|
1286
1373
|
return `tailwindcss ${declared}. nxds needs Tailwind CSS 4.`;
|
|
1287
1374
|
}
|
|
1288
1375
|
function read2(root, file) {
|
|
1289
|
-
if (!file || !
|
|
1290
|
-
return readFileSync4(
|
|
1376
|
+
if (!file || !existsSync8(path9.join(root, file))) return "";
|
|
1377
|
+
return readFileSync4(path9.join(root, file), "utf8");
|
|
1291
1378
|
}
|
|
1292
1379
|
function sourcesNxdsDir(config, css) {
|
|
1293
|
-
const entryDir2 =
|
|
1380
|
+
const entryDir2 = path9.posix.dirname(config.css);
|
|
1294
1381
|
return [...css.matchAll(/@source\s+(['"])(.*?)\1/g)].some(([, , source]) => {
|
|
1295
1382
|
const base = (source ?? "").split(/[*{[]/)[0] ?? "";
|
|
1296
|
-
const folder =
|
|
1383
|
+
const folder = path9.posix.join(entryDir2, base).replace(/\/+$/, "");
|
|
1297
1384
|
return folder === "." || /^\.\.(\/\.\.)*$/.test(folder) || config.dir === folder || config.dir.startsWith(`${folder}/`);
|
|
1298
1385
|
});
|
|
1299
1386
|
}
|
|
@@ -1307,10 +1394,42 @@ function unscannedReason(cwd, config, css) {
|
|
|
1307
1394
|
}
|
|
1308
1395
|
return null;
|
|
1309
1396
|
}
|
|
1397
|
+
function installedMajor(project, name) {
|
|
1398
|
+
const version = installedVersion(project, name);
|
|
1399
|
+
return version === null ? null : major2(version);
|
|
1400
|
+
}
|
|
1401
|
+
function reactChecks(project, config) {
|
|
1402
|
+
let react;
|
|
1403
|
+
try {
|
|
1404
|
+
react = reactMajor(project);
|
|
1405
|
+
} catch (error) {
|
|
1406
|
+
if (!(error instanceof CliError)) throw error;
|
|
1407
|
+
return [{ name: "react", ok: false, message: error.message }];
|
|
1408
|
+
}
|
|
1409
|
+
const checks = [
|
|
1410
|
+
{
|
|
1411
|
+
name: "react",
|
|
1412
|
+
ok: config.react === react,
|
|
1413
|
+
message: config.react === react ? `The files are for React ${react}, which this app uses` : `The files are for React ${config.react}, and this app uses React ${react}. Run \`${CLI_COMMAND} update\` to switch them.`
|
|
1414
|
+
}
|
|
1415
|
+
];
|
|
1416
|
+
const reactInstalled = installedMajor(project, "react");
|
|
1417
|
+
if (reactInstalled === null) return checks;
|
|
1418
|
+
for (const name of ["react-dom", "@types/react", "@types/react-dom"]) {
|
|
1419
|
+
if (name.startsWith("@types/") && !(name in project.dependencies)) continue;
|
|
1420
|
+
const installed = installedMajor(project, name);
|
|
1421
|
+
checks.push({
|
|
1422
|
+
name,
|
|
1423
|
+
ok: installed === reactInstalled,
|
|
1424
|
+
message: installed === null ? `${name} is not installed` : installed === reactInstalled ? `${name} ${installed} matches react ${reactInstalled}` : `${name} ${installed} is installed with react ${reactInstalled}. Install the same major as react.`
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
return checks;
|
|
1428
|
+
}
|
|
1310
1429
|
function packageProblems(project, config, items) {
|
|
1311
1430
|
const needed = Object.assign(
|
|
1312
1431
|
{},
|
|
1313
|
-
...items.map((item) =>
|
|
1432
|
+
...items.map((item) => variantFor(item, config).packages.npm)
|
|
1314
1433
|
);
|
|
1315
1434
|
return Object.entries(needed).flatMap(([name, range]) => {
|
|
1316
1435
|
if (!(name in project.dependencies))
|
|
@@ -1350,6 +1469,7 @@ async function doctor(cwd, registry) {
|
|
|
1350
1469
|
majorVersion(project, "tailwindcss") === 4,
|
|
1351
1470
|
tailwindMessage(project)
|
|
1352
1471
|
);
|
|
1472
|
+
checks.push(...reactChecks(project, config));
|
|
1353
1473
|
const css = read2(cwd, config.css);
|
|
1354
1474
|
const importsTokens = css.includes("tailwind/nxds.css");
|
|
1355
1475
|
check(
|
|
@@ -1365,7 +1485,7 @@ async function doctor(cwd, registry) {
|
|
|
1365
1485
|
);
|
|
1366
1486
|
const entry = project.setup === "next-app" ? `${entryDir(project)}/layout.tsx` : findFile(project, VITE_ENTRIES);
|
|
1367
1487
|
const entrySource = read2(cwd, entry);
|
|
1368
|
-
const cssFile =
|
|
1488
|
+
const cssFile = path9.basename(config.css);
|
|
1369
1489
|
const importsCss = entrySource.includes(cssFile);
|
|
1370
1490
|
check(
|
|
1371
1491
|
"css import",
|
|
@@ -1393,8 +1513,8 @@ async function doctor(cwd, registry) {
|
|
|
1393
1513
|
);
|
|
1394
1514
|
const items = resolveItems(catalog, config.framework, known);
|
|
1395
1515
|
const missingFiles = items.flatMap(
|
|
1396
|
-
(item) =>
|
|
1397
|
-
(file) => !
|
|
1516
|
+
(item) => variantFor(item, config).files.filter(
|
|
1517
|
+
(file) => !existsSync8(path9.join(cwd, config.dir, file.path))
|
|
1398
1518
|
).map((file) => `${item.name}: ${file.path}`)
|
|
1399
1519
|
);
|
|
1400
1520
|
check(
|
|
@@ -1406,7 +1526,7 @@ async function doctor(cwd, registry) {
|
|
|
1406
1526
|
cwd,
|
|
1407
1527
|
config,
|
|
1408
1528
|
items.flatMap(
|
|
1409
|
-
(item) =>
|
|
1529
|
+
(item) => variantFor(item, config).files.map((file) => file.path)
|
|
1410
1530
|
)
|
|
1411
1531
|
).map((file) => `${config.dir}/${file}`);
|
|
1412
1532
|
check(
|
|
@@ -1437,12 +1557,12 @@ function report(checks) {
|
|
|
1437
1557
|
}
|
|
1438
1558
|
|
|
1439
1559
|
// src/commands/install.ts
|
|
1440
|
-
import { existsSync as
|
|
1441
|
-
import
|
|
1560
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1561
|
+
import path11 from "path";
|
|
1442
1562
|
|
|
1443
1563
|
// src/install.ts
|
|
1444
|
-
import { existsSync as
|
|
1445
|
-
import
|
|
1564
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
1565
|
+
import path10 from "path";
|
|
1446
1566
|
function emptyInstallResult() {
|
|
1447
1567
|
return {
|
|
1448
1568
|
items: [],
|
|
@@ -1458,8 +1578,8 @@ function emptyInstallResult() {
|
|
|
1458
1578
|
}
|
|
1459
1579
|
function addCssImports(project, config, files, dryRun) {
|
|
1460
1580
|
if (files.length === 0) return [];
|
|
1461
|
-
const entry =
|
|
1462
|
-
const current =
|
|
1581
|
+
const entry = path10.join(project.root, config.css);
|
|
1582
|
+
const current = existsSync9(entry) ? readFileSync5(entry, "utf8") : "";
|
|
1463
1583
|
const added = [...new Set(files)].map((file) => cssImportLine(config, file)).filter((line) => !current.includes(line));
|
|
1464
1584
|
if (added.length > 0 && !dryRun) {
|
|
1465
1585
|
writeFileSync3(entry, `${current.trimEnd()}
|
|
@@ -1470,7 +1590,7 @@ ${added.join("\n")}
|
|
|
1470
1590
|
}
|
|
1471
1591
|
function installItems(catalog, project, config, names, previous, options) {
|
|
1472
1592
|
const items = resolveItems(catalog, config.framework, names);
|
|
1473
|
-
const variants = items.map((item) =>
|
|
1593
|
+
const variants = items.map((item) => variantFor(item, config));
|
|
1474
1594
|
const files = new Map(
|
|
1475
1595
|
variants.flatMap((variant) => variant.files).map((file) => [file.path, file])
|
|
1476
1596
|
);
|
|
@@ -1536,6 +1656,12 @@ function installLines(config, result, options) {
|
|
|
1536
1656
|
function settled(result) {
|
|
1537
1657
|
return result.skipped.length === 0 && result.conflicted.length === 0 && result.unresolved.length === 0;
|
|
1538
1658
|
}
|
|
1659
|
+
function assertFilesMatchReact(config, react) {
|
|
1660
|
+
if (config.react === react) return;
|
|
1661
|
+
throw new CliError(
|
|
1662
|
+
`nxds.json has React ${config.react} files, and this app uses React ${react}. Run \`${CLI_COMMAND} update\` to switch them, then run this command again.`
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1539
1665
|
function nxdsDir(dir) {
|
|
1540
1666
|
const normalized = relativePath(dir);
|
|
1541
1667
|
if (normalized) return normalized;
|
|
@@ -1568,11 +1694,17 @@ async function init(cwd, registry, options) {
|
|
|
1568
1694
|
"nxds components are TypeScript. Add a tsconfig.json to your app, then run init again."
|
|
1569
1695
|
);
|
|
1570
1696
|
}
|
|
1697
|
+
const react = reactMajor(project);
|
|
1571
1698
|
const addTailwind = !("tailwindcss" in project.dependencies);
|
|
1572
1699
|
if (!addTailwind) assertTailwind4(project);
|
|
1573
1700
|
const dir = options.dir ? nxdsDir(options.dir) : void 0;
|
|
1574
1701
|
const existing = readConfig(cwd);
|
|
1575
|
-
const catalog = await loadRelease(
|
|
1702
|
+
const catalog = await loadRelease(
|
|
1703
|
+
registry,
|
|
1704
|
+
existing?.version ?? "latest",
|
|
1705
|
+
react
|
|
1706
|
+
);
|
|
1707
|
+
if (existing) assertFilesMatchReact(existing, react);
|
|
1576
1708
|
if (existing && dir && dir !== existing.dir) {
|
|
1577
1709
|
throw new CliError(
|
|
1578
1710
|
`nxds is already set up in ${existing.dir}. Run init without --dir, or move the folder and change "dir" in nxds.json.`
|
|
@@ -1580,12 +1712,13 @@ async function init(cwd, registry, options) {
|
|
|
1580
1712
|
}
|
|
1581
1713
|
const config = existing ?? {
|
|
1582
1714
|
framework: "react",
|
|
1715
|
+
react,
|
|
1583
1716
|
version: catalog.version,
|
|
1584
1717
|
dir: dir ?? defaultDir(project),
|
|
1585
1718
|
css: cssEntryPath(project),
|
|
1586
1719
|
items: []
|
|
1587
1720
|
};
|
|
1588
|
-
const setup = setupVariant(catalog, config
|
|
1721
|
+
const setup = setupVariant(catalog, config);
|
|
1589
1722
|
const wiring = planWiring(project, config, addTailwind, setup.steps);
|
|
1590
1723
|
const tailwind = packagesToInstall(
|
|
1591
1724
|
project,
|
|
@@ -1604,8 +1737,8 @@ async function init(cwd, registry, options) {
|
|
|
1604
1737
|
);
|
|
1605
1738
|
if (!options.dryRun) {
|
|
1606
1739
|
for (const change of wiring.changes) {
|
|
1607
|
-
const target =
|
|
1608
|
-
mkdirSync2(
|
|
1740
|
+
const target = path11.join(project.root, change.path);
|
|
1741
|
+
mkdirSync2(path11.dirname(target), { recursive: true });
|
|
1609
1742
|
writeFileSync4(target, change.content);
|
|
1610
1743
|
}
|
|
1611
1744
|
writeConfig(cwd, config);
|
|
@@ -1616,6 +1749,7 @@ async function init(cwd, registry, options) {
|
|
|
1616
1749
|
return {
|
|
1617
1750
|
data: {
|
|
1618
1751
|
setup: project.setup,
|
|
1752
|
+
react: config.react,
|
|
1619
1753
|
version: config.version,
|
|
1620
1754
|
dir: config.dir,
|
|
1621
1755
|
css: config.css,
|
|
@@ -1627,7 +1761,7 @@ async function init(cwd, registry, options) {
|
|
|
1627
1761
|
manual: wiring.manual
|
|
1628
1762
|
},
|
|
1629
1763
|
lines: [
|
|
1630
|
-
`nxds ${config.version} in ${config.dir}`,
|
|
1764
|
+
`nxds ${config.version} in ${config.dir}, for React ${config.react}`,
|
|
1631
1765
|
...installLines(config, { ...installed, packages }, options),
|
|
1632
1766
|
...wiring.changes.map(
|
|
1633
1767
|
(change) => `${change.created ? createVerb : editVerb} ${change.path}`
|
|
@@ -1649,10 +1783,12 @@ function manualLines(manual) {
|
|
|
1649
1783
|
}
|
|
1650
1784
|
async function add(cwd, registry, names, options) {
|
|
1651
1785
|
const config = requireConfig(cwd);
|
|
1652
|
-
const
|
|
1786
|
+
const project = readProject(cwd);
|
|
1787
|
+
assertFilesMatchReact(config, reactMajor(project));
|
|
1788
|
+
const catalog = await loadRelease(registry, config.version, config.react);
|
|
1653
1789
|
const installed = installItems(
|
|
1654
1790
|
catalog,
|
|
1655
|
-
|
|
1791
|
+
project,
|
|
1656
1792
|
config,
|
|
1657
1793
|
names,
|
|
1658
1794
|
null,
|
|
@@ -1670,32 +1806,34 @@ async function add(cwd, registry, names, options) {
|
|
|
1670
1806
|
ok: settled(installed)
|
|
1671
1807
|
};
|
|
1672
1808
|
}
|
|
1673
|
-
function filesOf(catalog,
|
|
1809
|
+
function filesOf(catalog, target, names) {
|
|
1674
1810
|
return new Set(
|
|
1675
|
-
resolveItems(catalog, framework, names).flatMap(
|
|
1676
|
-
(item) =>
|
|
1811
|
+
resolveItems(catalog, target.framework, names).flatMap(
|
|
1812
|
+
(item) => variantFor(item, target).files.map((file) => file.path)
|
|
1677
1813
|
)
|
|
1678
1814
|
);
|
|
1679
1815
|
}
|
|
1680
1816
|
async function update(cwd, registry, options) {
|
|
1681
1817
|
const config = requireConfig(cwd);
|
|
1818
|
+
const project = readProject(cwd);
|
|
1682
1819
|
const from = config.version;
|
|
1683
|
-
const
|
|
1820
|
+
const fromReact = config.react;
|
|
1821
|
+
const react = reactMajor(project);
|
|
1822
|
+
const latest = await loadRelease(registry, "latest", react);
|
|
1684
1823
|
const { known, removed } = splitInstalled(latest, config.items);
|
|
1685
|
-
const current = filesOf(latest, config
|
|
1824
|
+
const current = filesOf(latest, { ...config, react }, known);
|
|
1686
1825
|
const previous = await fetchRelease(registry, from);
|
|
1687
|
-
const installedBefore = previous ? filesOf(
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
splitInstalled(previous, config.items).known
|
|
1691
|
-
) : /* @__PURE__ */ new Set();
|
|
1692
|
-
const unresolved = from === latest.version || options.overwrite ? [] : conflictedFiles(cwd, config, /* @__PURE__ */ new Set([...current, ...installedBefore]));
|
|
1826
|
+
const installedBefore = previous ? filesOf(previous, config, splitInstalled(previous, config.items).known) : /* @__PURE__ */ new Set();
|
|
1827
|
+
const unchanged = from === latest.version && fromReact === react;
|
|
1828
|
+
const unresolved = unchanged || options.overwrite ? [] : conflictedFiles(cwd, config, /* @__PURE__ */ new Set([...current, ...installedBefore]));
|
|
1693
1829
|
if (unresolved.length > 0) {
|
|
1694
1830
|
const data2 = {
|
|
1695
1831
|
...emptyInstallResult(),
|
|
1696
1832
|
dryRun: options.dryRun,
|
|
1697
1833
|
from,
|
|
1698
1834
|
version: from,
|
|
1835
|
+
fromReact,
|
|
1836
|
+
react: fromReact,
|
|
1699
1837
|
unresolved,
|
|
1700
1838
|
removed: [],
|
|
1701
1839
|
orphaned: []
|
|
@@ -1710,24 +1848,30 @@ async function update(cwd, registry, options) {
|
|
|
1710
1848
|
};
|
|
1711
1849
|
}
|
|
1712
1850
|
const orphaned = [...installedBefore].filter(
|
|
1713
|
-
(file) => !current.has(file) &&
|
|
1851
|
+
(file) => !current.has(file) && existsSync10(path11.join(cwd, config.dir, file))
|
|
1714
1852
|
);
|
|
1853
|
+
const previousFiles = previous && releaseFiles(previous, config);
|
|
1715
1854
|
config.items = known;
|
|
1716
1855
|
const installed = installItems(
|
|
1717
1856
|
latest,
|
|
1718
|
-
|
|
1719
|
-
config,
|
|
1857
|
+
project,
|
|
1858
|
+
{ ...config, react },
|
|
1720
1859
|
known,
|
|
1721
|
-
|
|
1860
|
+
previousFiles,
|
|
1722
1861
|
options
|
|
1723
1862
|
);
|
|
1724
|
-
const stays =
|
|
1725
|
-
if (!stays)
|
|
1863
|
+
const stays = !unchanged && installed.skipped.length > 0;
|
|
1864
|
+
if (!stays) {
|
|
1865
|
+
config.version = latest.version;
|
|
1866
|
+
config.react = react;
|
|
1867
|
+
}
|
|
1726
1868
|
if (!options.dryRun) writeConfig(cwd, config);
|
|
1727
1869
|
const data = {
|
|
1728
1870
|
dryRun: options.dryRun,
|
|
1729
1871
|
from,
|
|
1730
1872
|
version: config.version,
|
|
1873
|
+
fromReact,
|
|
1874
|
+
react: config.react,
|
|
1731
1875
|
...installed,
|
|
1732
1876
|
removed,
|
|
1733
1877
|
orphaned
|
|
@@ -1745,6 +1889,9 @@ async function update(cwd, registry, options) {
|
|
|
1745
1889
|
...orphaned.map(
|
|
1746
1890
|
(file) => `No installed item uses ${config.dir}/${file} any more. Delete it once nothing in your app imports it.`
|
|
1747
1891
|
),
|
|
1892
|
+
...fromReact !== react && !stays ? [
|
|
1893
|
+
`${options.dryRun ? "Would switch" : "Switched"} the files from React ${fromReact} to React ${react}.`
|
|
1894
|
+
] : [],
|
|
1748
1895
|
stays ? `nxds.json stays on ${from} until every file above is resolved or replaced.` : `nxds ${from === latest.version ? "is on" : `${options.dryRun ? "would move" : "moved"} from ${from} to`} ${latest.version}.`
|
|
1749
1896
|
],
|
|
1750
1897
|
ok: settled(installed)
|
|
@@ -1755,7 +1902,7 @@ async function update(cwd, registry, options) {
|
|
|
1755
1902
|
function context(command) {
|
|
1756
1903
|
const { cwd, registry } = command.optsWithGlobals();
|
|
1757
1904
|
return {
|
|
1758
|
-
cwd:
|
|
1905
|
+
cwd: path12.resolve(cwd ?? process.cwd()),
|
|
1759
1906
|
registry: resolveRegistry(registry)
|
|
1760
1907
|
};
|
|
1761
1908
|
}
|
|
@@ -1777,7 +1924,7 @@ function program(done, write) {
|
|
|
1777
1924
|
// Errors are printed once, by `main`, as text or JSON.
|
|
1778
1925
|
outputError: () => {
|
|
1779
1926
|
}
|
|
1780
|
-
}).exitOverride().name(CLI_COMMAND).description("Add nxds to your app.").version("0.
|
|
1927
|
+
}).exitOverride().name(CLI_COMMAND).description("Add nxds to your app.").version("0.3.0", "-v, --version", "print the version").helpOption("-h, --help", "show help").helpCommand("help [command]", "show help for a command").option("--json", "print machine-readable JSON").option("--cwd <path>", "run in another folder").addOption(
|
|
1781
1928
|
new Option(
|
|
1782
1929
|
"--registry <url|path>",
|
|
1783
1930
|
"where to get nxds releases (default: https://nxds.dev/r)"
|