nxds-cli 0.0.0 → 0.2.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 +636 -378
- package/package.json +15 -11
package/dist/index.js
CHANGED
|
@@ -1,27 +1,146 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
import
|
|
5
|
-
import
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command, CommanderError, Option } from "commander";
|
|
5
|
+
import path11 from "path";
|
|
6
|
+
|
|
7
|
+
// ../catalog/scripts/catalog-schema.ts
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { valid, validRange } from "semver";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
function relativePath(value) {
|
|
12
|
+
const normalized = path.posix.normalize(value.replaceAll("\\", "/")).replace(/\/+$/, "");
|
|
13
|
+
if ([...normalized].some((char) => char < " " || char === "\x7F") || path.posix.isAbsolute(normalized) || /^[A-Za-z]:/.test(normalized) || normalized === "." || normalized === ".." || normalized.startsWith("../")) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return normalized;
|
|
17
|
+
}
|
|
18
|
+
var relativePathSchema = z.string().refine(
|
|
19
|
+
(value) => relativePath(value) === value,
|
|
20
|
+
"must be a relative path that stays inside its folder"
|
|
21
|
+
);
|
|
22
|
+
var versionSchema = z.string().refine((value) => valid(value) === value, "must be a release version");
|
|
23
|
+
var rangeSchema = z.string().refine(
|
|
24
|
+
(value) => /^[ -~]+$/.test(value) && value === value.trim() && validRange(value) !== null,
|
|
25
|
+
"must be a semver range"
|
|
26
|
+
);
|
|
27
|
+
var npmNameSchema = z.string().regex(
|
|
28
|
+
/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/,
|
|
29
|
+
"must be an npm package name"
|
|
30
|
+
);
|
|
31
|
+
var frameworkSchema = z.enum(["react", "vue"]);
|
|
32
|
+
function isFramework(value) {
|
|
33
|
+
return frameworkSchema.safeParse(value).success;
|
|
34
|
+
}
|
|
35
|
+
function variantsSchema(variant) {
|
|
36
|
+
return z.object({
|
|
37
|
+
react: variant.optional(),
|
|
38
|
+
vue: variant.optional()
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
var catalogFileSchema = z.object({
|
|
42
|
+
/** Relative to the project's nxds folder. */
|
|
43
|
+
path: relativePathSchema,
|
|
44
|
+
content: z.string()
|
|
45
|
+
});
|
|
46
|
+
var setupStepSchema = z.enum([
|
|
47
|
+
"add-css-import",
|
|
48
|
+
"add-head-script",
|
|
49
|
+
"wrap-root-with-provider"
|
|
50
|
+
]);
|
|
51
|
+
var npmPackagesSchema = z.record(npmNameSchema, rangeSchema);
|
|
52
|
+
function variantWith(packages) {
|
|
53
|
+
return z.object({
|
|
54
|
+
/** Other items this one needs, installed first. */
|
|
55
|
+
items: z.array(z.string()),
|
|
56
|
+
packages,
|
|
57
|
+
files: z.array(catalogFileSchema),
|
|
58
|
+
/** Files in `files` the nxds CSS entry imports. */
|
|
59
|
+
css: z.array(relativePathSchema),
|
|
60
|
+
steps: z.array(setupStepSchema)
|
|
61
|
+
}).refine(
|
|
62
|
+
({ css, files }) => css.every((entry) => files.some((file) => file.path === entry)),
|
|
63
|
+
{ path: ["css"], message: "must name files in `files`" }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
var variantSchema = variantWith(z.object({ npm: npmPackagesSchema }));
|
|
67
|
+
var setupVariantSchema = variantWith(
|
|
68
|
+
z.object({
|
|
69
|
+
npm: npmPackagesSchema,
|
|
70
|
+
/** The Tailwind range `init` installs, with the app's build plugin, when the app has no Tailwind. */
|
|
71
|
+
tailwind: rangeSchema
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
var itemInfo = {
|
|
75
|
+
name: z.string(),
|
|
76
|
+
title: z.string(),
|
|
77
|
+
description: z.string(),
|
|
78
|
+
docs: z.string()
|
|
79
|
+
};
|
|
80
|
+
var setupItemSchema = z.object({
|
|
81
|
+
...itemInfo,
|
|
82
|
+
kind: z.literal("setup"),
|
|
83
|
+
variants: variantsSchema(setupVariantSchema)
|
|
84
|
+
});
|
|
85
|
+
var catalogItemSchema = z.discriminatedUnion("kind", [
|
|
86
|
+
setupItemSchema,
|
|
87
|
+
z.object({
|
|
88
|
+
...itemInfo,
|
|
89
|
+
kind: z.enum(["component", "block"]),
|
|
90
|
+
variants: variantsSchema(variantSchema)
|
|
91
|
+
})
|
|
92
|
+
]);
|
|
93
|
+
var CATALOG_SCHEMA = 1;
|
|
94
|
+
var catalogSchema = z.object({
|
|
95
|
+
schema: z.literal(CATALOG_SCHEMA),
|
|
96
|
+
/** The `@nxds/react` version this catalog was built from. */
|
|
97
|
+
version: versionSchema,
|
|
98
|
+
/** 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
|
+
frameworks: z.array(z.string()).transform((frameworks) => frameworks.filter(isFramework)),
|
|
100
|
+
items: z.array(catalogItemSchema)
|
|
101
|
+
});
|
|
102
|
+
function lines(issues, prefix) {
|
|
103
|
+
return issues.flatMap((issue) => {
|
|
104
|
+
const at = [...prefix, ...issue.path];
|
|
105
|
+
if (issue.code === "invalid_key") return lines(issue.issues, at);
|
|
106
|
+
return [
|
|
107
|
+
`${at.length > 0 ? at.map(String).join(".") : "(root)"}: ${issue.message}`
|
|
108
|
+
];
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function issueLines(error) {
|
|
112
|
+
return lines(error.issues, []);
|
|
113
|
+
}
|
|
6
114
|
|
|
7
115
|
// src/catalog.ts
|
|
8
|
-
import { existsSync
|
|
116
|
+
import { existsSync } from "fs";
|
|
9
117
|
import path2 from "path";
|
|
10
|
-
import {
|
|
118
|
+
import { EnvHttpProxyAgent, fetch } from "undici";
|
|
11
119
|
|
|
12
|
-
// src/
|
|
13
|
-
|
|
14
|
-
import path from "path";
|
|
15
|
-
import { valid } from "semver";
|
|
120
|
+
// src/cli-command.ts
|
|
121
|
+
var CLI_COMMAND = "npx nxds-cli@latest";
|
|
16
122
|
|
|
17
123
|
// src/errors.ts
|
|
18
124
|
import { readFileSync } from "fs";
|
|
125
|
+
var EXIT_CODES = {
|
|
126
|
+
ok: 0,
|
|
127
|
+
/** A network or registry failure, an invalid file, or a bug in nxds. */
|
|
128
|
+
error: 1,
|
|
129
|
+
/** A bad flag, unknown command or item, or a missing argument. */
|
|
130
|
+
usage: 2,
|
|
131
|
+
/** The command ran, and left something for the person to do: conflicts, kept files, manual steps or failed checks. */
|
|
132
|
+
attention: 3
|
|
133
|
+
};
|
|
19
134
|
var CliError = class extends Error {
|
|
135
|
+
exitCode = EXIT_CODES.error;
|
|
136
|
+
};
|
|
137
|
+
var UsageError = class extends CliError {
|
|
138
|
+
exitCode = EXIT_CODES.usage;
|
|
20
139
|
};
|
|
21
140
|
function readJson(file) {
|
|
22
|
-
const
|
|
141
|
+
const text = readFileSync(file, "utf8");
|
|
23
142
|
try {
|
|
24
|
-
return JSON.parse(
|
|
143
|
+
return JSON.parse(text);
|
|
25
144
|
} catch (error) {
|
|
26
145
|
throw new CliError(
|
|
27
146
|
`${file} is not valid JSON (${error.message}). Fix it, then run the command again.`
|
|
@@ -29,57 +148,11 @@ function readJson(file) {
|
|
|
29
148
|
}
|
|
30
149
|
}
|
|
31
150
|
|
|
32
|
-
// src/config.ts
|
|
33
|
-
var CONFIG_FILE = "nxds.json";
|
|
34
|
-
function configPath(root) {
|
|
35
|
-
return path.join(root, CONFIG_FILE);
|
|
36
|
-
}
|
|
37
|
-
function projectPath(value) {
|
|
38
|
-
const normalized = path.posix.normalize(value.replaceAll("\\", "/")).replace(/\/+$/, "");
|
|
39
|
-
if (path.posix.isAbsolute(normalized) || /^[A-Za-z]:/.test(normalized) || normalized === "." || normalized === ".." || normalized.startsWith("../")) {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
return normalized;
|
|
43
|
-
}
|
|
44
|
-
function invalidField(value) {
|
|
45
|
-
if (value.framework !== "react") return "framework";
|
|
46
|
-
if (typeof value.version !== "string" || valid(value.version) !== value.version)
|
|
47
|
-
return "version";
|
|
48
|
-
for (const field of ["dir", "css"]) {
|
|
49
|
-
const file = value[field];
|
|
50
|
-
if (typeof file !== "string" || projectPath(file) !== file) return field;
|
|
51
|
-
}
|
|
52
|
-
const { items } = value;
|
|
53
|
-
if (!Array.isArray(items) || !items.every((item) => typeof item === "string"))
|
|
54
|
-
return "items";
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
function readConfig(root) {
|
|
58
|
-
const file = configPath(root);
|
|
59
|
-
if (!existsSync(file)) return null;
|
|
60
|
-
const value = readJson(file);
|
|
61
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
62
|
-
throw new CliError(`${CONFIG_FILE} must be a JSON object.`);
|
|
63
|
-
}
|
|
64
|
-
const field = invalidField(value);
|
|
65
|
-
if (field === null) return value;
|
|
66
|
-
throw new CliError(
|
|
67
|
-
`${CONFIG_FILE} has a missing or invalid "${field}". Fix it, or delete ${CONFIG_FILE} and run \`nxds init\` again.`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
function requireConfig(root) {
|
|
71
|
-
const config = readConfig(root);
|
|
72
|
-
if (config) return config;
|
|
73
|
-
throw new CliError(`nxds is not set up here. Run \`nxds init\` first.`);
|
|
74
|
-
}
|
|
75
|
-
function writeConfig(root, config) {
|
|
76
|
-
writeFileSync(configPath(root), `${JSON.stringify(config, null, 2)}
|
|
77
|
-
`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
151
|
// src/catalog.ts
|
|
81
152
|
var SETUP = "setup";
|
|
82
153
|
var DEFAULT_REGISTRY = "https://nxds.dev/r";
|
|
154
|
+
var FETCH_TIMEOUT_MS = 3e4;
|
|
155
|
+
var dispatcher = new EnvHttpProxyAgent();
|
|
83
156
|
function isUrl(registry) {
|
|
84
157
|
return /^https?:\/\//.test(registry);
|
|
85
158
|
}
|
|
@@ -87,13 +160,26 @@ function resolveRegistry(option) {
|
|
|
87
160
|
if (!option) return DEFAULT_REGISTRY;
|
|
88
161
|
return isUrl(option) ? option.replace(/\/+$/, "") : path2.resolve(option);
|
|
89
162
|
}
|
|
163
|
+
function timeoutError(url, error) {
|
|
164
|
+
if (!(error instanceof Error) || error.name !== "TimeoutError") return null;
|
|
165
|
+
return new CliError(
|
|
166
|
+
`${url} did not answer within ${FETCH_TIMEOUT_MS / 1e3}s. Check your connection, then run the command again.`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
90
169
|
async function fetchJson(url) {
|
|
91
170
|
let response;
|
|
92
171
|
try {
|
|
93
|
-
response = await fetch(url
|
|
172
|
+
response = await fetch(url, {
|
|
173
|
+
dispatcher,
|
|
174
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
175
|
+
});
|
|
94
176
|
} catch (error) {
|
|
177
|
+
const timeout = timeoutError(url, error);
|
|
178
|
+
if (timeout) throw timeout;
|
|
179
|
+
const { message, cause } = error;
|
|
180
|
+
const reason = cause instanceof Error ? cause.message : message;
|
|
95
181
|
throw new CliError(
|
|
96
|
-
`Could not reach ${url} (${
|
|
182
|
+
`Could not reach ${url} (${reason}). Check your connection, or set HTTPS_PROXY if you are behind a proxy, then run the command again.`
|
|
97
183
|
);
|
|
98
184
|
}
|
|
99
185
|
if (response.status === 404) return null;
|
|
@@ -105,7 +191,7 @@ async function fetchJson(url) {
|
|
|
105
191
|
try {
|
|
106
192
|
return await response.json();
|
|
107
193
|
} catch (error) {
|
|
108
|
-
throw new CliError(
|
|
194
|
+
throw timeoutError(url, error) ?? new CliError(
|
|
109
195
|
`${url} is not valid JSON (${error.message}). Run the command again later.`
|
|
110
196
|
);
|
|
111
197
|
}
|
|
@@ -116,104 +202,45 @@ function registryLocation(registry, file) {
|
|
|
116
202
|
async function readRegistryFile(registry, file) {
|
|
117
203
|
const location = registryLocation(registry, file);
|
|
118
204
|
if (isUrl(registry)) return fetchJson(location);
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
var KINDS = /* @__PURE__ */ new Set(["setup", "component", "block"]);
|
|
122
|
-
var STEPS = new Set(
|
|
123
|
-
Object.keys({
|
|
124
|
-
"add-css-import": true,
|
|
125
|
-
"add-head-script": true,
|
|
126
|
-
"wrap-root-with-provider": true
|
|
127
|
-
})
|
|
128
|
-
);
|
|
129
|
-
var NPM_NAME = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
130
|
-
function isRange(value) {
|
|
131
|
-
return typeof value === "string" && /^[ -~]+$/.test(value) && value === value.trim() && validRange(value) !== null;
|
|
132
|
-
}
|
|
133
|
-
function isRecord(value) {
|
|
134
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
205
|
+
return existsSync(location) ? readJson(location) : null;
|
|
135
206
|
}
|
|
136
|
-
function
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
function fileProblem(file) {
|
|
140
|
-
if (!isRecord(file) || typeof file.content !== "string") {
|
|
141
|
-
return "has a file without content";
|
|
142
|
-
}
|
|
143
|
-
if (typeof file.path !== "string" || projectPath(file.path) !== file.path) {
|
|
144
|
-
return `has a file path outside the nxds folder: ${String(file.path)}`;
|
|
145
|
-
}
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
function variantProblem(variant, kind) {
|
|
149
|
-
if (!isRecord(variant)) return "has a variant that is not an object";
|
|
150
|
-
const { packages } = variant;
|
|
151
|
-
if (!isRecord(packages) || !isRecord(packages.npm) || !Object.entries(packages.npm).every(
|
|
152
|
-
([name, range]) => NPM_NAME.test(name) && isRange(range)
|
|
153
|
-
) || kind === "setup" && !isRange(packages.tailwind)) {
|
|
154
|
-
return 'has invalid "packages"';
|
|
155
|
-
}
|
|
156
|
-
for (const field of ["items", "css"]) {
|
|
157
|
-
if (!isStrings(variant[field])) return `has invalid "${field}"`;
|
|
158
|
-
}
|
|
159
|
-
const { steps } = variant;
|
|
160
|
-
if (!Array.isArray(steps) || !steps.every((step) => STEPS.has(step))) {
|
|
161
|
-
return 'has invalid "steps"';
|
|
162
|
-
}
|
|
163
|
-
if (!Array.isArray(variant.files)) return 'has invalid "files"';
|
|
164
|
-
return variant.files.map(fileProblem).find(Boolean) ?? null;
|
|
165
|
-
}
|
|
166
|
-
function itemProblem(item) {
|
|
167
|
-
if (!isRecord(item) || typeof item.name !== "string") {
|
|
168
|
-
return "has an item without a name";
|
|
169
|
-
}
|
|
170
|
-
const fields = ["title", "description", "docs"];
|
|
171
|
-
if (!KINDS.has(item.kind) || fields.some((field) => typeof item[field] !== "string") || !isRecord(item.variants)) {
|
|
172
|
-
return `"${item.name}" is not a catalog item`;
|
|
173
|
-
}
|
|
174
|
-
const problem = Object.values(item.variants).map((variant) => variantProblem(variant, item.kind)).find(Boolean);
|
|
175
|
-
return problem ? `"${item.name}" ${problem}` : null;
|
|
176
|
-
}
|
|
177
|
-
function releaseProblem(value, version) {
|
|
178
|
-
if (!isRecord(value)) return "it is not a JSON object";
|
|
179
|
-
if (value.schema !== 1) return 'its "schema" is not 1';
|
|
180
|
-
if (typeof value.version !== "string" || valid2(value.version) !== value.version) {
|
|
181
|
-
return 'its "version" is not a release version';
|
|
182
|
-
}
|
|
183
|
-
if (version !== "latest" && value.version !== version) {
|
|
184
|
-
return `it holds nxds ${value.version}`;
|
|
185
|
-
}
|
|
186
|
-
if (!isStrings(value.frameworks)) return 'its "frameworks" is invalid';
|
|
187
|
-
if (!Array.isArray(value.items)) return 'its "items" is invalid';
|
|
188
|
-
return value.items.map(itemProblem).find(Boolean) ?? null;
|
|
207
|
+
function isNewerSchema(value) {
|
|
208
|
+
return typeof value === "object" && value !== null && "schema" in value && typeof value.schema === "number" && value.schema > CATALOG_SCHEMA;
|
|
189
209
|
}
|
|
190
210
|
async function fetchRelease(registry, version) {
|
|
191
211
|
const file = `${version}.json`;
|
|
192
212
|
const value = await readRegistryFile(registry, file);
|
|
193
213
|
if (value === null) return null;
|
|
194
|
-
if (
|
|
214
|
+
if (isNewerSchema(value)) {
|
|
215
|
+
const release = version === "latest" ? "The latest nxds release" : `nxds ${version}`;
|
|
195
216
|
throw new CliError(
|
|
196
|
-
|
|
217
|
+
`${release} needs a newer nxds-cli than ${"0.2.0"}. Run \`${CLI_COMMAND}\` to get the newest one.`
|
|
197
218
|
);
|
|
198
219
|
}
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
220
|
+
const invalid = (problems) => new CliError(
|
|
221
|
+
`${registryLocation(registry, file)} is not a valid nxds release:
|
|
222
|
+
${problems.map((line) => ` ${line}`).join("\n")}
|
|
223
|
+
Check \`--registry\`, or run the command again later.`
|
|
203
224
|
);
|
|
225
|
+
const parsed = catalogSchema.safeParse(value);
|
|
226
|
+
if (!parsed.success) throw invalid(issueLines(parsed.error));
|
|
227
|
+
if (version !== "latest" && parsed.data.version !== version) {
|
|
228
|
+
throw invalid([`version: it holds nxds ${parsed.data.version}`]);
|
|
229
|
+
}
|
|
230
|
+
return parsed.data;
|
|
204
231
|
}
|
|
205
232
|
async function loadRelease(registry, version) {
|
|
206
233
|
const catalog = await fetchRelease(registry, version);
|
|
207
234
|
if (catalog) return catalog;
|
|
208
235
|
throw new CliError(
|
|
209
|
-
version === "latest" ? `${registry} has no nxds release.` : `${registry} has no nxds ${version}, the release this app is on. Run
|
|
236
|
+
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.`
|
|
210
237
|
);
|
|
211
238
|
}
|
|
212
239
|
function findItem(catalog, name) {
|
|
213
240
|
const item = catalog.items.find((candidate) => candidate.name === name);
|
|
214
241
|
if (item) return item;
|
|
215
|
-
throw new
|
|
216
|
-
`nxds ${catalog.version} has no item named "${name}". Run
|
|
242
|
+
throw new UsageError(
|
|
243
|
+
`nxds ${catalog.version} has no item named "${name}". Run \`${CLI_COMMAND} search ${name}\` to find the right name, or \`${CLI_COMMAND} update\` if it's newer.`
|
|
217
244
|
);
|
|
218
245
|
}
|
|
219
246
|
function variantOf(item, framework) {
|
|
@@ -258,15 +285,60 @@ function splitInstalled(catalog, installed) {
|
|
|
258
285
|
};
|
|
259
286
|
}
|
|
260
287
|
|
|
288
|
+
// src/config.ts
|
|
289
|
+
import { existsSync as existsSync2, writeFileSync } from "fs";
|
|
290
|
+
import path3 from "path";
|
|
291
|
+
import { z as z2 } from "zod";
|
|
292
|
+
var CONFIG_FILE = "nxds.json";
|
|
293
|
+
var configSchema = z2.looseObject({
|
|
294
|
+
/** The CLI installs React only. */
|
|
295
|
+
framework: z2.literal("react"),
|
|
296
|
+
/** The nxds release every installed file comes from. */
|
|
297
|
+
version: versionSchema,
|
|
298
|
+
/** The nxds folder, relative to the project root. */
|
|
299
|
+
dir: relativePathSchema,
|
|
300
|
+
/** The nxds CSS entry, relative to the project root. */
|
|
301
|
+
css: relativePathSchema,
|
|
302
|
+
/** Installed items, in install order. */
|
|
303
|
+
items: z2.array(z2.string())
|
|
304
|
+
});
|
|
305
|
+
function configPath(root) {
|
|
306
|
+
return path3.join(root, CONFIG_FILE);
|
|
307
|
+
}
|
|
308
|
+
function readConfig(root) {
|
|
309
|
+
const file = configPath(root);
|
|
310
|
+
if (!existsSync2(file)) return null;
|
|
311
|
+
const parsed = configSchema.safeParse(readJson(file));
|
|
312
|
+
if (parsed.success) return parsed.data;
|
|
313
|
+
throw new CliError(
|
|
314
|
+
`${CONFIG_FILE} is not valid:
|
|
315
|
+
${issueLines(parsed.error).map((line) => ` ${line}`).join(
|
|
316
|
+
"\n"
|
|
317
|
+
)}
|
|
318
|
+
Fix it, or delete ${CONFIG_FILE} and run \`${CLI_COMMAND} init\` again.`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
function requireConfig(root) {
|
|
322
|
+
const config = readConfig(root);
|
|
323
|
+
if (config) return config;
|
|
324
|
+
throw new CliError(
|
|
325
|
+
`nxds is not set up here. Run \`${CLI_COMMAND} init\` first.`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
function writeConfig(root, config) {
|
|
329
|
+
writeFileSync(configPath(root), `${JSON.stringify(config, null, 2)}
|
|
330
|
+
`);
|
|
331
|
+
}
|
|
332
|
+
|
|
261
333
|
// src/project.ts
|
|
262
334
|
import { existsSync as existsSync4 } from "fs";
|
|
263
|
-
import
|
|
335
|
+
import path5 from "path";
|
|
264
336
|
import { major, minVersion as minVersion2, validRange as validRange3 } from "semver";
|
|
265
337
|
|
|
266
338
|
// src/packages.ts
|
|
267
339
|
import { spawnSync } from "child_process";
|
|
268
340
|
import { existsSync as existsSync3 } from "fs";
|
|
269
|
-
import
|
|
341
|
+
import path4 from "path";
|
|
270
342
|
import { minVersion, satisfies, validRange as validRange2 } from "semver";
|
|
271
343
|
var ADD_COMMAND = {
|
|
272
344
|
npm: ["install"],
|
|
@@ -287,11 +359,11 @@ function packagesToInstall(project, packages) {
|
|
|
287
359
|
function installedVersion(project, name) {
|
|
288
360
|
let dir = project.root;
|
|
289
361
|
for (; ; ) {
|
|
290
|
-
const manifest =
|
|
362
|
+
const manifest = path4.join(dir, "node_modules", name, "package.json");
|
|
291
363
|
if (existsSync3(manifest)) {
|
|
292
364
|
return readJson(manifest).version ?? null;
|
|
293
365
|
}
|
|
294
|
-
const parent =
|
|
366
|
+
const parent = path4.dirname(dir);
|
|
295
367
|
if (parent === dir) return null;
|
|
296
368
|
dir = parent;
|
|
297
369
|
}
|
|
@@ -307,11 +379,11 @@ function installPackages(project, specs, { dev }) {
|
|
|
307
379
|
cwd: project.root,
|
|
308
380
|
stdio: ["ignore", 2, 2]
|
|
309
381
|
};
|
|
310
|
-
const
|
|
382
|
+
const run = process.platform === "win32" ? spawnSync(
|
|
311
383
|
[project.packageManager, ...args.map((arg) => `"${arg}"`)].join(" "),
|
|
312
384
|
{ ...options, shell: true }
|
|
313
385
|
) : spawnSync(project.packageManager, args, options);
|
|
314
|
-
if (
|
|
386
|
+
if (run.status !== 0) {
|
|
315
387
|
throw new CliError(
|
|
316
388
|
`\`${project.packageManager} ${args.join(" ")}\` failed. Fix the error above, then run the same nxds command again.`
|
|
317
389
|
);
|
|
@@ -338,7 +410,7 @@ var VITE_CONFIGS = [
|
|
|
338
410
|
"vite.config.js"
|
|
339
411
|
];
|
|
340
412
|
function firstExisting(root, candidates) {
|
|
341
|
-
return candidates.find((file) => existsSync4(
|
|
413
|
+
return candidates.find((file) => existsSync4(path5.join(root, file)));
|
|
342
414
|
}
|
|
343
415
|
function detectSetup(root, dependencies) {
|
|
344
416
|
if ("next" in dependencies && firstExisting(
|
|
@@ -353,10 +425,10 @@ function detectSetup(root, dependencies) {
|
|
|
353
425
|
return null;
|
|
354
426
|
}
|
|
355
427
|
function readProject(root) {
|
|
356
|
-
const manifestPath =
|
|
428
|
+
const manifestPath = path5.join(root, "package.json");
|
|
357
429
|
if (!existsSync4(manifestPath)) {
|
|
358
430
|
throw new CliError(
|
|
359
|
-
`No package.json in ${root}. Run
|
|
431
|
+
`No package.json in ${root}. Run \`${CLI_COMMAND}\` from your app's root folder, or pass --cwd.`
|
|
360
432
|
);
|
|
361
433
|
}
|
|
362
434
|
const manifest = readJson(manifestPath);
|
|
@@ -367,9 +439,9 @@ function readProject(root) {
|
|
|
367
439
|
return {
|
|
368
440
|
root,
|
|
369
441
|
setup: detectSetup(root, dependencies),
|
|
370
|
-
packageManager: LOCKFILES.find(([file]) => existsSync4(
|
|
371
|
-
usesSrc: existsSync4(
|
|
372
|
-
typescript: existsSync4(
|
|
442
|
+
packageManager: LOCKFILES.find(([file]) => existsSync4(path5.join(root, file)))?.[1] ?? "npm",
|
|
443
|
+
usesSrc: existsSync4(path5.join(root, "src")),
|
|
444
|
+
typescript: existsSync4(path5.join(root, "tsconfig.json")),
|
|
373
445
|
dependencies
|
|
374
446
|
};
|
|
375
447
|
}
|
|
@@ -385,7 +457,6 @@ function majorVersion(project, name) {
|
|
|
385
457
|
}
|
|
386
458
|
|
|
387
459
|
// src/commands/browse.ts
|
|
388
|
-
var KINDS2 = ["component", "block"];
|
|
389
460
|
function appRelease(cwd, registry) {
|
|
390
461
|
return loadRelease(registry, readConfig(cwd)?.version ?? "latest");
|
|
391
462
|
}
|
|
@@ -397,8 +468,8 @@ function summary(item) {
|
|
|
397
468
|
description: item.description
|
|
398
469
|
};
|
|
399
470
|
}
|
|
400
|
-
function firstSentence(
|
|
401
|
-
return
|
|
471
|
+
function firstSentence(text) {
|
|
472
|
+
return text.split(/(?<=\.)\s/)[0]?.replaceAll("`", "") ?? text;
|
|
402
473
|
}
|
|
403
474
|
function summaryLines(items) {
|
|
404
475
|
const width = Math.max(...items.map((item) => item.name.length));
|
|
@@ -407,17 +478,12 @@ function summaryLines(items) {
|
|
|
407
478
|
);
|
|
408
479
|
}
|
|
409
480
|
async function list(cwd, registry, kind) {
|
|
410
|
-
if (kind !== void 0 && !KINDS2.includes(kind)) {
|
|
411
|
-
throw new CliError(`--kind takes ${KINDS2.join(" or ")}.`);
|
|
412
|
-
}
|
|
413
481
|
const items = (await appRelease(cwd, registry)).items.filter(
|
|
414
482
|
(item) => item.name !== SETUP && (kind === void 0 || item.kind === kind)
|
|
415
483
|
);
|
|
416
484
|
return { data: items.map(summary), lines: summaryLines(items), ok: true };
|
|
417
485
|
}
|
|
418
486
|
async function search(cwd, registry, query) {
|
|
419
|
-
if (!query)
|
|
420
|
-
throw new CliError("Give a search term, such as `nxds search date`.");
|
|
421
487
|
const needle = query.toLowerCase();
|
|
422
488
|
const rank = (item) => {
|
|
423
489
|
if (item.name.includes(needle)) return 0;
|
|
@@ -441,11 +507,10 @@ function projectFramework(cwd, catalog) {
|
|
|
441
507
|
const [only, ...others] = catalog.frameworks;
|
|
442
508
|
if (only && others.length === 0) return only;
|
|
443
509
|
throw new CliError(
|
|
444
|
-
|
|
510
|
+
`This release has several frameworks. Run \`${CLI_COMMAND} init\` first, so nxds knows which one your app uses.`
|
|
445
511
|
);
|
|
446
512
|
}
|
|
447
513
|
async function view(cwd, registry, name) {
|
|
448
|
-
if (!name) throw new CliError("Name an item, such as `nxds view button`.");
|
|
449
514
|
const catalog = await appRelease(cwd, registry);
|
|
450
515
|
const item = findItem(catalog, name);
|
|
451
516
|
const variant = variantOf(item, projectFramework(cwd, catalog));
|
|
@@ -497,7 +562,7 @@ function info(cwd) {
|
|
|
497
562
|
`Package manager: ${project.packageManager}`,
|
|
498
563
|
`TypeScript: ${project.typescript ? "yes" : "no"}`,
|
|
499
564
|
`Tailwind: ${project.dependencies.tailwindcss ?? "not installed"}`,
|
|
500
|
-
config ? `nxds: ${config.version} in ${config.dir}, CSS entry ${config.css}` :
|
|
565
|
+
config ? `nxds: ${config.version} in ${config.dir}, CSS entry ${config.css}` : `nxds: not set up. Run \`${CLI_COMMAND} init\`.`,
|
|
501
566
|
...config && config.items.length > 0 ? [
|
|
502
567
|
`Installed: ${config.items.filter((item) => item !== SETUP).join(", ") || "setup only"}`
|
|
503
568
|
] : []
|
|
@@ -509,42 +574,104 @@ function info(cwd) {
|
|
|
509
574
|
// src/commands/check.ts
|
|
510
575
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
511
576
|
import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
|
|
512
|
-
import
|
|
577
|
+
import path8 from "path";
|
|
513
578
|
import { satisfies as satisfies2 } from "semver";
|
|
514
579
|
|
|
515
580
|
// src/files.ts
|
|
516
581
|
import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
517
|
-
import
|
|
518
|
-
|
|
582
|
+
import path6 from "path";
|
|
583
|
+
import { merge } from "node-diff3";
|
|
584
|
+
function baseOf(previous, file) {
|
|
585
|
+
if (!previous) return void 0;
|
|
586
|
+
return previous.get(file) ?? "";
|
|
587
|
+
}
|
|
588
|
+
function withLf(content) {
|
|
589
|
+
return `${content.replace(/\r\n?/g, "\n").replace(/\n+$/, "")}
|
|
590
|
+
`;
|
|
591
|
+
}
|
|
592
|
+
function normalize(content) {
|
|
593
|
+
const lines2 = withLf(content).split("\n").map((line) => line.trimEnd());
|
|
594
|
+
return `${lines2.join("\n").trimEnd()}
|
|
595
|
+
`;
|
|
596
|
+
}
|
|
597
|
+
function sameContent(a, b) {
|
|
598
|
+
return b !== void 0 && normalize(a) === normalize(b);
|
|
599
|
+
}
|
|
600
|
+
var OURS_LABEL = "your file";
|
|
601
|
+
var CONFLICT_START = `<<<<<<< ${OURS_LABEL}`;
|
|
602
|
+
function hasConflictMarkers(content) {
|
|
603
|
+
return content.split(/\r?\n/).includes(CONFLICT_START);
|
|
604
|
+
}
|
|
605
|
+
function conflictedFiles(root, config, files) {
|
|
606
|
+
return [...files].filter((file) => {
|
|
607
|
+
const target = path6.join(root, config.dir, file);
|
|
608
|
+
return existsSync5(target) && hasConflictMarkers(readFileSync2(target, "utf8"));
|
|
609
|
+
});
|
|
610
|
+
}
|
|
519
611
|
function fileState(root, config, file, previous) {
|
|
520
|
-
const target =
|
|
612
|
+
const target = path6.join(root, config.dir, file.path);
|
|
521
613
|
if (!existsSync5(target)) return "missing";
|
|
522
614
|
const onDisk = readFileSync2(target, "utf8");
|
|
523
|
-
if (onDisk
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
615
|
+
if (hasConflictMarkers(onDisk)) return "conflicted";
|
|
616
|
+
if (sameContent(onDisk, file.content)) return "current";
|
|
617
|
+
return sameContent(onDisk, previous?.get(file.path)) ? "outdated" : "changed";
|
|
618
|
+
}
|
|
619
|
+
function mergeFile(ours, base, theirs, version) {
|
|
620
|
+
const lines2 = (content) => withLf(content).split("\n");
|
|
621
|
+
const { conflict, result } = merge(lines2(ours), lines2(base), lines2(theirs), {
|
|
622
|
+
label: { a: OURS_LABEL, b: `nxds ${version}` }
|
|
623
|
+
});
|
|
624
|
+
return { content: result.join("\n"), conflict };
|
|
625
|
+
}
|
|
626
|
+
function writeFiles(root, config, files, previous, { overwrite, dryRun, version }) {
|
|
627
|
+
const result = {
|
|
628
|
+
written: [],
|
|
629
|
+
unchanged: [],
|
|
630
|
+
merged: [],
|
|
631
|
+
conflicted: [],
|
|
632
|
+
unresolved: [],
|
|
633
|
+
skipped: []
|
|
634
|
+
};
|
|
635
|
+
const write = (file, content) => {
|
|
636
|
+
if (dryRun) return;
|
|
637
|
+
const target = path6.join(root, config.dir, file);
|
|
638
|
+
mkdirSync(path6.dirname(target), { recursive: true });
|
|
639
|
+
writeFileSync2(target, content);
|
|
640
|
+
};
|
|
528
641
|
for (const file of files) {
|
|
529
642
|
const state = fileState(root, config, file, previous);
|
|
530
643
|
if (state === "current") {
|
|
531
644
|
result.unchanged.push(file.path);
|
|
532
645
|
continue;
|
|
533
646
|
}
|
|
534
|
-
if (state === "
|
|
647
|
+
if (overwrite || state === "missing" || state === "outdated") {
|
|
648
|
+
result.written.push(file.path);
|
|
649
|
+
write(file.path, file.content);
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
if (state === "conflicted") {
|
|
653
|
+
result.unresolved.push(file.path);
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
const base = baseOf(previous, file.path);
|
|
657
|
+
if (base === void 0) {
|
|
535
658
|
result.skipped.push(file.path);
|
|
536
659
|
continue;
|
|
537
660
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
661
|
+
const target = path6.join(root, config.dir, file.path);
|
|
662
|
+
const ours = readFileSync2(target, "utf8");
|
|
663
|
+
const merged = mergeFile(ours, base, file.content, version);
|
|
664
|
+
if (sameContent(ours, merged.content)) {
|
|
665
|
+
result.unchanged.push(file.path);
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
result[merged.conflict ? "conflicted" : "merged"].push(file.path);
|
|
669
|
+
write(file.path, merged.content);
|
|
543
670
|
}
|
|
544
671
|
return result;
|
|
545
672
|
}
|
|
546
673
|
function relativeImport(fromFile, toFile) {
|
|
547
|
-
const relative =
|
|
674
|
+
const relative = path6.relative(path6.dirname(fromFile), toFile).split(path6.sep).join("/");
|
|
548
675
|
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
549
676
|
}
|
|
550
677
|
|
|
@@ -561,7 +688,7 @@ import {
|
|
|
561
688
|
isSpreadElement
|
|
562
689
|
} from "@babel/types";
|
|
563
690
|
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
564
|
-
import
|
|
691
|
+
import path7 from "path";
|
|
565
692
|
|
|
566
693
|
// src/setup/source-edit.ts
|
|
567
694
|
import { parse } from "@babel/parser";
|
|
@@ -611,24 +738,24 @@ function findJsx(root, name) {
|
|
|
611
738
|
}
|
|
612
739
|
function applyInsertions(source, insertions) {
|
|
613
740
|
return [...insertions].sort((a, b) => b.at - a.at).reduce(
|
|
614
|
-
(
|
|
741
|
+
(text, { at, text: inserted }) => text.slice(0, at) + inserted + text.slice(at),
|
|
615
742
|
source
|
|
616
743
|
);
|
|
617
744
|
}
|
|
618
|
-
function importInsertion(source,
|
|
619
|
-
const imports =
|
|
745
|
+
function importInsertion(source, program2, lines2) {
|
|
746
|
+
const imports = program2.body.filter((node) => isImportDeclaration(node));
|
|
620
747
|
const first = imports[0];
|
|
621
748
|
const quote = first && source[startOf(first.source)];
|
|
622
749
|
const semicolons = !first || source[endOf(first) - 1] === ";";
|
|
623
|
-
const
|
|
750
|
+
const text = lines2.map((line) => quote === '"' ? line.replaceAll("'", '"') : line).map((line) => semicolons ? line : line.replace(/;$/, "")).join("\n");
|
|
624
751
|
const lastImport = imports.at(-1);
|
|
625
752
|
if (lastImport) return { at: endOf(lastImport), text: `
|
|
626
|
-
${
|
|
627
|
-
const lastDirective =
|
|
753
|
+
${text}` };
|
|
754
|
+
const lastDirective = program2.directives.at(-1);
|
|
628
755
|
if (lastDirective) return { at: endOf(lastDirective), text: `
|
|
629
756
|
|
|
630
|
-
${
|
|
631
|
-
return { at: 0, text: `${
|
|
757
|
+
${text}` };
|
|
758
|
+
return { at: 0, text: `${text}
|
|
632
759
|
|
|
633
760
|
` };
|
|
634
761
|
}
|
|
@@ -667,7 +794,7 @@ function tailwindPackages(setup, range) {
|
|
|
667
794
|
}
|
|
668
795
|
function appDir(project) {
|
|
669
796
|
const dir = APP_DIRS.find(
|
|
670
|
-
(candidate) => existsSync6(
|
|
797
|
+
(candidate) => existsSync6(path7.join(project.root, candidate, "layout.tsx"))
|
|
671
798
|
);
|
|
672
799
|
if (!dir)
|
|
673
800
|
throw new CliError("No app/layout.tsx or src/app/layout.tsx found.");
|
|
@@ -683,7 +810,7 @@ function cssEntryPath(project) {
|
|
|
683
810
|
return `${entryDir(project)}/nxds.css`;
|
|
684
811
|
}
|
|
685
812
|
function read(project, file) {
|
|
686
|
-
return readFileSync3(
|
|
813
|
+
return readFileSync3(path7.join(project.root, file), "utf8");
|
|
687
814
|
}
|
|
688
815
|
function cssEntry(config, withPreflight) {
|
|
689
816
|
const tokens = relativeImport(config.css, `${config.dir}/tailwind/nxds.css`);
|
|
@@ -722,8 +849,8 @@ function wireNextLayout(project, config, steps) {
|
|
|
722
849
|
const layout = `${appDir(project)}/layout.tsx`;
|
|
723
850
|
const source = read(project, layout);
|
|
724
851
|
if (/NxdsAppearance(Provider|Script)/.test(source)) return NO_CHANGES;
|
|
725
|
-
const
|
|
726
|
-
const html = findJsx(
|
|
852
|
+
const program2 = parseModule(source, layout);
|
|
853
|
+
const html = findJsx(program2, "html");
|
|
727
854
|
const body = html && findJsx(html, "body");
|
|
728
855
|
const head = html && findJsx(html, "head");
|
|
729
856
|
if (!html || !body?.closingElement || head && !head.closingElement) {
|
|
@@ -785,15 +912,15 @@ ${htmlIndent}${step}</head>`
|
|
|
785
912
|
imports.push("import { defaultAppearance } from './appearance-state';");
|
|
786
913
|
}
|
|
787
914
|
if (imports.length === 0) return NO_CHANGES;
|
|
788
|
-
insertions.push(importInsertion(source,
|
|
915
|
+
insertions.push(importInsertion(source, program2, imports));
|
|
789
916
|
return {
|
|
790
917
|
changes: [editedFile(layout, applyInsertions(source, insertions))],
|
|
791
918
|
manual: []
|
|
792
919
|
};
|
|
793
920
|
}
|
|
794
|
-
function renderedElement(
|
|
921
|
+
function renderedElement(program2) {
|
|
795
922
|
const call = findNode(
|
|
796
|
-
|
|
923
|
+
program2,
|
|
797
924
|
(node) => isCallExpression(node) && isMemberExpression(node.callee) && isIdentifier(node.callee.property, { name: "render" }) && isJSXElement2(node.arguments[0])
|
|
798
925
|
);
|
|
799
926
|
return call?.arguments[0];
|
|
@@ -803,12 +930,12 @@ function wireViteEntry(project, config, steps) {
|
|
|
803
930
|
if (!entry) throw new CliError("No src/main.tsx found.");
|
|
804
931
|
const source = read(project, entry);
|
|
805
932
|
if (source.includes("NxdsAppearanceProvider")) return NO_CHANGES;
|
|
806
|
-
const
|
|
933
|
+
const program2 = parseModule(source, entry);
|
|
807
934
|
const imports = [];
|
|
808
935
|
const insertions = [];
|
|
809
936
|
if (steps.has("add-css-import")) imports.push("import './nxds.css';");
|
|
810
937
|
if (steps.has("wrap-root-with-provider")) {
|
|
811
|
-
const app = renderedElement(
|
|
938
|
+
const app = renderedElement(program2);
|
|
812
939
|
if (!app) {
|
|
813
940
|
return {
|
|
814
941
|
changes: [],
|
|
@@ -836,7 +963,7 @@ ${indent}</NxdsAppearanceProvider>` }
|
|
|
836
963
|
);
|
|
837
964
|
}
|
|
838
965
|
if (imports.length === 0) return NO_CHANGES;
|
|
839
|
-
insertions.push(importInsertion(source,
|
|
966
|
+
insertions.push(importInsertion(source, program2, imports));
|
|
840
967
|
return {
|
|
841
968
|
changes: [editedFile(entry, applyInsertions(source, insertions))],
|
|
842
969
|
manual: []
|
|
@@ -863,8 +990,8 @@ export function nxdsAppearance(): Plugin {
|
|
|
863
990
|
};
|
|
864
991
|
}
|
|
865
992
|
`;
|
|
866
|
-
function configObject(
|
|
867
|
-
const declaration =
|
|
993
|
+
function configObject(program2) {
|
|
994
|
+
const declaration = program2.body.find(
|
|
868
995
|
(node) => isExportDefaultDeclaration(node)
|
|
869
996
|
)?.declaration;
|
|
870
997
|
if (isObjectExpression(declaration)) return declaration;
|
|
@@ -894,11 +1021,11 @@ var NXDS_VITE_PLUGIN = {
|
|
|
894
1021
|
var VITE_ALIAS = "{ '@': '/src' }";
|
|
895
1022
|
function wireViteConfig(project, addTailwind, steps) {
|
|
896
1023
|
const configFile = findFile(project, VITE_CONFIGS) ?? "vite.config.ts";
|
|
897
|
-
const addScript = steps.has("add-head-script") && !existsSync6(
|
|
1024
|
+
const addScript = steps.has("add-head-script") && !existsSync6(path7.join(project.root, "nxds.vite.ts"));
|
|
898
1025
|
const changes = addScript ? [{ path: "nxds.vite.ts", content: VITE_PLUGIN, created: true }] : [];
|
|
899
|
-
const source = existsSync6(
|
|
900
|
-
const
|
|
901
|
-
const object =
|
|
1026
|
+
const source = existsSync6(path7.join(project.root, configFile)) ? read(project, configFile) : "";
|
|
1027
|
+
const program2 = source ? parseModule(source, configFile) : void 0;
|
|
1028
|
+
const object = program2 && configObject(program2);
|
|
902
1029
|
const plugins = object && pluginsArray(object);
|
|
903
1030
|
const newPlugins = [
|
|
904
1031
|
...addTailwind ? [TAILWIND_VITE_PLUGIN] : [],
|
|
@@ -938,8 +1065,8 @@ function wireViteConfig(project, addTailwind, steps) {
|
|
|
938
1065
|
step: `Add resolve.alias ${VITE_ALIAS}, so imports like @/components/nxds/... resolve.`
|
|
939
1066
|
});
|
|
940
1067
|
}
|
|
941
|
-
if (
|
|
942
|
-
insertions.push(importInsertion(source,
|
|
1068
|
+
if (program2 && insertions.length > 0) {
|
|
1069
|
+
insertions.push(importInsertion(source, program2, imports));
|
|
943
1070
|
changes.push(editedFile(configFile, applyInsertions(source, insertions)));
|
|
944
1071
|
}
|
|
945
1072
|
return { changes, manual };
|
|
@@ -984,8 +1111,8 @@ var ESLINT_CONFIGS = [
|
|
|
984
1111
|
"eslint.config.mjs",
|
|
985
1112
|
"eslint.config.ts"
|
|
986
1113
|
];
|
|
987
|
-
function exportedConfigs(
|
|
988
|
-
const declaration =
|
|
1114
|
+
function exportedConfigs(program2) {
|
|
1115
|
+
const declaration = program2.body.find(
|
|
989
1116
|
(node) => isExportDefaultDeclaration(node)
|
|
990
1117
|
)?.declaration;
|
|
991
1118
|
if (isArrayExpression(declaration)) return declaration.elements;
|
|
@@ -1057,7 +1184,7 @@ function wireNextTailwind(project) {
|
|
|
1057
1184
|
};
|
|
1058
1185
|
}
|
|
1059
1186
|
function createdUnlessExists(project, file, content) {
|
|
1060
|
-
if (existsSync6(
|
|
1187
|
+
if (existsSync6(path7.join(project.root, file))) return NO_CHANGES;
|
|
1061
1188
|
return { changes: [{ path: file, content, created: true }], manual: [] };
|
|
1062
1189
|
}
|
|
1063
1190
|
function planWiring(project, config, addTailwind, setupSteps) {
|
|
@@ -1096,34 +1223,50 @@ async function diff(cwd, registry, names) {
|
|
|
1096
1223
|
const config = requireConfig(cwd);
|
|
1097
1224
|
const latest = await loadRelease(registry, "latest");
|
|
1098
1225
|
const previous = await fetchRelease(registry, config.version);
|
|
1099
|
-
const previousFiles = previous
|
|
1226
|
+
const previousFiles = previous && releaseFiles(previous, config.framework);
|
|
1100
1227
|
const { known, removed } = splitInstalled(latest, config.items);
|
|
1101
1228
|
const items = resolveItems(
|
|
1102
1229
|
latest,
|
|
1103
1230
|
config.framework,
|
|
1104
1231
|
names.length > 0 ? names : known
|
|
1105
1232
|
);
|
|
1233
|
+
const update2 = `\`${CLI_COMMAND} update\``;
|
|
1234
|
+
const overwrite = `\`${CLI_COMMAND} update --overwrite\``;
|
|
1235
|
+
function advice(state, file) {
|
|
1236
|
+
if (state === "outdated") return `${update2} replaces it`;
|
|
1237
|
+
if (state === "missing") return `${update2} restores it`;
|
|
1238
|
+
if (state === "conflicted") {
|
|
1239
|
+
return `has conflict markers; resolve them, or ${overwrite} replaces it`;
|
|
1240
|
+
}
|
|
1241
|
+
const base = baseOf(previousFiles, file.path);
|
|
1242
|
+
if (base === void 0) {
|
|
1243
|
+
return `has your edits; ${update2} keeps it, ${overwrite} replaces it`;
|
|
1244
|
+
}
|
|
1245
|
+
if (sameContent(base, file.content)) {
|
|
1246
|
+
return `has your edits; nxds hasn't changed it since ${config.version}`;
|
|
1247
|
+
}
|
|
1248
|
+
return `has your edits; ${update2} merges nxds's changes into it, ${overwrite} replaces it`;
|
|
1249
|
+
}
|
|
1106
1250
|
const files = items.flatMap(
|
|
1107
|
-
(item) => variantOf(item, config.framework).files.map((file) =>
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1251
|
+
(item) => variantOf(item, config.framework).files.map((file) => {
|
|
1252
|
+
const state = fileState(cwd, config, file, previousFiles);
|
|
1253
|
+
return {
|
|
1254
|
+
item: item.name,
|
|
1255
|
+
path: file.path,
|
|
1256
|
+
state,
|
|
1257
|
+
advice: state === "current" ? null : advice(state, file)
|
|
1258
|
+
};
|
|
1259
|
+
})
|
|
1112
1260
|
);
|
|
1113
1261
|
const differing = files.filter((file) => file.state !== "current");
|
|
1114
|
-
const advice = {
|
|
1115
|
-
outdated: "`nxds update` replaces it",
|
|
1116
|
-
changed: "differs from nxds's copy; `nxds update --overwrite` replaces it",
|
|
1117
|
-
missing: "`nxds update` restores it"
|
|
1118
|
-
};
|
|
1119
1262
|
return {
|
|
1120
1263
|
data: { version: config.version, latest: latest.version, files, removed },
|
|
1121
1264
|
lines: [
|
|
1122
1265
|
...differing.map(
|
|
1123
|
-
(file) => `${file.state.padEnd(
|
|
1266
|
+
(file) => `${file.state.padEnd(10)} ${file.path} (${file.advice})`
|
|
1124
1267
|
),
|
|
1125
1268
|
...removed.map(
|
|
1126
|
-
(name) =>
|
|
1269
|
+
(name) => `${"removed".padEnd(10)} ${name} (no longer in nxds; \`${CLI_COMMAND} update\` drops it)`
|
|
1127
1270
|
),
|
|
1128
1271
|
...differing.length === 0 && removed.length === 0 ? [`Every file matches nxds ${latest.version}.`] : []
|
|
1129
1272
|
],
|
|
@@ -1133,7 +1276,7 @@ async function diff(cwd, registry, names) {
|
|
|
1133
1276
|
function tailwindMessage(project) {
|
|
1134
1277
|
const declared = project.dependencies.tailwindcss;
|
|
1135
1278
|
if (declared === void 0) {
|
|
1136
|
-
return
|
|
1279
|
+
return `Tailwind 4 is not installed. Run \`${CLI_COMMAND} init\`.`;
|
|
1137
1280
|
}
|
|
1138
1281
|
const tailwindMajor = majorVersion(project, "tailwindcss");
|
|
1139
1282
|
if (tailwindMajor === 4) return `tailwindcss ${declared}`;
|
|
@@ -1143,14 +1286,14 @@ function tailwindMessage(project) {
|
|
|
1143
1286
|
return `tailwindcss ${declared}. nxds needs Tailwind CSS 4.`;
|
|
1144
1287
|
}
|
|
1145
1288
|
function read2(root, file) {
|
|
1146
|
-
if (!file || !existsSync7(
|
|
1147
|
-
return readFileSync4(
|
|
1289
|
+
if (!file || !existsSync7(path8.join(root, file))) return "";
|
|
1290
|
+
return readFileSync4(path8.join(root, file), "utf8");
|
|
1148
1291
|
}
|
|
1149
1292
|
function sourcesNxdsDir(config, css) {
|
|
1150
|
-
const entryDir2 =
|
|
1293
|
+
const entryDir2 = path8.posix.dirname(config.css);
|
|
1151
1294
|
return [...css.matchAll(/@source\s+(['"])(.*?)\1/g)].some(([, , source]) => {
|
|
1152
1295
|
const base = (source ?? "").split(/[*{[]/)[0] ?? "";
|
|
1153
|
-
const folder =
|
|
1296
|
+
const folder = path8.posix.join(entryDir2, base).replace(/\/+$/, "");
|
|
1154
1297
|
return folder === "." || /^\.\.(\/\.\.)*$/.test(folder) || config.dir === folder || config.dir.startsWith(`${folder}/`);
|
|
1155
1298
|
});
|
|
1156
1299
|
}
|
|
@@ -1190,7 +1333,7 @@ async function doctor(cwd, registry) {
|
|
|
1190
1333
|
check(
|
|
1191
1334
|
"config",
|
|
1192
1335
|
config !== null,
|
|
1193
|
-
config ? "nxds.json found" :
|
|
1336
|
+
config ? "nxds.json found" : `No nxds.json. Run \`${CLI_COMMAND} init\`.`
|
|
1194
1337
|
);
|
|
1195
1338
|
if (!project.setup) {
|
|
1196
1339
|
check("app", false, "Not a Next.js App Router or Vite + React app.");
|
|
@@ -1200,7 +1343,7 @@ async function doctor(cwd, registry) {
|
|
|
1200
1343
|
check(
|
|
1201
1344
|
"release",
|
|
1202
1345
|
catalog !== null,
|
|
1203
|
-
catalog ? `Files come from nxds ${config.version}` : `${registry} has no nxds ${config.version}. Run
|
|
1346
|
+
catalog ? `Files come from nxds ${config.version}` : `${registry} has no nxds ${config.version}. Run \`${CLI_COMMAND} update\`.`
|
|
1204
1347
|
);
|
|
1205
1348
|
check(
|
|
1206
1349
|
"tailwind",
|
|
@@ -1212,7 +1355,7 @@ async function doctor(cwd, registry) {
|
|
|
1212
1355
|
check(
|
|
1213
1356
|
"css entry",
|
|
1214
1357
|
importsTokens,
|
|
1215
|
-
!css ? `${config.css} is missing. Run
|
|
1358
|
+
!css ? `${config.css} is missing. Run \`${CLI_COMMAND} init\`.` : importsTokens ? `${config.css} imports the nxds tokens` : `${config.css} does not import ${config.dir}/tailwind/nxds.css. Add the @import back.`
|
|
1216
1359
|
);
|
|
1217
1360
|
const unscanned = unscannedReason(cwd, config, css);
|
|
1218
1361
|
check(
|
|
@@ -1222,7 +1365,7 @@ async function doctor(cwd, registry) {
|
|
|
1222
1365
|
);
|
|
1223
1366
|
const entry = project.setup === "next-app" ? `${entryDir(project)}/layout.tsx` : findFile(project, VITE_ENTRIES);
|
|
1224
1367
|
const entrySource = read2(cwd, entry);
|
|
1225
|
-
const cssFile =
|
|
1368
|
+
const cssFile = path8.basename(config.css);
|
|
1226
1369
|
const importsCss = entrySource.includes(cssFile);
|
|
1227
1370
|
check(
|
|
1228
1371
|
"css import",
|
|
@@ -1239,31 +1382,43 @@ async function doctor(cwd, registry) {
|
|
|
1239
1382
|
check(
|
|
1240
1383
|
"theme script",
|
|
1241
1384
|
scriptSource.includes(scriptMarker),
|
|
1242
|
-
scriptSource.includes(scriptMarker) ? "The theme script runs before first paint" :
|
|
1385
|
+
scriptSource.includes(scriptMarker) ? "The theme script runs before first paint" : `The theme script is missing, so the page can flash the wrong theme. Run \`${CLI_COMMAND} init\`.`
|
|
1243
1386
|
);
|
|
1244
1387
|
if (!catalog) return report(checks);
|
|
1245
1388
|
const { known, removed } = splitInstalled(catalog, config.items);
|
|
1246
1389
|
check(
|
|
1247
1390
|
"items",
|
|
1248
1391
|
removed.length === 0,
|
|
1249
|
-
removed.length === 0 ? `${known.length} installed items, all in this release` : `No longer in nxds: ${removed.join(", ")}. Run
|
|
1392
|
+
removed.length === 0 ? `${known.length} installed items, all in this release` : `No longer in nxds: ${removed.join(", ")}. Run \`${CLI_COMMAND} update\`.`
|
|
1250
1393
|
);
|
|
1251
1394
|
const items = resolveItems(catalog, config.framework, known);
|
|
1252
1395
|
const missingFiles = items.flatMap(
|
|
1253
1396
|
(item) => variantOf(item, config.framework).files.filter(
|
|
1254
|
-
(file) => !existsSync7(
|
|
1397
|
+
(file) => !existsSync7(path8.join(cwd, config.dir, file.path))
|
|
1255
1398
|
).map((file) => `${item.name}: ${file.path}`)
|
|
1256
1399
|
);
|
|
1257
1400
|
check(
|
|
1258
1401
|
"files",
|
|
1259
1402
|
missingFiles.length === 0,
|
|
1260
|
-
missingFiles.length === 0 ? `All files for ${known.length} installed items are present` : `Missing ${missingFiles.join(", ")}. Run
|
|
1403
|
+
missingFiles.length === 0 ? `All files for ${known.length} installed items are present` : `Missing ${missingFiles.join(", ")}. Run \`${CLI_COMMAND} add\` for those items.`
|
|
1404
|
+
);
|
|
1405
|
+
const conflicted = conflictedFiles(
|
|
1406
|
+
cwd,
|
|
1407
|
+
config,
|
|
1408
|
+
items.flatMap(
|
|
1409
|
+
(item) => variantOf(item, config.framework).files.map((file) => file.path)
|
|
1410
|
+
)
|
|
1411
|
+
).map((file) => `${config.dir}/${file}`);
|
|
1412
|
+
check(
|
|
1413
|
+
"conflicts",
|
|
1414
|
+
conflicted.length === 0,
|
|
1415
|
+
conflicted.length === 0 ? "No conflict markers left from an update" : `Resolve the <<<<<<< markers in ${conflicted.join(", ")}.`
|
|
1261
1416
|
);
|
|
1262
1417
|
const problems = packageProblems(project, config, items);
|
|
1263
1418
|
check(
|
|
1264
1419
|
"packages",
|
|
1265
1420
|
problems.length === 0,
|
|
1266
|
-
problems.length === 0 ? "Every package the installed items need is installed in range, @nxds/core included" : `${problems.join("; ")}. Run
|
|
1421
|
+
problems.length === 0 ? "Every package the installed items need is installed in range, @nxds/core included" : `${problems.join("; ")}. Run \`${CLI_COMMAND} update\`, then your package manager's install.`
|
|
1267
1422
|
);
|
|
1268
1423
|
return report(checks);
|
|
1269
1424
|
}
|
|
@@ -1283,14 +1438,27 @@ function report(checks) {
|
|
|
1283
1438
|
|
|
1284
1439
|
// src/commands/install.ts
|
|
1285
1440
|
import { existsSync as existsSync9, mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1286
|
-
import
|
|
1441
|
+
import path10 from "path";
|
|
1287
1442
|
|
|
1288
1443
|
// src/install.ts
|
|
1289
1444
|
import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
1290
|
-
import
|
|
1445
|
+
import path9 from "path";
|
|
1446
|
+
function emptyInstallResult() {
|
|
1447
|
+
return {
|
|
1448
|
+
items: [],
|
|
1449
|
+
written: [],
|
|
1450
|
+
unchanged: [],
|
|
1451
|
+
merged: [],
|
|
1452
|
+
conflicted: [],
|
|
1453
|
+
unresolved: [],
|
|
1454
|
+
skipped: [],
|
|
1455
|
+
packages: [],
|
|
1456
|
+
cssImports: []
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1291
1459
|
function addCssImports(project, config, files, dryRun) {
|
|
1292
1460
|
if (files.length === 0) return [];
|
|
1293
|
-
const entry =
|
|
1461
|
+
const entry = path9.join(project.root, config.css);
|
|
1294
1462
|
const current = existsSync8(entry) ? readFileSync5(entry, "utf8") : "";
|
|
1295
1463
|
const added = [...new Set(files)].map((file) => cssImportLine(config, file)).filter((line) => !current.includes(line));
|
|
1296
1464
|
if (added.length > 0 && !dryRun) {
|
|
@@ -1311,7 +1479,7 @@ function installItems(catalog, project, config, names, previous, options) {
|
|
|
1311
1479
|
config,
|
|
1312
1480
|
[...files.values()],
|
|
1313
1481
|
previous,
|
|
1314
|
-
options
|
|
1482
|
+
{ ...options, version: catalog.version }
|
|
1315
1483
|
);
|
|
1316
1484
|
const packages = packagesToInstall(
|
|
1317
1485
|
project,
|
|
@@ -1347,6 +1515,15 @@ function installLines(config, result, options) {
|
|
|
1347
1515
|
const verb = dryRun ? "Would write" : "Wrote";
|
|
1348
1516
|
return [
|
|
1349
1517
|
...result.written.map((file) => `${verb} ${config.dir}/${file}`),
|
|
1518
|
+
...result.merged.map(
|
|
1519
|
+
(file) => `${dryRun ? "Would merge" : "Merged"} nxds's changes into your edits in ${config.dir}/${file}`
|
|
1520
|
+
),
|
|
1521
|
+
...result.conflicted.map(
|
|
1522
|
+
(file) => `${dryRun ? "Would conflict" : "Conflict"} in ${config.dir}/${file}: nxds changed lines you edited. Resolve the <<<<<<< markers, then run \`${CLI_COMMAND} update\` again.`
|
|
1523
|
+
),
|
|
1524
|
+
...result.unresolved.map(
|
|
1525
|
+
(file) => `Still conflicted: ${config.dir}/${file}. Resolve the <<<<<<< markers, then run \`${CLI_COMMAND} update\` again.`
|
|
1526
|
+
),
|
|
1350
1527
|
...result.skipped.map(
|
|
1351
1528
|
(file) => `Kept ${config.dir}/${file}: it differs from nxds's copy, so it may hold your edits. Pass --overwrite to replace it.`
|
|
1352
1529
|
),
|
|
@@ -1356,10 +1533,13 @@ function installLines(config, result, options) {
|
|
|
1356
1533
|
)
|
|
1357
1534
|
];
|
|
1358
1535
|
}
|
|
1536
|
+
function settled(result) {
|
|
1537
|
+
return result.skipped.length === 0 && result.conflicted.length === 0 && result.unresolved.length === 0;
|
|
1538
|
+
}
|
|
1359
1539
|
function nxdsDir(dir) {
|
|
1360
|
-
const normalized =
|
|
1540
|
+
const normalized = relativePath(dir);
|
|
1361
1541
|
if (normalized) return normalized;
|
|
1362
|
-
throw new
|
|
1542
|
+
throw new UsageError(
|
|
1363
1543
|
`--dir takes a folder inside your app, such as components/nxds, not "${dir}".`
|
|
1364
1544
|
);
|
|
1365
1545
|
}
|
|
@@ -1380,7 +1560,7 @@ async function init(cwd, registry, options) {
|
|
|
1380
1560
|
const project = readProject(cwd);
|
|
1381
1561
|
if (!project.setup) {
|
|
1382
1562
|
throw new CliError(
|
|
1383
|
-
|
|
1563
|
+
`\`${CLI_COMMAND} init\` supports Next.js App Router and Vite + React apps. For other setups, follow the manual steps at https://nxds.dev/getting-started/install.`
|
|
1384
1564
|
);
|
|
1385
1565
|
}
|
|
1386
1566
|
if (!project.typescript) {
|
|
@@ -1419,20 +1599,20 @@ async function init(cwd, registry, options) {
|
|
|
1419
1599
|
project,
|
|
1420
1600
|
config,
|
|
1421
1601
|
[SETUP],
|
|
1422
|
-
|
|
1602
|
+
null,
|
|
1423
1603
|
options
|
|
1424
1604
|
);
|
|
1425
1605
|
if (!options.dryRun) {
|
|
1426
1606
|
for (const change of wiring.changes) {
|
|
1427
|
-
const target =
|
|
1428
|
-
mkdirSync2(
|
|
1607
|
+
const target = path10.join(project.root, change.path);
|
|
1608
|
+
mkdirSync2(path10.dirname(target), { recursive: true });
|
|
1429
1609
|
writeFileSync4(target, change.content);
|
|
1430
1610
|
}
|
|
1431
1611
|
writeConfig(cwd, config);
|
|
1432
1612
|
}
|
|
1433
1613
|
const packages = [...tailwind, ...installed.packages];
|
|
1434
1614
|
const [createVerb, editVerb] = options.dryRun ? ["Would create", "Would edit"] : ["Created", "Edited"];
|
|
1435
|
-
const done = wiring.manual.length === 0;
|
|
1615
|
+
const done = wiring.manual.length === 0 && settled(installed);
|
|
1436
1616
|
return {
|
|
1437
1617
|
data: {
|
|
1438
1618
|
setup: project.setup,
|
|
@@ -1453,7 +1633,9 @@ async function init(cwd, registry, options) {
|
|
|
1453
1633
|
(change) => `${change.created ? createVerb : editVerb} ${change.path}`
|
|
1454
1634
|
),
|
|
1455
1635
|
...manualLines(wiring.manual),
|
|
1456
|
-
...done && !options.dryRun ? [
|
|
1636
|
+
...done && !options.dryRun ? [
|
|
1637
|
+
`nxds is set up. Add a component with \`${CLI_COMMAND} add button\`.`
|
|
1638
|
+
] : []
|
|
1457
1639
|
],
|
|
1458
1640
|
ok: done
|
|
1459
1641
|
};
|
|
@@ -1461,14 +1643,11 @@ async function init(cwd, registry, options) {
|
|
|
1461
1643
|
function manualLines(manual) {
|
|
1462
1644
|
if (manual.length === 0) return [];
|
|
1463
1645
|
return [
|
|
1464
|
-
|
|
1646
|
+
`Finish these steps by hand, then run \`${CLI_COMMAND} doctor\`:`,
|
|
1465
1647
|
...manual.map(({ file, step }) => ` ${file}: ${step}`)
|
|
1466
1648
|
];
|
|
1467
1649
|
}
|
|
1468
1650
|
async function add(cwd, registry, names, options) {
|
|
1469
|
-
if (names.length === 0) {
|
|
1470
|
-
throw new CliError("Name at least one item, such as `nxds add button`.");
|
|
1471
|
-
}
|
|
1472
1651
|
const config = requireConfig(cwd);
|
|
1473
1652
|
const catalog = await loadRelease(registry, config.version);
|
|
1474
1653
|
const installed = installItems(
|
|
@@ -1476,7 +1655,7 @@ async function add(cwd, registry, names, options) {
|
|
|
1476
1655
|
readProject(cwd),
|
|
1477
1656
|
config,
|
|
1478
1657
|
names,
|
|
1479
|
-
|
|
1658
|
+
null,
|
|
1480
1659
|
options
|
|
1481
1660
|
);
|
|
1482
1661
|
if (!options.dryRun) writeConfig(cwd, config);
|
|
@@ -1484,11 +1663,11 @@ async function add(cwd, registry, names, options) {
|
|
|
1484
1663
|
data: { dryRun: options.dryRun, ...installed },
|
|
1485
1664
|
lines: [
|
|
1486
1665
|
...installLines(config, installed, options),
|
|
1487
|
-
...installed.written.length === 0 && installed
|
|
1666
|
+
...installed.written.length === 0 && settled(installed) ? [
|
|
1488
1667
|
`${names.join(", ")} ${names.length === 1 ? "is" : "are"} already up to date.`
|
|
1489
1668
|
] : []
|
|
1490
1669
|
],
|
|
1491
|
-
ok:
|
|
1670
|
+
ok: settled(installed)
|
|
1492
1671
|
};
|
|
1493
1672
|
}
|
|
1494
1673
|
function filesOf(catalog, framework, names) {
|
|
@@ -1498,24 +1677,40 @@ function filesOf(catalog, framework, names) {
|
|
|
1498
1677
|
)
|
|
1499
1678
|
);
|
|
1500
1679
|
}
|
|
1501
|
-
async function update(cwd, registry,
|
|
1502
|
-
if (names.length > 0) {
|
|
1503
|
-
throw new CliError(
|
|
1504
|
-
"`nxds update` updates every installed item, so one app never mixes two releases. Run it without item names."
|
|
1505
|
-
);
|
|
1506
|
-
}
|
|
1680
|
+
async function update(cwd, registry, options) {
|
|
1507
1681
|
const config = requireConfig(cwd);
|
|
1682
|
+
const from = config.version;
|
|
1508
1683
|
const latest = await loadRelease(registry, "latest");
|
|
1509
|
-
const previous = await fetchRelease(registry, config.version);
|
|
1510
1684
|
const { known, removed } = splitInstalled(latest, config.items);
|
|
1511
1685
|
const current = filesOf(latest, config.framework, known);
|
|
1686
|
+
const previous = await fetchRelease(registry, from);
|
|
1512
1687
|
const installedBefore = previous ? filesOf(
|
|
1513
1688
|
previous,
|
|
1514
1689
|
config.framework,
|
|
1515
1690
|
splitInstalled(previous, config.items).known
|
|
1516
1691
|
) : /* @__PURE__ */ new Set();
|
|
1692
|
+
const unresolved = from === latest.version || options.overwrite ? [] : conflictedFiles(cwd, config, /* @__PURE__ */ new Set([...current, ...installedBefore]));
|
|
1693
|
+
if (unresolved.length > 0) {
|
|
1694
|
+
const data2 = {
|
|
1695
|
+
...emptyInstallResult(),
|
|
1696
|
+
dryRun: options.dryRun,
|
|
1697
|
+
from,
|
|
1698
|
+
version: from,
|
|
1699
|
+
unresolved,
|
|
1700
|
+
removed: [],
|
|
1701
|
+
orphaned: []
|
|
1702
|
+
};
|
|
1703
|
+
return {
|
|
1704
|
+
data: data2,
|
|
1705
|
+
lines: [
|
|
1706
|
+
...unresolved.map((file) => `Still conflicted: ${config.dir}/${file}`),
|
|
1707
|
+
`Changed nothing, and nxds.json stays on ${from}. Resolve the <<<<<<< markers, then run \`${CLI_COMMAND} update\` again.`
|
|
1708
|
+
],
|
|
1709
|
+
ok: false
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1517
1712
|
const orphaned = [...installedBefore].filter(
|
|
1518
|
-
(file) => !current.has(file) && existsSync9(
|
|
1713
|
+
(file) => !current.has(file) && existsSync9(path10.join(cwd, config.dir, file))
|
|
1519
1714
|
);
|
|
1520
1715
|
config.items = known;
|
|
1521
1716
|
const installed = installItems(
|
|
@@ -1523,22 +1718,22 @@ async function update(cwd, registry, names, options) {
|
|
|
1523
1718
|
readProject(cwd),
|
|
1524
1719
|
config,
|
|
1525
1720
|
known,
|
|
1526
|
-
previous
|
|
1721
|
+
previous && releaseFiles(previous, config.framework),
|
|
1527
1722
|
options
|
|
1528
1723
|
);
|
|
1529
|
-
const from = config.version;
|
|
1530
1724
|
const stays = from !== latest.version && installed.skipped.length > 0;
|
|
1531
1725
|
if (!stays) config.version = latest.version;
|
|
1532
1726
|
if (!options.dryRun) writeConfig(cwd, config);
|
|
1727
|
+
const data = {
|
|
1728
|
+
dryRun: options.dryRun,
|
|
1729
|
+
from,
|
|
1730
|
+
version: config.version,
|
|
1731
|
+
...installed,
|
|
1732
|
+
removed,
|
|
1733
|
+
orphaned
|
|
1734
|
+
};
|
|
1533
1735
|
return {
|
|
1534
|
-
data
|
|
1535
|
-
dryRun: options.dryRun,
|
|
1536
|
-
from,
|
|
1537
|
-
version: config.version,
|
|
1538
|
-
...installed,
|
|
1539
|
-
removed,
|
|
1540
|
-
orphaned
|
|
1541
|
-
},
|
|
1736
|
+
data,
|
|
1542
1737
|
lines: [
|
|
1543
1738
|
...previous ? [] : [
|
|
1544
1739
|
`${registry} has no nxds ${from}, so every file that differs from ${latest.version} counts as changed.`
|
|
@@ -1550,121 +1745,184 @@ async function update(cwd, registry, names, options) {
|
|
|
1550
1745
|
...orphaned.map(
|
|
1551
1746
|
(file) => `No installed item uses ${config.dir}/${file} any more. Delete it once nothing in your app imports it.`
|
|
1552
1747
|
),
|
|
1553
|
-
stays ? `nxds.json stays on ${from} until every
|
|
1748
|
+
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}.`
|
|
1554
1749
|
],
|
|
1555
|
-
ok:
|
|
1750
|
+
ok: settled(installed)
|
|
1556
1751
|
};
|
|
1557
1752
|
}
|
|
1558
1753
|
|
|
1559
|
-
// src/
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
--overwrite
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
json
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
);
|
|
1604
|
-
|
|
1754
|
+
// src/cli.ts
|
|
1755
|
+
function context(command) {
|
|
1756
|
+
const { cwd, registry } = command.optsWithGlobals();
|
|
1757
|
+
return {
|
|
1758
|
+
cwd: path11.resolve(cwd ?? process.cwd()),
|
|
1759
|
+
registry: resolveRegistry(registry)
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
function installOptions(command) {
|
|
1763
|
+
const flags = command.opts();
|
|
1764
|
+
return {
|
|
1765
|
+
overwrite: flags.overwrite ?? false,
|
|
1766
|
+
dryRun: flags.dryRun ?? false,
|
|
1767
|
+
install: flags.install
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
function withInstallFlags(command) {
|
|
1771
|
+
return command.option("--dry-run", "show what would change, without changing anything").option("--overwrite", "replace files that differ from nxds's copy").option("--no-install", "skip installing npm packages");
|
|
1772
|
+
}
|
|
1773
|
+
function program(done, write) {
|
|
1774
|
+
const cli = new Command().configureOutput({
|
|
1775
|
+
writeOut: write,
|
|
1776
|
+
writeErr: write,
|
|
1777
|
+
// Errors are printed once, by `main`, as text or JSON.
|
|
1778
|
+
outputError: () => {
|
|
1779
|
+
}
|
|
1780
|
+
}).exitOverride().name(CLI_COMMAND).description("Add nxds to your app.").version("0.2.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
|
+
new Option(
|
|
1782
|
+
"--registry <url|path>",
|
|
1783
|
+
"where to get nxds releases (default: https://nxds.dev/r)"
|
|
1784
|
+
).env("NXDS_REGISTRY_URL")
|
|
1785
|
+
).configureHelp({ showGlobalOptions: true }).addHelpText(
|
|
1786
|
+
"after",
|
|
1787
|
+
`
|
|
1788
|
+
Exit codes: 0 done, 1 error, 2 usage error, 3 done with something left for you (conflicts, kept files, manual steps, failed checks).
|
|
1789
|
+
Docs: https://nxds.dev/getting-started/install`
|
|
1790
|
+
);
|
|
1791
|
+
withInstallFlags(
|
|
1792
|
+
cli.command("init").description("set up nxds: token CSS, provider, and the layout wiring").option(
|
|
1793
|
+
"--dir <path>",
|
|
1794
|
+
"nxds folder (default: components/nxds, or src/components/nxds)"
|
|
1795
|
+
)
|
|
1796
|
+
).action(async (_options, command) => {
|
|
1797
|
+
const { cwd, registry } = context(command);
|
|
1798
|
+
const { dir } = command.opts();
|
|
1799
|
+
done(await init(cwd, registry, { ...installOptions(command), dir }));
|
|
1800
|
+
});
|
|
1801
|
+
withInstallFlags(
|
|
1802
|
+
cli.command("add").description("add components or blocks, and everything they need").argument("<items...>", "item names, such as button")
|
|
1803
|
+
).action(async (items, _options, command) => {
|
|
1804
|
+
const { cwd, registry } = context(command);
|
|
1805
|
+
done(await add(cwd, registry, items, installOptions(command)));
|
|
1806
|
+
});
|
|
1807
|
+
withInstallFlags(
|
|
1808
|
+
cli.command("update").description(
|
|
1809
|
+
"update every installed item to the latest release, merging nxds changes into your edits"
|
|
1810
|
+
)
|
|
1811
|
+
).action(async (_options, command) => {
|
|
1812
|
+
const { cwd, registry } = context(command);
|
|
1813
|
+
done(await update(cwd, registry, installOptions(command)));
|
|
1814
|
+
});
|
|
1815
|
+
cli.command("list").description("list components and blocks").addOption(
|
|
1816
|
+
new Option("--kind <kind>", "only this kind").choices([
|
|
1817
|
+
"component",
|
|
1818
|
+
"block"
|
|
1819
|
+
])
|
|
1820
|
+
).action(async (_options, command) => {
|
|
1821
|
+
const { cwd, registry } = context(command);
|
|
1822
|
+
const { kind } = command.opts();
|
|
1823
|
+
done(await list(cwd, registry, kind));
|
|
1824
|
+
});
|
|
1825
|
+
cli.command("search").description("find items by name or description").argument("<term...>", "words to look for").action(async (term, _options, command) => {
|
|
1826
|
+
const { cwd, registry } = context(command);
|
|
1827
|
+
done(await search(cwd, registry, term.join(" ")));
|
|
1828
|
+
});
|
|
1829
|
+
cli.command("view").description("show an item's files, packages and docs link").argument("<item>", "item name").action(async (item, _options, command) => {
|
|
1830
|
+
const { cwd, registry } = context(command);
|
|
1831
|
+
done(await view(cwd, registry, item));
|
|
1832
|
+
});
|
|
1833
|
+
cli.command("info").description("show what the CLI detects in this app").action((_options, command) => {
|
|
1834
|
+
done(info(context(command).cwd));
|
|
1835
|
+
});
|
|
1836
|
+
cli.command("diff").description("compare your nxds files with the latest release").argument("[items...]", "item names (default: every installed item)").action(async (items, _options, command) => {
|
|
1837
|
+
const { cwd, registry } = context(command);
|
|
1838
|
+
done(await diff(cwd, registry, items));
|
|
1839
|
+
});
|
|
1840
|
+
cli.command("doctor").description("check that nxds is wired up correctly").action(async (_options, command) => {
|
|
1841
|
+
const { cwd, registry } = context(command);
|
|
1842
|
+
done(await doctor(cwd, registry));
|
|
1843
|
+
});
|
|
1844
|
+
return cli;
|
|
1605
1845
|
}
|
|
1606
|
-
function text
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
return text({ help: HELP }, HELP);
|
|
1613
|
-
const [command, ...args] = positionals;
|
|
1614
|
-
const cwd = path10.resolve(values.cwd ?? process.cwd());
|
|
1615
|
-
const registry = resolveRegistry(values.registry);
|
|
1616
|
-
const installOptions = {
|
|
1617
|
-
overwrite: values.overwrite,
|
|
1618
|
-
dryRun: values["dry-run"],
|
|
1619
|
-
install: !values["no-install"]
|
|
1846
|
+
function printed(text, key) {
|
|
1847
|
+
const trimmed = text.trimEnd();
|
|
1848
|
+
return {
|
|
1849
|
+
exitCode: EXIT_CODES.ok,
|
|
1850
|
+
data: { [key]: trimmed },
|
|
1851
|
+
lines: [trimmed]
|
|
1620
1852
|
};
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
return add(cwd, registry, args, installOptions);
|
|
1626
|
-
case "update":
|
|
1627
|
-
return update(cwd, registry, args, installOptions);
|
|
1628
|
-
case "list":
|
|
1629
|
-
return list(cwd, registry, values.kind);
|
|
1630
|
-
case "search":
|
|
1631
|
-
return search(cwd, registry, args.join(" "));
|
|
1632
|
-
case "view":
|
|
1633
|
-
return view(cwd, registry, args[0]);
|
|
1634
|
-
case "info":
|
|
1635
|
-
return info(cwd);
|
|
1636
|
-
case "diff":
|
|
1637
|
-
return diff(cwd, registry, args);
|
|
1638
|
-
case "doctor":
|
|
1639
|
-
return doctor(cwd, registry);
|
|
1640
|
-
default:
|
|
1641
|
-
throw new CliError(
|
|
1642
|
-
command ? `Unknown command "${command}". Run \`nxds --help\`.` : HELP
|
|
1643
|
-
);
|
|
1853
|
+
}
|
|
1854
|
+
function commanderOutcome(error, printedText) {
|
|
1855
|
+
if (error.code === "commander.version") {
|
|
1856
|
+
return printed(printedText, "version");
|
|
1644
1857
|
}
|
|
1858
|
+
if (error.exitCode === 0) return printed(printedText, "help");
|
|
1859
|
+
if (error.code === "commander.help") {
|
|
1860
|
+
return { exitCode: EXIT_CODES.usage, error: printedText.trimEnd() };
|
|
1861
|
+
}
|
|
1862
|
+
return {
|
|
1863
|
+
exitCode: EXIT_CODES.usage,
|
|
1864
|
+
error: `${error.message.replace(/^error: /, "")}
|
|
1865
|
+
Run \`${CLI_COMMAND} --help\` for the commands and options.`
|
|
1866
|
+
};
|
|
1645
1867
|
}
|
|
1646
|
-
async function
|
|
1647
|
-
|
|
1868
|
+
async function outcome(argv) {
|
|
1869
|
+
let result;
|
|
1870
|
+
let printedText = "";
|
|
1871
|
+
const cli = program(
|
|
1872
|
+
(done) => {
|
|
1873
|
+
result = done;
|
|
1874
|
+
},
|
|
1875
|
+
(text) => {
|
|
1876
|
+
printedText += text;
|
|
1877
|
+
}
|
|
1878
|
+
);
|
|
1648
1879
|
try {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1880
|
+
await cli.parseAsync(argv, { from: "user" });
|
|
1881
|
+
if (!result) throw new Error("The command stored no result.");
|
|
1882
|
+
return {
|
|
1883
|
+
exitCode: result.ok ? EXIT_CODES.ok : EXIT_CODES.attention,
|
|
1884
|
+
data: result.data,
|
|
1885
|
+
lines: result.lines
|
|
1886
|
+
};
|
|
1655
1887
|
} catch (error) {
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
`
|
|
1662
|
-
);
|
|
1663
|
-
} else if (error instanceof CliError) {
|
|
1664
|
-
console.error(message);
|
|
1665
|
-
} else {
|
|
1666
|
-
console.error(error);
|
|
1888
|
+
if (error instanceof CommanderError) {
|
|
1889
|
+
return commanderOutcome(error, printedText);
|
|
1890
|
+
}
|
|
1891
|
+
if (error instanceof CliError) {
|
|
1892
|
+
return { exitCode: error.exitCode, error: error.message };
|
|
1667
1893
|
}
|
|
1894
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1895
|
+
const stack = error instanceof Error ? error.stack : void 0;
|
|
1896
|
+
return {
|
|
1897
|
+
exitCode: EXIT_CODES.error,
|
|
1898
|
+
error: message,
|
|
1899
|
+
stack: stack ?? message
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
async function main(argv, output) {
|
|
1904
|
+
const json = argv.includes("--json");
|
|
1905
|
+
const result = await outcome(argv);
|
|
1906
|
+
const ok = result.exitCode === EXIT_CODES.ok;
|
|
1907
|
+
if (json) {
|
|
1908
|
+
const body = "error" in result ? { ok: false, exitCode: result.exitCode, error: result.error } : { ok, exitCode: result.exitCode, data: result.data };
|
|
1909
|
+
output.out(`${JSON.stringify(body, null, 2)}
|
|
1910
|
+
`);
|
|
1911
|
+
} else if ("lines" in result) {
|
|
1912
|
+
output.out(`${result.lines.join("\n")}
|
|
1913
|
+
`);
|
|
1914
|
+
} else if (result.stack !== void 0) {
|
|
1915
|
+
output.err(`${result.stack}
|
|
1916
|
+
`);
|
|
1917
|
+
} else {
|
|
1918
|
+
output.err(`${result.error}
|
|
1919
|
+
`);
|
|
1668
1920
|
}
|
|
1921
|
+
return result.exitCode;
|
|
1669
1922
|
}
|
|
1670
|
-
|
|
1923
|
+
|
|
1924
|
+
// src/index.ts
|
|
1925
|
+
process.exitCode = await main(process.argv.slice(2), {
|
|
1926
|
+
out: (text) => process.stdout.write(text),
|
|
1927
|
+
err: (text) => process.stderr.write(text)
|
|
1928
|
+
});
|