@nuvio/cli 0.5.5 → 1.1.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/README.md +5 -3
- package/dist/cli-entry.js +1851 -638
- package/package.json +12 -6
- package/templates/AGENT.md.tpl +7 -4
- package/templates/START_HERE.md.tpl +5 -1
package/dist/cli-entry.js
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { resolve } from "path";
|
|
4
|
+
import { resolve as resolve4 } from "path";
|
|
5
5
|
|
|
6
|
-
// src/
|
|
7
|
-
import {
|
|
6
|
+
// src/brand-apply.ts
|
|
7
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
8
|
+
import { join as join2, resolve } from "path";
|
|
9
|
+
import { applyPatchToSource } from "@nuvio/ast-engine";
|
|
10
|
+
import {
|
|
11
|
+
buildBrandPatchOps,
|
|
12
|
+
brandFragmentHostHint,
|
|
13
|
+
DEFAULT_BRAND_CONFIG,
|
|
14
|
+
isHostPatchable,
|
|
15
|
+
isPccBrandableCategory,
|
|
16
|
+
normalizeBrandConfig
|
|
17
|
+
} from "@nuvio/shared";
|
|
18
|
+
import {
|
|
19
|
+
listPccManifestFiles,
|
|
20
|
+
loadPccManifestFromFile,
|
|
21
|
+
resolvePccManifestPath
|
|
22
|
+
} from "@nuvio/shared/load-pcc-manifest";
|
|
8
23
|
|
|
9
24
|
// src/detect-project.ts
|
|
10
25
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -104,283 +119,957 @@ function detectProject(root) {
|
|
|
104
119
|
};
|
|
105
120
|
}
|
|
106
121
|
|
|
107
|
-
// src/
|
|
108
|
-
import {
|
|
109
|
-
import {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
122
|
+
// src/project-scan.ts
|
|
123
|
+
import { relative } from "path";
|
|
124
|
+
import {
|
|
125
|
+
buildSourceIndex,
|
|
126
|
+
detectProjectLibraries,
|
|
127
|
+
NUVIO_DEFAULT_SCAN_GLOBS
|
|
128
|
+
} from "@nuvio/vite-plugin/scan";
|
|
129
|
+
var SCAN_GLOBS = [...NUVIO_DEFAULT_SCAN_GLOBS, "app/**/*.{tsx,jsx}"];
|
|
130
|
+
function scanProject(root) {
|
|
131
|
+
const ctx = detectProject(root);
|
|
132
|
+
const detectedLibraries = detectProjectLibraries(root, ctx.packageJson);
|
|
133
|
+
const index = buildSourceIndex(root, SCAN_GLOBS, { detectedLibraries });
|
|
134
|
+
return { ctx, detectedLibraries, index };
|
|
135
|
+
}
|
|
136
|
+
function relPath(root, fileAbs) {
|
|
137
|
+
return relative(root, fileAbs).replace(/\\/g, "/");
|
|
138
|
+
}
|
|
139
|
+
function isTableHost(entry) {
|
|
140
|
+
return entry.hierarchyRole === "table" || entry.id.endsWith(".table") || entry.id.includes(".header.") || /\.row\./.test(entry.id);
|
|
141
|
+
}
|
|
142
|
+
function aggregateClassNameModes(entries) {
|
|
143
|
+
const counts = {};
|
|
144
|
+
for (const entry of entries) {
|
|
145
|
+
const mode = entry.classNameMode ?? "literal-only";
|
|
146
|
+
counts[mode] = (counts[mode] ?? 0) + 1;
|
|
130
147
|
}
|
|
148
|
+
return counts;
|
|
131
149
|
}
|
|
132
150
|
|
|
133
|
-
// src/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const raw = dev?.[name] ?? deps?.[name];
|
|
140
|
-
if (!raw) return null;
|
|
141
|
-
return raw.replace(/^[\^~]/, "");
|
|
142
|
-
}
|
|
143
|
-
function packagesNeedInstall(packageJsonPath, targetVersion) {
|
|
144
|
-
const pkg = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
|
|
145
|
-
for (const name of ["@nuvio/vite-plugin", "@nuvio/overlay"]) {
|
|
146
|
-
const v = parseInstalledVersion(pkg, name);
|
|
147
|
-
if (v !== targetVersion) return true;
|
|
151
|
+
// src/brand-apply.ts
|
|
152
|
+
var BRAND_RELATIVE = "nuvio/brand.json";
|
|
153
|
+
function readProjectBrandConfig(cwd) {
|
|
154
|
+
const filePath = join2(resolve(cwd), BRAND_RELATIVE);
|
|
155
|
+
if (!existsSync2(filePath)) {
|
|
156
|
+
return { ...DEFAULT_BRAND_CONFIG };
|
|
148
157
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
cwd: root,
|
|
155
|
-
shell: true,
|
|
156
|
-
stdio: "inherit",
|
|
157
|
-
env: process.env
|
|
158
|
-
});
|
|
159
|
-
if (result.status !== 0) {
|
|
160
|
-
return {
|
|
161
|
-
ok: false,
|
|
162
|
-
message: `Install failed. Try manually:
|
|
163
|
-
${cmd}`
|
|
164
|
-
};
|
|
158
|
+
try {
|
|
159
|
+
const raw = readFileSync2(filePath, "utf8");
|
|
160
|
+
return normalizeBrandConfig(JSON.parse(raw));
|
|
161
|
+
} catch {
|
|
162
|
+
return { ...DEFAULT_BRAND_CONFIG };
|
|
165
163
|
}
|
|
166
|
-
return { ok: true };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// src/babel-traverse.ts
|
|
170
|
-
import traverseImport from "@babel/traverse";
|
|
171
|
-
var traverse = typeof traverseImport === "function" ? traverseImport : traverseImport.default;
|
|
172
|
-
var babel_traverse_default = traverse;
|
|
173
|
-
|
|
174
|
-
// src/patch-vite-config.ts
|
|
175
|
-
import * as t from "@babel/types";
|
|
176
|
-
import { readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
177
|
-
|
|
178
|
-
// src/babel-generator.ts
|
|
179
|
-
import generateImport from "@babel/generator";
|
|
180
|
-
var generate = typeof generateImport === "function" ? generateImport : generateImport.default;
|
|
181
|
-
var babel_generator_default = generate;
|
|
182
|
-
|
|
183
|
-
// src/parse-ts.ts
|
|
184
|
-
import { parse } from "@babel/parser";
|
|
185
|
-
var PARSE_OPTS = {
|
|
186
|
-
sourceType: "module",
|
|
187
|
-
plugins: ["typescript", "jsx"]
|
|
188
|
-
};
|
|
189
|
-
function parseTs(source, filename = "file.tsx") {
|
|
190
|
-
return parse(source, {
|
|
191
|
-
...PARSE_OPTS,
|
|
192
|
-
sourceFilename: filename
|
|
193
|
-
});
|
|
194
164
|
}
|
|
195
|
-
function
|
|
196
|
-
|
|
197
|
-
return out.code.endsWith("\n") ? out.code : `${out.code}
|
|
198
|
-
`;
|
|
165
|
+
function duplicateIdSet(errors) {
|
|
166
|
+
return new Set(errors.map((error) => error.id));
|
|
199
167
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
let
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
168
|
+
function collectApplyTargets(manifest, entries, duplicateIds) {
|
|
169
|
+
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
170
|
+
const targets = [];
|
|
171
|
+
let skipped = 0;
|
|
172
|
+
for (const category of Object.keys(manifest.categories)) {
|
|
173
|
+
if (!isPccBrandableCategory(category)) {
|
|
174
|
+
continue;
|
|
207
175
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
function hasNuvioPluginCall(ast) {
|
|
212
|
-
let found = false;
|
|
213
|
-
babel_traverse_default(ast, {
|
|
214
|
-
CallExpression(path) {
|
|
215
|
-
if (t.isIdentifier(path.node.callee, { name: "nuvio" })) found = true;
|
|
176
|
+
const config = manifest.categories[category];
|
|
177
|
+
if (!config) {
|
|
178
|
+
continue;
|
|
216
179
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (!expr || !t.isArrayExpression(expr)) return false;
|
|
223
|
-
return expr.elements.some(
|
|
224
|
-
(el) => t.isStringLiteral(el) && el.value === OVERLAY_DEP
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
function ensureOptimizeDepsExclude(ast) {
|
|
228
|
-
let patched = false;
|
|
229
|
-
babel_traverse_default(ast, {
|
|
230
|
-
CallExpression(path) {
|
|
231
|
-
const callee = path.node.callee;
|
|
232
|
-
if (!t.isIdentifier(callee, { name: "defineConfig" })) return;
|
|
233
|
-
const arg = path.node.arguments[0];
|
|
234
|
-
if (!t.isObjectExpression(arg)) return;
|
|
235
|
-
let optimizeDeps;
|
|
236
|
-
for (const prop of arg.properties) {
|
|
237
|
-
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key, { name: "optimizeDeps" })) {
|
|
238
|
-
optimizeDeps = prop;
|
|
239
|
-
break;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
if (!optimizeDeps) {
|
|
243
|
-
arg.properties.push(
|
|
244
|
-
t.objectProperty(
|
|
245
|
-
t.identifier("optimizeDeps"),
|
|
246
|
-
t.objectExpression([
|
|
247
|
-
t.objectProperty(
|
|
248
|
-
t.identifier("exclude"),
|
|
249
|
-
t.arrayExpression([t.stringLiteral(OVERLAY_DEP)])
|
|
250
|
-
)
|
|
251
|
-
])
|
|
252
|
-
)
|
|
253
|
-
);
|
|
254
|
-
patched = true;
|
|
255
|
-
return;
|
|
180
|
+
for (const hostId of config.hosts) {
|
|
181
|
+
const entry = byId.get(hostId);
|
|
182
|
+
if (!entry) {
|
|
183
|
+
skipped += 1;
|
|
184
|
+
continue;
|
|
256
185
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
excludeProp = p;
|
|
262
|
-
break;
|
|
263
|
-
}
|
|
186
|
+
const patch = isHostPatchable(entry, duplicateIds);
|
|
187
|
+
if (!patch.patchable) {
|
|
188
|
+
skipped += 1;
|
|
189
|
+
continue;
|
|
264
190
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
191
|
+
targets.push({
|
|
192
|
+
hostId,
|
|
193
|
+
category,
|
|
194
|
+
action: category,
|
|
195
|
+
entry
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return { targets, skipped };
|
|
200
|
+
}
|
|
201
|
+
async function applyTargetsToProject(projectRoot, targets, brand, dryRun) {
|
|
202
|
+
const root = resolve(projectRoot);
|
|
203
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
204
|
+
for (const target of targets) {
|
|
205
|
+
const filePath = resolve(root, target.entry.file);
|
|
206
|
+
const list = byFile.get(filePath) ?? [];
|
|
207
|
+
list.push(target);
|
|
208
|
+
byFile.set(filePath, list);
|
|
209
|
+
}
|
|
210
|
+
let applied = 0;
|
|
211
|
+
const failed = [];
|
|
212
|
+
for (const [filePath, fileTargets] of byFile) {
|
|
213
|
+
if (!existsSync2(filePath)) {
|
|
214
|
+
for (const target of fileTargets) {
|
|
215
|
+
failed.push({ hostId: target.hostId, reason: "file_missing" });
|
|
274
216
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (dryRun) {
|
|
220
|
+
applied += fileTargets.length;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
let source = readFileSync2(filePath, "utf8");
|
|
224
|
+
for (const target of fileTargets) {
|
|
225
|
+
const ops = buildBrandPatchOps(
|
|
226
|
+
target.action,
|
|
227
|
+
brand,
|
|
228
|
+
brandFragmentHostHint(target.entry)
|
|
229
|
+
);
|
|
230
|
+
const result = await applyPatchToSource(source, filePath, target.hostId, ops, {
|
|
231
|
+
classNameMode: target.entry.classNameMode
|
|
232
|
+
});
|
|
233
|
+
if (!result.ok) {
|
|
234
|
+
failed.push({ hostId: target.hostId, reason: result.message ?? result.code });
|
|
235
|
+
continue;
|
|
278
236
|
}
|
|
237
|
+
source = result.source;
|
|
238
|
+
applied += 1;
|
|
279
239
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (!t.isArrayExpression(path.node.value)) return;
|
|
295
|
-
path.node.value.elements.push(t.callExpression(t.identifier("nuvio"), []));
|
|
296
|
-
patched = true;
|
|
240
|
+
writeFileSync(filePath, source, "utf8");
|
|
241
|
+
}
|
|
242
|
+
return { applied, failed };
|
|
243
|
+
}
|
|
244
|
+
function printHumanReport(result) {
|
|
245
|
+
console.log(`Page: ${result.page}`);
|
|
246
|
+
console.log(`Route: ${result.route}`);
|
|
247
|
+
console.log(`Manifest: ${result.manifestPath}`);
|
|
248
|
+
console.log(`Applied: ${result.applied}`);
|
|
249
|
+
console.log(`Skipped: ${result.skipped}`);
|
|
250
|
+
if (result.failed.length > 0) {
|
|
251
|
+
console.log("Failed:");
|
|
252
|
+
for (const failure of result.failed) {
|
|
253
|
+
console.log(`- ${failure.hostId}: ${failure.reason}`);
|
|
297
254
|
}
|
|
298
|
-
}
|
|
299
|
-
return patched;
|
|
255
|
+
}
|
|
300
256
|
}
|
|
301
|
-
function
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
257
|
+
async function applyLoadedManifest(manifestPath, manifest, entries, duplicateIds, brand, projectRoot, opts) {
|
|
258
|
+
const { targets, skipped } = collectApplyTargets(manifest, entries, duplicateIds);
|
|
259
|
+
const { applied, failed } = await applyTargetsToProject(
|
|
260
|
+
projectRoot,
|
|
261
|
+
targets,
|
|
262
|
+
brand,
|
|
263
|
+
opts.dryRun === true
|
|
264
|
+
);
|
|
265
|
+
return {
|
|
266
|
+
page: manifest.page,
|
|
267
|
+
route: manifest.route,
|
|
268
|
+
manifestPath,
|
|
269
|
+
applied,
|
|
270
|
+
skipped,
|
|
271
|
+
failed
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
async function runBrandApplyAll(opts) {
|
|
275
|
+
const manifestPaths = listPccManifestFiles(opts.cwd);
|
|
276
|
+
if (manifestPaths.length === 0) {
|
|
277
|
+
console.error(`No PCC manifests found under ${resolve(opts.cwd)}/nuvio/pages`);
|
|
278
|
+
return 2;
|
|
308
279
|
}
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
280
|
+
const brand = readProjectBrandConfig(opts.cwd);
|
|
281
|
+
let scan;
|
|
282
|
+
try {
|
|
283
|
+
scan = scanProject(opts.cwd);
|
|
284
|
+
} catch (e) {
|
|
285
|
+
if (e instanceof PreflightError) {
|
|
286
|
+
console.error(e.message);
|
|
287
|
+
return 3;
|
|
288
|
+
}
|
|
289
|
+
throw e;
|
|
313
290
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
291
|
+
const duplicateIds = duplicateIdSet(scan.index.duplicateErrors);
|
|
292
|
+
const pages = [];
|
|
293
|
+
for (const manifestPath of manifestPaths) {
|
|
294
|
+
const loaded = loadPccManifestFromFile(manifestPath);
|
|
295
|
+
if (!loaded.ok) {
|
|
296
|
+
console.error(`Invalid PCC manifest (${manifestPath}): ${loaded.error.message}`);
|
|
297
|
+
return 2;
|
|
298
|
+
}
|
|
299
|
+
pages.push(
|
|
300
|
+
await applyLoadedManifest(
|
|
301
|
+
manifestPath,
|
|
302
|
+
loaded.manifest,
|
|
303
|
+
scan.index.entries,
|
|
304
|
+
duplicateIds,
|
|
305
|
+
brand,
|
|
306
|
+
scan.ctx.root,
|
|
307
|
+
opts
|
|
319
308
|
)
|
|
320
309
|
);
|
|
321
310
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
311
|
+
const pass = pages.every((page) => page.failed.length === 0);
|
|
312
|
+
if (opts.json) {
|
|
313
|
+
console.log(JSON.stringify({ pass, dryRun: opts.dryRun === true, pages }, null, 2));
|
|
314
|
+
return pass ? 0 : 1;
|
|
326
315
|
}
|
|
327
|
-
|
|
328
|
-
|
|
316
|
+
console.log(`Nuvio Brand Apply${opts.dryRun ? " (dry run)" : ""}
|
|
317
|
+
`);
|
|
318
|
+
for (const page of pages) {
|
|
319
|
+
printHumanReport(page);
|
|
320
|
+
console.log("");
|
|
321
|
+
}
|
|
322
|
+
console.log(`Result: ${pass ? "PASS" : "FAIL"}`);
|
|
323
|
+
return pass ? 0 : 1;
|
|
329
324
|
}
|
|
330
|
-
function
|
|
331
|
-
|
|
325
|
+
async function runBrandApply(opts) {
|
|
326
|
+
if (opts.all) {
|
|
327
|
+
return runBrandApplyAll(opts);
|
|
328
|
+
}
|
|
329
|
+
let manifestPath;
|
|
332
330
|
try {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
331
|
+
manifestPath = resolve(
|
|
332
|
+
resolvePccManifestPath(opts.cwd, { page: opts.page, manifest: opts.manifest })
|
|
333
|
+
);
|
|
334
|
+
} catch (e) {
|
|
335
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
336
|
+
return 2;
|
|
337
337
|
}
|
|
338
|
+
const loaded = loadPccManifestFromFile(manifestPath);
|
|
339
|
+
if (!loaded.ok) {
|
|
340
|
+
console.error(`Invalid PCC manifest (${manifestPath}): ${loaded.error.message}`);
|
|
341
|
+
return 2;
|
|
342
|
+
}
|
|
343
|
+
const brand = readProjectBrandConfig(opts.cwd);
|
|
344
|
+
let scan;
|
|
345
|
+
try {
|
|
346
|
+
scan = scanProject(opts.cwd);
|
|
347
|
+
} catch (e) {
|
|
348
|
+
if (e instanceof PreflightError) {
|
|
349
|
+
console.error(e.message);
|
|
350
|
+
return 3;
|
|
351
|
+
}
|
|
352
|
+
throw e;
|
|
353
|
+
}
|
|
354
|
+
const duplicateIds = duplicateIdSet(scan.index.duplicateErrors);
|
|
355
|
+
const result = await applyLoadedManifest(
|
|
356
|
+
manifestPath,
|
|
357
|
+
loaded.manifest,
|
|
358
|
+
scan.index.entries,
|
|
359
|
+
duplicateIds,
|
|
360
|
+
brand,
|
|
361
|
+
scan.ctx.root,
|
|
362
|
+
opts
|
|
363
|
+
);
|
|
364
|
+
const pass = result.failed.length === 0;
|
|
365
|
+
if (opts.json) {
|
|
366
|
+
console.log(JSON.stringify({ pass, dryRun: opts.dryRun === true, ...result }, null, 2));
|
|
367
|
+
return pass ? 0 : 1;
|
|
368
|
+
}
|
|
369
|
+
console.log(`Nuvio Brand Apply${opts.dryRun ? " (dry run)" : ""}
|
|
370
|
+
`);
|
|
371
|
+
printHumanReport(result);
|
|
372
|
+
console.log(`
|
|
373
|
+
Result: ${pass ? "PASS" : "FAIL"}`);
|
|
374
|
+
return pass ? 0 : 1;
|
|
338
375
|
}
|
|
339
376
|
|
|
340
|
-
// src/
|
|
341
|
-
import
|
|
342
|
-
import {
|
|
343
|
-
import {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
377
|
+
// src/brand-scan.ts
|
|
378
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
379
|
+
import { join as join3, resolve as resolve2 } from "path";
|
|
380
|
+
import {
|
|
381
|
+
DEFAULT_BRAND_CONFIG as DEFAULT_BRAND_CONFIG2,
|
|
382
|
+
evaluateBrandPageScan,
|
|
383
|
+
getBrandColorLabel,
|
|
384
|
+
getBrandDensityLabel,
|
|
385
|
+
getBrandRadiusLabel,
|
|
386
|
+
getBrandTypographyLabel,
|
|
387
|
+
normalizeBrandConfig as normalizeBrandConfig2,
|
|
388
|
+
pccCategoryLabel
|
|
389
|
+
} from "@nuvio/shared";
|
|
390
|
+
import {
|
|
391
|
+
listPccManifestFiles as listPccManifestFiles2,
|
|
392
|
+
loadPccManifestFromFile as loadPccManifestFromFile2,
|
|
393
|
+
resolvePccManifestPath as resolvePccManifestPath2
|
|
394
|
+
} from "@nuvio/shared/load-pcc-manifest";
|
|
395
|
+
var BRAND_RELATIVE2 = "nuvio/brand.json";
|
|
396
|
+
function readProjectBrandConfig2(cwd) {
|
|
397
|
+
const filePath = join3(resolve2(cwd), BRAND_RELATIVE2);
|
|
398
|
+
if (!existsSync3(filePath)) {
|
|
399
|
+
return { ...DEFAULT_BRAND_CONFIG2 };
|
|
400
|
+
}
|
|
401
|
+
try {
|
|
402
|
+
const raw = readFileSync3(filePath, "utf8");
|
|
403
|
+
return normalizeBrandConfig2(JSON.parse(raw));
|
|
404
|
+
} catch {
|
|
405
|
+
return { ...DEFAULT_BRAND_CONFIG2 };
|
|
354
406
|
}
|
|
355
|
-
return null;
|
|
356
407
|
}
|
|
357
|
-
function
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
408
|
+
function formatBrandSummary(brand) {
|
|
409
|
+
return [
|
|
410
|
+
getBrandColorLabel(brand.color),
|
|
411
|
+
getBrandRadiusLabel(brand.radius),
|
|
412
|
+
getBrandDensityLabel(brand.density),
|
|
413
|
+
getBrandTypographyLabel(brand.typography)
|
|
414
|
+
].join(" \xB7 ");
|
|
415
|
+
}
|
|
416
|
+
function printHumanReport2(result, manifestPath) {
|
|
417
|
+
console.log("Nuvio Brand Scan\n");
|
|
418
|
+
console.log(`Page: ${result.page}`);
|
|
419
|
+
console.log(`Route: ${result.route}`);
|
|
420
|
+
console.log(`Manifest: ${manifestPath}`);
|
|
421
|
+
console.log(`Saved brand: ${formatBrandSummary(result.brand)}
|
|
422
|
+
`);
|
|
423
|
+
console.log("Category summary:");
|
|
424
|
+
for (const category of result.categories) {
|
|
425
|
+
const label = pccCategoryLabel(category.category).padEnd(10);
|
|
426
|
+
const status = category.pass ? "PASS" : "FAIL";
|
|
427
|
+
console.log(
|
|
428
|
+
`${label} ${status} on-brand ${category.onBrand}/${category.expected} \xB7 off-brand ${category.offBrand} \xB7 no-traits ${category.noTraits}`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
console.log("\nTotals:");
|
|
432
|
+
console.log(`On-brand ${result.onBrandCount}`);
|
|
433
|
+
console.log(`Off-brand ${result.offBrandCount}`);
|
|
434
|
+
console.log(`No traits ${result.noTraitsCount}`);
|
|
435
|
+
console.log(`Missing ${result.missingCount}`);
|
|
436
|
+
const offBrandHosts = result.hosts.filter((host) => host.status === "off_brand");
|
|
437
|
+
if (offBrandHosts.length > 0) {
|
|
438
|
+
console.log("\nOff-brand hosts:");
|
|
439
|
+
for (const host of offBrandHosts.slice(0, 12)) {
|
|
440
|
+
const headline = host.inspect?.headline ?? "Off-brand";
|
|
441
|
+
console.log(`- ${host.hostId} (${host.category}) \u2014 ${headline}`);
|
|
362
442
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
function hasDevShell(ast) {
|
|
367
|
-
let found = false;
|
|
368
|
-
babel_traverse_default(ast, {
|
|
369
|
-
JSXElement(path) {
|
|
370
|
-
const name = path.node.openingElement.name;
|
|
371
|
-
if (t2.isJSXIdentifier(name) && name.name === "NuvioDevShell") found = true;
|
|
443
|
+
if (offBrandHosts.length > 12) {
|
|
444
|
+
console.log(`\u2026and ${offBrandHosts.length - 12} more`);
|
|
372
445
|
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
446
|
+
}
|
|
447
|
+
console.log(`
|
|
448
|
+
Result: ${result.pass ? "PASS" : "FAIL"}`);
|
|
449
|
+
}
|
|
450
|
+
function printAllHumanReport(pages) {
|
|
451
|
+
console.log("Nuvio Brand Scan (all pages)\n");
|
|
452
|
+
for (const page of pages) {
|
|
453
|
+
const status = page.result.pass ? "PASS" : "FAIL";
|
|
454
|
+
const summary = `${page.result.onBrandCount} on-brand \xB7 ${page.result.offBrandCount} off-brand`;
|
|
455
|
+
console.log(`${page.result.page.padEnd(16)} ${status} ${summary}`);
|
|
456
|
+
}
|
|
457
|
+
const pass = pages.every((page) => page.result.pass);
|
|
458
|
+
console.log(`
|
|
459
|
+
Result: ${pass ? "PASS" : "FAIL"}`);
|
|
460
|
+
}
|
|
461
|
+
function scanLoadedManifest(manifestPath, entries, brand) {
|
|
462
|
+
const loaded = loadPccManifestFromFile2(manifestPath);
|
|
463
|
+
if (!loaded.ok) {
|
|
464
|
+
return {
|
|
465
|
+
ok: false,
|
|
466
|
+
code: 2,
|
|
467
|
+
message: `Invalid PCC manifest (${manifestPath}): ${loaded.error.message}`
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
ok: true,
|
|
472
|
+
result: evaluateBrandPageScan(loaded.manifest, entries, brand)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function runBrandScanAll(opts) {
|
|
476
|
+
const manifestPaths = listPccManifestFiles2(opts.cwd);
|
|
477
|
+
if (manifestPaths.length === 0) {
|
|
478
|
+
console.error(`No PCC manifests found under ${resolve2(opts.cwd)}/nuvio/pages`);
|
|
479
|
+
return 2;
|
|
480
|
+
}
|
|
481
|
+
const brand = readProjectBrandConfig2(opts.cwd);
|
|
482
|
+
let scan;
|
|
483
|
+
try {
|
|
484
|
+
scan = scanProject(opts.cwd);
|
|
485
|
+
} catch (e) {
|
|
486
|
+
if (e instanceof PreflightError) {
|
|
487
|
+
console.error(e.message);
|
|
488
|
+
return 3;
|
|
489
|
+
}
|
|
490
|
+
throw e;
|
|
491
|
+
}
|
|
492
|
+
const pages = [];
|
|
493
|
+
for (const manifestPath of manifestPaths) {
|
|
494
|
+
const scanned = scanLoadedManifest(manifestPath, scan.index.entries, brand);
|
|
495
|
+
if (!scanned.ok) {
|
|
496
|
+
console.error(scanned.message);
|
|
497
|
+
return scanned.code;
|
|
498
|
+
}
|
|
499
|
+
pages.push({ manifestPath, result: scanned.result });
|
|
500
|
+
}
|
|
501
|
+
const pass = pages.every((page) => page.result.pass);
|
|
502
|
+
if (opts.json) {
|
|
503
|
+
console.log(JSON.stringify({ pass, brand, pages }, null, 2));
|
|
504
|
+
return pass ? 0 : 1;
|
|
505
|
+
}
|
|
506
|
+
printAllHumanReport(pages);
|
|
507
|
+
return pass ? 0 : 1;
|
|
508
|
+
}
|
|
509
|
+
function runBrandScan(opts) {
|
|
510
|
+
if (opts.all) {
|
|
511
|
+
return runBrandScanAll(opts);
|
|
512
|
+
}
|
|
513
|
+
let manifestPath;
|
|
514
|
+
try {
|
|
515
|
+
manifestPath = resolve2(
|
|
516
|
+
resolvePccManifestPath2(opts.cwd, { page: opts.page, manifest: opts.manifest })
|
|
517
|
+
);
|
|
518
|
+
} catch (e) {
|
|
519
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
520
|
+
return 2;
|
|
521
|
+
}
|
|
522
|
+
const brand = readProjectBrandConfig2(opts.cwd);
|
|
523
|
+
let scan;
|
|
524
|
+
try {
|
|
525
|
+
scan = scanProject(opts.cwd);
|
|
526
|
+
} catch (e) {
|
|
527
|
+
if (e instanceof PreflightError) {
|
|
528
|
+
console.error(e.message);
|
|
529
|
+
return 3;
|
|
530
|
+
}
|
|
531
|
+
throw e;
|
|
532
|
+
}
|
|
533
|
+
const scanned = scanLoadedManifest(manifestPath, scan.index.entries, brand);
|
|
534
|
+
if (!scanned.ok) {
|
|
535
|
+
console.error(scanned.message);
|
|
536
|
+
return scanned.code;
|
|
537
|
+
}
|
|
538
|
+
if (opts.json) {
|
|
539
|
+
console.log(
|
|
540
|
+
JSON.stringify(
|
|
541
|
+
{
|
|
542
|
+
manifestPath,
|
|
543
|
+
...scanned.result
|
|
544
|
+
},
|
|
545
|
+
null,
|
|
546
|
+
2
|
|
547
|
+
)
|
|
548
|
+
);
|
|
549
|
+
return scanned.result.pass ? 0 : 1;
|
|
550
|
+
}
|
|
551
|
+
printHumanReport2(scanned.result, manifestPath);
|
|
552
|
+
return scanned.result.pass ? 0 : 1;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// src/coverage-verify.ts
|
|
556
|
+
import { resolve as resolve3 } from "path";
|
|
557
|
+
import {
|
|
558
|
+
evaluatePageCoverage,
|
|
559
|
+
pccCategoryLabel as pccCategoryLabel2
|
|
560
|
+
} from "@nuvio/shared";
|
|
561
|
+
import {
|
|
562
|
+
listPccManifestFiles as listPccManifestFiles3,
|
|
563
|
+
loadPccManifestFromFile as loadPccManifestFromFile3,
|
|
564
|
+
resolvePccManifestPath as resolvePccManifestPath3
|
|
565
|
+
} from "@nuvio/shared/load-pcc-manifest";
|
|
566
|
+
function formatCategoryLine(summary) {
|
|
567
|
+
const label = pccCategoryLabel2(summary.category).padEnd(10);
|
|
568
|
+
const status = summary.pass ? "PASS" : "FAIL";
|
|
569
|
+
return `${label} ${status} ${summary.indexed}/${summary.expected}`;
|
|
570
|
+
}
|
|
571
|
+
function printHumanReport3(result, manifestPath) {
|
|
572
|
+
console.log("Nuvio Coverage Report\n");
|
|
573
|
+
console.log(`Page: ${result.page}`);
|
|
574
|
+
console.log(`Route: ${result.route}`);
|
|
575
|
+
console.log(`Manifest: ${manifestPath}
|
|
576
|
+
`);
|
|
577
|
+
console.log("Category summary:");
|
|
578
|
+
for (const category of result.categories) {
|
|
579
|
+
console.log(formatCategoryLine(category));
|
|
580
|
+
}
|
|
581
|
+
console.log("\nCoverage gates:");
|
|
582
|
+
const g = result.gates;
|
|
583
|
+
console.log(`Indexed ${g.indexed}/${g.expected}`);
|
|
584
|
+
console.log(`Patchable ${g.patchable}/${g.expected}`);
|
|
585
|
+
console.log(`Categorized ${g.categorized}/${g.expected}`);
|
|
586
|
+
console.log(`Editable ${g.editable}/${g.expected}`);
|
|
587
|
+
console.log(`Brandable ${g.brandable}/${g.expected}`);
|
|
588
|
+
console.log("\nBrandability:");
|
|
589
|
+
console.log(`Brandable ${result.brandableCount}`);
|
|
590
|
+
console.log(`Editable-only ${result.editableOnlyCount}`);
|
|
591
|
+
const missing = result.issues.filter((i) => i.kind === "missing");
|
|
592
|
+
const unpatchable = result.issues.filter((i) => i.kind === "unpatchable");
|
|
593
|
+
const duplicates = result.issues.filter((i) => i.kind === "duplicate_id");
|
|
594
|
+
if (missing.length > 0) {
|
|
595
|
+
console.log("\nMissing hosts:");
|
|
596
|
+
for (const issue of missing) {
|
|
597
|
+
console.log(`- ${issue.hostId} (${issue.category})`);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if (unpatchable.length > 0) {
|
|
601
|
+
console.log("\nUnpatchable hosts:");
|
|
602
|
+
for (const issue of unpatchable) {
|
|
603
|
+
console.log(`- ${issue.hostId}`);
|
|
604
|
+
if (issue.reason) {
|
|
605
|
+
console.log(` reason: ${issue.reason}`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (duplicates.length > 0) {
|
|
610
|
+
console.log("\nDuplicate id hosts:");
|
|
611
|
+
for (const issue of duplicates) {
|
|
612
|
+
console.log(`- ${issue.hostId}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
console.log(`
|
|
616
|
+
Result: ${result.pass ? "PASS" : "FAIL"}`);
|
|
617
|
+
}
|
|
618
|
+
function printAllHumanReport2(summary) {
|
|
619
|
+
console.log("Nuvio Coverage Report (all pages)\n");
|
|
620
|
+
for (const entry of summary.pages) {
|
|
621
|
+
const status = entry.result.pass ? "PASS" : "FAIL";
|
|
622
|
+
console.log(`${entry.result.page.padEnd(16)} ${status} ${entry.manifestPath}`);
|
|
623
|
+
}
|
|
624
|
+
console.log(`
|
|
625
|
+
Result: ${summary.pass ? "PASS" : "FAIL"}`);
|
|
626
|
+
}
|
|
627
|
+
function verifyLoadedManifest(manifestPath, entries, duplicateErrors) {
|
|
628
|
+
const loaded = loadPccManifestFromFile3(manifestPath);
|
|
629
|
+
if (!loaded.ok) {
|
|
630
|
+
return {
|
|
631
|
+
ok: false,
|
|
632
|
+
code: 2,
|
|
633
|
+
message: `Invalid PCC manifest (${manifestPath}): ${loaded.error.message}`
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
const result = evaluatePageCoverage(loaded.manifest, entries, duplicateErrors);
|
|
637
|
+
return { ok: true, result };
|
|
638
|
+
}
|
|
639
|
+
function runCoverageVerifyAll(opts) {
|
|
640
|
+
const manifestPaths = listPccManifestFiles3(opts.cwd);
|
|
641
|
+
if (manifestPaths.length === 0) {
|
|
642
|
+
console.error(`No PCC manifests found under ${resolve3(opts.cwd)}/nuvio/pages`);
|
|
643
|
+
return 2;
|
|
644
|
+
}
|
|
645
|
+
let scan;
|
|
646
|
+
try {
|
|
647
|
+
scan = scanProject(opts.cwd);
|
|
648
|
+
} catch (e) {
|
|
649
|
+
if (e instanceof PreflightError) {
|
|
650
|
+
console.error(e.message);
|
|
651
|
+
return 3;
|
|
652
|
+
}
|
|
653
|
+
throw e;
|
|
654
|
+
}
|
|
655
|
+
const pages = [];
|
|
656
|
+
for (const manifestPath of manifestPaths) {
|
|
657
|
+
const verified = verifyLoadedManifest(
|
|
658
|
+
manifestPath,
|
|
659
|
+
scan.index.entries,
|
|
660
|
+
scan.index.duplicateErrors
|
|
661
|
+
);
|
|
662
|
+
if (!verified.ok) {
|
|
663
|
+
console.error(verified.message);
|
|
664
|
+
return verified.code;
|
|
665
|
+
}
|
|
666
|
+
pages.push({ manifestPath, result: verified.result });
|
|
667
|
+
}
|
|
668
|
+
const summary = {
|
|
669
|
+
pass: pages.every((page) => page.result.pass),
|
|
670
|
+
pages
|
|
671
|
+
};
|
|
672
|
+
if (opts.json) {
|
|
673
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
674
|
+
return summary.pass ? 0 : 1;
|
|
675
|
+
}
|
|
676
|
+
printAllHumanReport2(summary);
|
|
677
|
+
return summary.pass ? 0 : 1;
|
|
678
|
+
}
|
|
679
|
+
function runCoverageVerify(opts) {
|
|
680
|
+
if (opts.all) {
|
|
681
|
+
return runCoverageVerifyAll(opts);
|
|
682
|
+
}
|
|
683
|
+
let manifestPath;
|
|
684
|
+
try {
|
|
685
|
+
manifestPath = resolve3(
|
|
686
|
+
resolvePccManifestPath3(opts.cwd, { page: opts.page, manifest: opts.manifest })
|
|
687
|
+
);
|
|
688
|
+
} catch (e) {
|
|
689
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
690
|
+
return 2;
|
|
691
|
+
}
|
|
692
|
+
let scan;
|
|
693
|
+
try {
|
|
694
|
+
scan = scanProject(opts.cwd);
|
|
695
|
+
} catch (e) {
|
|
696
|
+
if (e instanceof PreflightError) {
|
|
697
|
+
console.error(e.message);
|
|
698
|
+
return 3;
|
|
699
|
+
}
|
|
700
|
+
throw e;
|
|
701
|
+
}
|
|
702
|
+
const verified = verifyLoadedManifest(
|
|
703
|
+
manifestPath,
|
|
704
|
+
scan.index.entries,
|
|
705
|
+
scan.index.duplicateErrors
|
|
706
|
+
);
|
|
707
|
+
if (!verified.ok) {
|
|
708
|
+
console.error(verified.message);
|
|
709
|
+
return verified.code;
|
|
710
|
+
}
|
|
711
|
+
if (opts.json) {
|
|
712
|
+
console.log(
|
|
713
|
+
JSON.stringify(
|
|
714
|
+
{
|
|
715
|
+
manifestPath,
|
|
716
|
+
...verified.result
|
|
717
|
+
},
|
|
718
|
+
null,
|
|
719
|
+
2
|
|
720
|
+
)
|
|
721
|
+
);
|
|
722
|
+
return verified.result.pass ? 0 : 1;
|
|
723
|
+
}
|
|
724
|
+
printHumanReport3(verified.result, manifestPath);
|
|
725
|
+
return verified.result.pass ? 0 : 1;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/detect-pm.ts
|
|
729
|
+
import { existsSync as existsSync4 } from "fs";
|
|
730
|
+
import { join as join4 } from "path";
|
|
731
|
+
function detectPackageManager(root, override) {
|
|
732
|
+
if (override) return override;
|
|
733
|
+
if (existsSync4(join4(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
734
|
+
if (existsSync4(join4(root, "package-lock.json"))) return "npm";
|
|
735
|
+
if (existsSync4(join4(root, "yarn.lock"))) return "yarn";
|
|
736
|
+
if (existsSync4(join4(root, "bun.lockb")) || existsSync4(join4(root, "bun.lock")))
|
|
737
|
+
return "bun";
|
|
738
|
+
return "npm";
|
|
739
|
+
}
|
|
740
|
+
function installCommand(pm, version) {
|
|
741
|
+
const pkgs = `@nuvio/vite-plugin@${version} @nuvio/overlay@${version}`;
|
|
742
|
+
switch (pm) {
|
|
743
|
+
case "pnpm":
|
|
744
|
+
return `pnpm add -D ${pkgs}`;
|
|
745
|
+
case "yarn":
|
|
746
|
+
return `yarn add -D ${pkgs}`;
|
|
747
|
+
case "bun":
|
|
748
|
+
return `bun add -d ${pkgs}`;
|
|
749
|
+
default:
|
|
750
|
+
return `npm install -D ${pkgs}`;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/nuvio-deps.ts
|
|
755
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
756
|
+
function readPackageJson(packageJsonPath) {
|
|
757
|
+
return JSON.parse(readFileSync4(packageJsonPath, "utf8"));
|
|
758
|
+
}
|
|
759
|
+
function getDependencyVersion(pkg, name) {
|
|
760
|
+
const deps = pkg.dependencies;
|
|
761
|
+
const devDeps = pkg.devDependencies;
|
|
762
|
+
return deps?.[name] ?? devDeps?.[name];
|
|
763
|
+
}
|
|
764
|
+
function hasNuvioDependency(pkg, name) {
|
|
765
|
+
return Boolean(getDependencyVersion(pkg, name));
|
|
766
|
+
}
|
|
767
|
+
function hasNuvioPackages(pkg) {
|
|
768
|
+
return hasNuvioDependency(pkg, "@nuvio/vite-plugin") && hasNuvioDependency(pkg, "@nuvio/overlay");
|
|
769
|
+
}
|
|
770
|
+
function isWorkspaceLinkedVersion(version) {
|
|
771
|
+
if (!version) return false;
|
|
772
|
+
return version.startsWith("workspace:") || version.startsWith("link:") || version.startsWith("file:");
|
|
773
|
+
}
|
|
774
|
+
function nuvioOverlayLinkKind(pkg) {
|
|
775
|
+
const raw = getDependencyVersion(pkg, "@nuvio/overlay");
|
|
776
|
+
if (!raw) return "missing";
|
|
777
|
+
return isWorkspaceLinkedVersion(raw) ? "workspace" : "npm";
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/telemetry.ts
|
|
781
|
+
import { mkdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
782
|
+
import { homedir } from "os";
|
|
783
|
+
import { join as join5 } from "path";
|
|
784
|
+
import { randomUUID } from "crypto";
|
|
785
|
+
import os from "os";
|
|
786
|
+
import { PostHog } from "posthog-node";
|
|
787
|
+
|
|
788
|
+
// src/nuvio-posthog-token.ts
|
|
789
|
+
var NUVIO_POSTHOG_TOKEN = "phc_CJnWrLU4hB4aA88DJrPnma2WBMQqVHxUMVvrsye3R6x2";
|
|
790
|
+
|
|
791
|
+
// src/version.ts
|
|
792
|
+
import { createRequire } from "module";
|
|
793
|
+
var require2 = createRequire(import.meta.url);
|
|
794
|
+
var NUVIO_VERSION = require2("../package.json").version;
|
|
795
|
+
|
|
796
|
+
// src/telemetry.ts
|
|
797
|
+
var POSTHOG_HOST = "https://us.i.posthog.com";
|
|
798
|
+
function telemetryFilePath() {
|
|
799
|
+
return join5(homedir(), ".nuvio", "telemetry.json");
|
|
800
|
+
}
|
|
801
|
+
var FORBIDDEN_PROP_KEYS = /* @__PURE__ */ new Set([
|
|
802
|
+
"cwd",
|
|
803
|
+
"root",
|
|
804
|
+
"file",
|
|
805
|
+
"path",
|
|
806
|
+
"name",
|
|
807
|
+
"message",
|
|
808
|
+
"stack"
|
|
809
|
+
]);
|
|
810
|
+
var SHUTDOWN_TIMEOUT_MS = 3e3;
|
|
811
|
+
var client = null;
|
|
812
|
+
var sessionAnonymousId = null;
|
|
813
|
+
var shutdownDone = false;
|
|
814
|
+
var signalHandlersRegistered = false;
|
|
815
|
+
function telemetryDebug(message, detail) {
|
|
816
|
+
if (process.env.NUVIO_TELEMETRY_DEBUG !== "1") return;
|
|
817
|
+
if (detail !== void 0) {
|
|
818
|
+
console.error(`[nuvio telemetry] ${message}`, detail);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
console.error(`[nuvio telemetry] ${message}`);
|
|
822
|
+
}
|
|
823
|
+
function isTelemetryEnabled() {
|
|
824
|
+
const flag = process.env.NUVIO_TELEMETRY;
|
|
825
|
+
if (flag === "0") return false;
|
|
826
|
+
if (flag?.toLowerCase() === "false") return false;
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
function posthogToken() {
|
|
830
|
+
return process.env.NUVIO_POSTHOG_TOKEN ?? NUVIO_POSTHOG_TOKEN;
|
|
831
|
+
}
|
|
832
|
+
function tokenIsConfigured(token) {
|
|
833
|
+
return Boolean(token && token.startsWith("phc_"));
|
|
834
|
+
}
|
|
835
|
+
function readOrCreateAnonymousId() {
|
|
836
|
+
if (sessionAnonymousId) return sessionAnonymousId;
|
|
837
|
+
try {
|
|
838
|
+
const raw = readFileSync5(telemetryFilePath(), "utf8");
|
|
839
|
+
const parsed = JSON.parse(raw);
|
|
840
|
+
if (parsed.anonymousId) {
|
|
841
|
+
sessionAnonymousId = parsed.anonymousId;
|
|
842
|
+
return parsed.anonymousId;
|
|
843
|
+
}
|
|
844
|
+
} catch {
|
|
845
|
+
}
|
|
846
|
+
const id = randomUUID();
|
|
847
|
+
sessionAnonymousId = id;
|
|
848
|
+
try {
|
|
849
|
+
mkdirSync(join5(homedir(), ".nuvio"), { recursive: true, mode: 448 });
|
|
850
|
+
writeFileSync2(
|
|
851
|
+
telemetryFilePath(),
|
|
852
|
+
JSON.stringify({ anonymousId: id }, null, 2),
|
|
853
|
+
{ mode: 384 }
|
|
854
|
+
);
|
|
855
|
+
} catch {
|
|
856
|
+
}
|
|
857
|
+
return id;
|
|
858
|
+
}
|
|
859
|
+
function getClient() {
|
|
860
|
+
if (!isTelemetryEnabled()) return null;
|
|
861
|
+
const token = posthogToken();
|
|
862
|
+
if (!tokenIsConfigured(token)) return null;
|
|
863
|
+
if (!client) {
|
|
864
|
+
client = new PostHog(token, {
|
|
865
|
+
host: POSTHOG_HOST,
|
|
866
|
+
flushAt: 1,
|
|
867
|
+
flushInterval: 0
|
|
868
|
+
});
|
|
869
|
+
telemetryDebug("PostHog client initialized", {
|
|
870
|
+
host: POSTHOG_HOST,
|
|
871
|
+
tokenPrefix: `${token.slice(0, 8)}\u2026`
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
return client;
|
|
875
|
+
}
|
|
876
|
+
function sanitizeProps(props) {
|
|
877
|
+
if (!props) return void 0;
|
|
878
|
+
const out = {};
|
|
879
|
+
for (const [key, value] of Object.entries(props)) {
|
|
880
|
+
if (FORBIDDEN_PROP_KEYS.has(key)) continue;
|
|
881
|
+
if (value === void 0) continue;
|
|
882
|
+
if (typeof value === "string" && /[/\\]/.test(value)) continue;
|
|
883
|
+
out[key] = value;
|
|
884
|
+
}
|
|
885
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
886
|
+
}
|
|
887
|
+
function resolveCliInvokedCommand(help, command) {
|
|
888
|
+
if (help) return "help";
|
|
889
|
+
if (!command) return "none";
|
|
890
|
+
if (command === "init") return "init";
|
|
891
|
+
if (command === "doctor") return "doctor";
|
|
892
|
+
if (command === "scan") return "scan";
|
|
893
|
+
if (command === "stats") return "stats";
|
|
894
|
+
return "unknown";
|
|
895
|
+
}
|
|
896
|
+
function buildCliInvokedProps(command, pmOverride) {
|
|
897
|
+
const props = {
|
|
898
|
+
nuvio_version: NUVIO_VERSION,
|
|
899
|
+
os: process.platform,
|
|
900
|
+
arch: os.arch(),
|
|
901
|
+
node: process.version,
|
|
902
|
+
command
|
|
903
|
+
};
|
|
904
|
+
if (pmOverride) props.package_manager = pmOverride;
|
|
905
|
+
return props;
|
|
906
|
+
}
|
|
907
|
+
function buildCliTelemetryProps(pm, project) {
|
|
908
|
+
const props = {
|
|
909
|
+
nuvio_version: NUVIO_VERSION,
|
|
910
|
+
os: process.platform,
|
|
911
|
+
arch: os.arch(),
|
|
912
|
+
node: process.version
|
|
913
|
+
};
|
|
914
|
+
if (pm) props.package_manager = pm;
|
|
915
|
+
if (project) {
|
|
916
|
+
props.has_react = true;
|
|
917
|
+
props.has_vite = true;
|
|
918
|
+
props.has_tailwind = project.tailwindOk;
|
|
919
|
+
}
|
|
920
|
+
return props;
|
|
921
|
+
}
|
|
922
|
+
function preflightErrorCode(message) {
|
|
923
|
+
if (message === MSG.noPackageJson) return "preflight_no_package_json";
|
|
924
|
+
if (message === MSG.noVite) return "preflight_no_vite";
|
|
925
|
+
if (message === MSG.noReact) return "preflight_no_react";
|
|
926
|
+
if (message === MSG.noViteDep) return "preflight_no_vite_dep";
|
|
927
|
+
if (message === MSG.monorepoRoot || message === MSG.cliPackage) {
|
|
928
|
+
return "preflight_monorepo";
|
|
929
|
+
}
|
|
930
|
+
return "preflight_unknown";
|
|
931
|
+
}
|
|
932
|
+
function captureCliInvoked(command, pmOverride) {
|
|
933
|
+
captureCliEvent("nuvio_cli_invoked", buildCliInvokedProps(command, pmOverride));
|
|
934
|
+
}
|
|
935
|
+
function captureCliEvent(event, props) {
|
|
936
|
+
try {
|
|
937
|
+
if (!isTelemetryEnabled()) {
|
|
938
|
+
telemetryDebug(`skipped ${event} (telemetry disabled)`);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
const ph = getClient();
|
|
942
|
+
if (!ph) {
|
|
943
|
+
telemetryDebug(`skipped ${event} (no PostHog client \u2014 check token)`);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const distinctId = readOrCreateAnonymousId();
|
|
947
|
+
ph.capture({
|
|
948
|
+
distinctId,
|
|
949
|
+
event,
|
|
950
|
+
properties: sanitizeProps(props)
|
|
951
|
+
});
|
|
952
|
+
telemetryDebug(`captured ${event}`, { distinctId });
|
|
953
|
+
} catch (error) {
|
|
954
|
+
telemetryDebug(`capture failed for ${event}`, error);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
async function flushAndShutdownClient() {
|
|
958
|
+
if (!client) return;
|
|
959
|
+
const active = client;
|
|
960
|
+
client = null;
|
|
961
|
+
await Promise.race([
|
|
962
|
+
(async () => {
|
|
963
|
+
await active.flush();
|
|
964
|
+
await active.shutdown();
|
|
965
|
+
})(),
|
|
966
|
+
new Promise((_, reject) => {
|
|
967
|
+
setTimeout(
|
|
968
|
+
() => reject(new Error("telemetry shutdown timed out")),
|
|
969
|
+
SHUTDOWN_TIMEOUT_MS
|
|
970
|
+
);
|
|
971
|
+
})
|
|
972
|
+
]);
|
|
973
|
+
}
|
|
974
|
+
async function shutdownTelemetry() {
|
|
975
|
+
if (shutdownDone) return;
|
|
976
|
+
shutdownDone = true;
|
|
977
|
+
try {
|
|
978
|
+
await flushAndShutdownClient();
|
|
979
|
+
telemetryDebug("flush + shutdown complete");
|
|
980
|
+
} catch (error) {
|
|
981
|
+
telemetryDebug("shutdown failed", error);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
function registerTelemetrySignalHandlers() {
|
|
985
|
+
if (signalHandlersRegistered) return;
|
|
986
|
+
signalHandlersRegistered = true;
|
|
987
|
+
const onSignal = (signal) => {
|
|
988
|
+
void (async () => {
|
|
989
|
+
await shutdownTelemetry();
|
|
990
|
+
const code2 = signal === "SIGINT" ? 130 : 143;
|
|
991
|
+
process.exit(code2);
|
|
992
|
+
})();
|
|
993
|
+
};
|
|
994
|
+
process.once("SIGINT", onSignal);
|
|
995
|
+
process.once("SIGTERM", onSignal);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// src/babel-traverse.ts
|
|
999
|
+
import traverseImport from "@babel/traverse";
|
|
1000
|
+
var traverse = typeof traverseImport === "function" ? traverseImport : traverseImport.default;
|
|
1001
|
+
var babel_traverse_default = traverse;
|
|
1002
|
+
|
|
1003
|
+
// src/patch-app-root.ts
|
|
1004
|
+
import * as t from "@babel/types";
|
|
1005
|
+
import fg from "fast-glob";
|
|
1006
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
1007
|
+
import { join as join6 } from "path";
|
|
1008
|
+
|
|
1009
|
+
// src/babel-generator.ts
|
|
1010
|
+
import generateImport from "@babel/generator";
|
|
1011
|
+
var generate = typeof generateImport === "function" ? generateImport : generateImport.default;
|
|
1012
|
+
var babel_generator_default = generate;
|
|
1013
|
+
|
|
1014
|
+
// src/parse-ts.ts
|
|
1015
|
+
import { parse } from "@babel/parser";
|
|
1016
|
+
var PARSE_OPTS = {
|
|
1017
|
+
sourceType: "module",
|
|
1018
|
+
plugins: ["typescript", "jsx"]
|
|
1019
|
+
};
|
|
1020
|
+
function parseTs(source, filename = "file.tsx") {
|
|
1021
|
+
return parse(source, {
|
|
1022
|
+
...PARSE_OPTS,
|
|
1023
|
+
sourceFilename: filename
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
function printTs(ast, source) {
|
|
1027
|
+
const out = babel_generator_default(ast, { retainLines: true }, source);
|
|
1028
|
+
return out.code.endsWith("\n") ? out.code : `${out.code}
|
|
1029
|
+
`;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// src/patch-app-root.ts
|
|
1033
|
+
var APP_CANDIDATES = [
|
|
1034
|
+
"src/App.tsx",
|
|
1035
|
+
"src/App.jsx",
|
|
1036
|
+
"src/main.tsx",
|
|
1037
|
+
"src/main.jsx"
|
|
1038
|
+
];
|
|
1039
|
+
function resolveAppFile(root) {
|
|
1040
|
+
for (const rel of APP_CANDIDATES) {
|
|
1041
|
+
const p = join6(root, rel);
|
|
1042
|
+
if (existsSync5(p)) return p;
|
|
1043
|
+
}
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
function hasOverlayImport(ast) {
|
|
1047
|
+
let found = false;
|
|
1048
|
+
babel_traverse_default(ast, {
|
|
1049
|
+
ImportDeclaration(path) {
|
|
1050
|
+
if (path.node.source.value === "@nuvio/overlay") found = true;
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
return found;
|
|
1054
|
+
}
|
|
1055
|
+
function hasDevShell(ast) {
|
|
1056
|
+
let found = false;
|
|
1057
|
+
babel_traverse_default(ast, {
|
|
1058
|
+
JSXElement(path) {
|
|
1059
|
+
const name = path.node.openingElement.name;
|
|
1060
|
+
if (t.isJSXIdentifier(name) && name.name === "NuvioDevShell") found = true;
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
return found;
|
|
1064
|
+
}
|
|
1065
|
+
function unwrapJsx(node) {
|
|
1066
|
+
if (!node) return null;
|
|
1067
|
+
if (t.isJSXElement(node) || t.isJSXFragment(node)) return node;
|
|
1068
|
+
if (t.isParenthesizedExpression(node)) return unwrapJsx(node.expression);
|
|
380
1069
|
return null;
|
|
381
1070
|
}
|
|
382
|
-
var devShellElement =
|
|
383
|
-
|
|
1071
|
+
var devShellElement = t.jsxElement(
|
|
1072
|
+
t.jsxOpeningElement(t.jsxIdentifier("NuvioDevShell"), [], true),
|
|
384
1073
|
null,
|
|
385
1074
|
[],
|
|
386
1075
|
true
|
|
@@ -391,15 +1080,15 @@ function appendDevShell(ast) {
|
|
|
391
1080
|
ReturnStatement(path) {
|
|
392
1081
|
const jsx = unwrapJsx(path.node.argument);
|
|
393
1082
|
if (!jsx) return;
|
|
394
|
-
if (
|
|
395
|
-
jsx.children.push(
|
|
1083
|
+
if (t.isJSXFragment(jsx)) {
|
|
1084
|
+
jsx.children.push(t.jsxText("\n "));
|
|
396
1085
|
jsx.children.push(devShellElement);
|
|
397
1086
|
patched = true;
|
|
398
1087
|
} else {
|
|
399
|
-
path.node.argument =
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
[jsx,
|
|
1088
|
+
path.node.argument = t.jsxFragment(
|
|
1089
|
+
t.jsxOpeningFragment(),
|
|
1090
|
+
t.jsxClosingFragment(),
|
|
1091
|
+
[jsx, t.jsxText("\n "), devShellElement]
|
|
403
1092
|
);
|
|
404
1093
|
patched = true;
|
|
405
1094
|
}
|
|
@@ -408,7 +1097,7 @@ function appendDevShell(ast) {
|
|
|
408
1097
|
return patched;
|
|
409
1098
|
}
|
|
410
1099
|
function patchAppRootFile(filePath) {
|
|
411
|
-
const source =
|
|
1100
|
+
const source = readFileSync6(filePath, "utf8");
|
|
412
1101
|
let ast;
|
|
413
1102
|
try {
|
|
414
1103
|
ast = parseTs(source, filePath);
|
|
@@ -420,26 +1109,26 @@ function patchAppRootFile(filePath) {
|
|
|
420
1109
|
}
|
|
421
1110
|
if (!hasOverlayImport(ast)) {
|
|
422
1111
|
ast.program.body.unshift(
|
|
423
|
-
|
|
1112
|
+
t.importDeclaration(
|
|
424
1113
|
[
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
1114
|
+
t.importSpecifier(
|
|
1115
|
+
t.identifier("NuvioDevShell"),
|
|
1116
|
+
t.identifier("NuvioDevShell")
|
|
428
1117
|
)
|
|
429
1118
|
],
|
|
430
|
-
|
|
1119
|
+
t.stringLiteral("@nuvio/overlay")
|
|
431
1120
|
)
|
|
432
1121
|
);
|
|
433
1122
|
}
|
|
434
1123
|
if (!hasDevShell(ast) && !appendDevShell(ast)) {
|
|
435
1124
|
return { ok: false, error: "no JSX return to patch" };
|
|
436
1125
|
}
|
|
437
|
-
|
|
1126
|
+
writeFileSync3(filePath, printTs(ast, source), "utf8");
|
|
438
1127
|
return { ok: true };
|
|
439
1128
|
}
|
|
440
1129
|
function appHasDevShell(filePath) {
|
|
441
|
-
if (!
|
|
442
|
-
const source =
|
|
1130
|
+
if (!existsSync5(filePath)) return false;
|
|
1131
|
+
const source = readFileSync6(filePath, "utf8");
|
|
443
1132
|
try {
|
|
444
1133
|
const ast = parseTs(source, filePath);
|
|
445
1134
|
return hasOverlayImport(ast) && hasDevShell(ast);
|
|
@@ -447,32 +1136,42 @@ function appHasDevShell(filePath) {
|
|
|
447
1136
|
return /NuvioDevShell/.test(source);
|
|
448
1137
|
}
|
|
449
1138
|
}
|
|
1139
|
+
function projectHasDevShell(root) {
|
|
1140
|
+
const appFile = resolveAppFile(root);
|
|
1141
|
+
if (appFile && appHasDevShell(appFile)) return true;
|
|
1142
|
+
const files = fg.sync(["src/**/*.{tsx,jsx}"], {
|
|
1143
|
+
cwd: root,
|
|
1144
|
+
absolute: true,
|
|
1145
|
+
onlyFiles: true
|
|
1146
|
+
});
|
|
1147
|
+
for (const file of files) {
|
|
1148
|
+
if (appHasDevShell(file)) return true;
|
|
1149
|
+
}
|
|
1150
|
+
return false;
|
|
1151
|
+
}
|
|
450
1152
|
|
|
451
1153
|
// src/patch-main-styles.ts
|
|
452
|
-
import { existsSync as
|
|
453
|
-
import { join as
|
|
1154
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
1155
|
+
import { join as join7 } from "path";
|
|
454
1156
|
var MAIN_CANDIDATES = ["src/main.tsx", "src/main.jsx", "main.tsx", "main.jsx"];
|
|
455
1157
|
var STYLE_IMPORT = 'import "@nuvio/overlay/style.css";';
|
|
456
1158
|
function overlayInstalledFromNpm(packageJsonPath) {
|
|
457
|
-
const pkg =
|
|
458
|
-
|
|
459
|
-
const raw = dev?.["@nuvio/overlay"];
|
|
460
|
-
if (!raw) return false;
|
|
461
|
-
return !raw.startsWith("workspace:") && !raw.startsWith("link:") && !raw.startsWith("file:");
|
|
1159
|
+
const pkg = readPackageJson(packageJsonPath);
|
|
1160
|
+
return nuvioOverlayLinkKind(pkg) === "npm";
|
|
462
1161
|
}
|
|
463
1162
|
function resolveMainEntry(root) {
|
|
464
1163
|
for (const rel of MAIN_CANDIDATES) {
|
|
465
|
-
const p =
|
|
466
|
-
if (
|
|
1164
|
+
const p = join7(root, rel);
|
|
1165
|
+
if (existsSync6(p)) return p;
|
|
467
1166
|
}
|
|
468
1167
|
return null;
|
|
469
1168
|
}
|
|
470
1169
|
function mainHasOverlayStyles(mainPath) {
|
|
471
|
-
const text =
|
|
1170
|
+
const text = readFileSync7(mainPath, "utf8");
|
|
472
1171
|
return text.includes("@nuvio/overlay/style.css") || text.includes("@nuvio/overlay/dist/style.css");
|
|
473
1172
|
}
|
|
474
1173
|
function patchMainOverlayStyles(mainPath) {
|
|
475
|
-
const text =
|
|
1174
|
+
const text = readFileSync7(mainPath, "utf8");
|
|
476
1175
|
if (text.includes("@nuvio/overlay/style.css") || text.includes("@nuvio/overlay/dist/style.css")) {
|
|
477
1176
|
return { ok: true, skipped: true };
|
|
478
1177
|
}
|
|
@@ -486,209 +1185,212 @@ function patchMainOverlayStyles(mainPath) {
|
|
|
486
1185
|
} else {
|
|
487
1186
|
lines.unshift(STYLE_IMPORT, "");
|
|
488
1187
|
}
|
|
489
|
-
|
|
1188
|
+
writeFileSync4(mainPath, lines.join("\n"));
|
|
490
1189
|
return { ok: true };
|
|
491
1190
|
}
|
|
492
1191
|
|
|
493
|
-
// src/patch-
|
|
494
|
-
import * as
|
|
495
|
-
import { readFileSync as
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
var ID_GLOB = ["src/**/*.{tsx,jsx}"];
|
|
502
|
-
function projectHasPageTitleId(root) {
|
|
503
|
-
const files = fg.sync(ID_GLOB, { cwd: root, absolute: true });
|
|
504
|
-
for (const file of files) {
|
|
505
|
-
const text = readFileSync6(file, "utf8");
|
|
506
|
-
if (/data-nuvio-id=["']page\.title["']/.test(text)) {
|
|
507
|
-
return true;
|
|
1192
|
+
// src/patch-vite-config.ts
|
|
1193
|
+
import * as t2 from "@babel/types";
|
|
1194
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
1195
|
+
function hasNuvioImport(ast) {
|
|
1196
|
+
let found = false;
|
|
1197
|
+
babel_traverse_default(ast, {
|
|
1198
|
+
ImportDeclaration(path) {
|
|
1199
|
+
if (path.node.source.value === "@nuvio/vite-plugin") found = true;
|
|
508
1200
|
}
|
|
509
|
-
}
|
|
510
|
-
return
|
|
1201
|
+
});
|
|
1202
|
+
return found;
|
|
511
1203
|
}
|
|
512
|
-
function
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
...files.filter(
|
|
518
|
-
(f) => !f.endsWith("App.tsx") && !f.endsWith("App.jsx")
|
|
519
|
-
)
|
|
520
|
-
];
|
|
521
|
-
const seen = /* @__PURE__ */ new Set();
|
|
522
|
-
const out = [];
|
|
523
|
-
for (const f of ordered) {
|
|
524
|
-
if (!seen.has(f) && files.includes(f)) {
|
|
525
|
-
seen.add(f);
|
|
526
|
-
out.push(f);
|
|
1204
|
+
function hasNuvioPluginCall(ast) {
|
|
1205
|
+
let found = false;
|
|
1206
|
+
babel_traverse_default(ast, {
|
|
1207
|
+
CallExpression(path) {
|
|
1208
|
+
if (t2.isIdentifier(path.node.callee, { name: "nuvio" })) found = true;
|
|
527
1209
|
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
if (!seen.has(f)) out.push(f);
|
|
531
|
-
}
|
|
532
|
-
return out;
|
|
1210
|
+
});
|
|
1211
|
+
return found;
|
|
533
1212
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
return { ok: false, error: "parse failed" };
|
|
543
|
-
}
|
|
1213
|
+
var OVERLAY_DEP = "@nuvio/overlay";
|
|
1214
|
+
function excludeListsOverlay(expr) {
|
|
1215
|
+
if (!expr || !t2.isArrayExpression(expr)) return false;
|
|
1216
|
+
return expr.elements.some(
|
|
1217
|
+
(el) => t2.isStringLiteral(el) && el.value === OVERLAY_DEP
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
function ensureOptimizeDepsExclude(ast) {
|
|
544
1221
|
let patched = false;
|
|
545
1222
|
babel_traverse_default(ast, {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
if (
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
1223
|
+
CallExpression(path) {
|
|
1224
|
+
const callee = path.node.callee;
|
|
1225
|
+
if (!t2.isIdentifier(callee, { name: "defineConfig" })) return;
|
|
1226
|
+
const arg = path.node.arguments[0];
|
|
1227
|
+
if (!t2.isObjectExpression(arg)) return;
|
|
1228
|
+
let optimizeDeps;
|
|
1229
|
+
for (const prop of arg.properties) {
|
|
1230
|
+
if (t2.isObjectProperty(prop) && t2.isIdentifier(prop.key, { name: "optimizeDeps" })) {
|
|
1231
|
+
optimizeDeps = prop;
|
|
1232
|
+
break;
|
|
554
1233
|
}
|
|
555
1234
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
1235
|
+
if (!optimizeDeps) {
|
|
1236
|
+
arg.properties.push(
|
|
1237
|
+
t2.objectProperty(
|
|
1238
|
+
t2.identifier("optimizeDeps"),
|
|
1239
|
+
t2.objectExpression([
|
|
1240
|
+
t2.objectProperty(
|
|
1241
|
+
t2.identifier("exclude"),
|
|
1242
|
+
t2.arrayExpression([t2.stringLiteral(OVERLAY_DEP)])
|
|
1243
|
+
)
|
|
1244
|
+
])
|
|
1245
|
+
)
|
|
1246
|
+
);
|
|
1247
|
+
patched = true;
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
if (!t2.isObjectExpression(optimizeDeps.value)) return;
|
|
1251
|
+
let excludeProp;
|
|
1252
|
+
for (const p of optimizeDeps.value.properties) {
|
|
1253
|
+
if (t2.isObjectProperty(p) && t2.isIdentifier(p.key, { name: "exclude" })) {
|
|
1254
|
+
excludeProp = p;
|
|
1255
|
+
break;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (!excludeProp) {
|
|
1259
|
+
optimizeDeps.value.properties.push(
|
|
1260
|
+
t2.objectProperty(
|
|
1261
|
+
t2.identifier("exclude"),
|
|
1262
|
+
t2.arrayExpression([t2.stringLiteral(OVERLAY_DEP)])
|
|
1263
|
+
)
|
|
1264
|
+
);
|
|
1265
|
+
patched = true;
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
if (t2.isArrayExpression(excludeProp.value) && !excludeListsOverlay(excludeProp.value)) {
|
|
1269
|
+
excludeProp.value.elements.push(t2.stringLiteral(OVERLAY_DEP));
|
|
1270
|
+
patched = true;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
return patched;
|
|
1275
|
+
}
|
|
1276
|
+
function viteConfigHasOverlayOptimizeExclude(filePath) {
|
|
1277
|
+
const source = readFileSync8(filePath, "utf8");
|
|
1278
|
+
return /optimizeDeps\s*:\s*\{[^}]*exclude\s*:\s*\[[^\]]*@nuvio\/overlay/.test(
|
|
1279
|
+
source
|
|
1280
|
+
) || /exclude\s*:\s*\[[^\]]*["']@nuvio\/overlay["']/.test(source);
|
|
1281
|
+
}
|
|
1282
|
+
function appendNuvioPlugin(ast) {
|
|
1283
|
+
let patched = false;
|
|
1284
|
+
babel_traverse_default(ast, {
|
|
1285
|
+
ObjectProperty(path) {
|
|
1286
|
+
if (!t2.isIdentifier(path.node.key, { name: "plugins" })) return;
|
|
1287
|
+
if (!t2.isArrayExpression(path.node.value)) return;
|
|
1288
|
+
path.node.value.elements.push(t2.callExpression(t2.identifier("nuvio"), []));
|
|
562
1289
|
patched = true;
|
|
563
1290
|
}
|
|
564
1291
|
});
|
|
565
|
-
|
|
566
|
-
writeFileSync4(filePath, printTs(ast, source), "utf8");
|
|
567
|
-
return { ok: true };
|
|
1292
|
+
return patched;
|
|
568
1293
|
}
|
|
569
|
-
function
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
1294
|
+
function patchViteConfigFile(filePath) {
|
|
1295
|
+
const source = readFileSync8(filePath, "utf8");
|
|
1296
|
+
let ast;
|
|
1297
|
+
try {
|
|
1298
|
+
ast = parseTs(source, filePath);
|
|
1299
|
+
} catch {
|
|
1300
|
+
return { ok: false, error: "parse failed" };
|
|
1301
|
+
}
|
|
1302
|
+
const depsPatched = ensureOptimizeDepsExclude(ast);
|
|
1303
|
+
const alreadyPlugin = hasNuvioImport(ast) && hasNuvioPluginCall(ast);
|
|
1304
|
+
if (alreadyPlugin && !depsPatched) {
|
|
1305
|
+
return { ok: true, skipped: true };
|
|
1306
|
+
}
|
|
1307
|
+
if (!hasNuvioImport(ast)) {
|
|
1308
|
+
ast.program.body.unshift(
|
|
1309
|
+
t2.importDeclaration(
|
|
1310
|
+
[t2.importSpecifier(t2.identifier("nuvio"), t2.identifier("nuvio"))],
|
|
1311
|
+
t2.stringLiteral("@nuvio/vite-plugin")
|
|
1312
|
+
)
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
if (!hasNuvioPluginCall(ast)) {
|
|
1316
|
+
if (!appendNuvioPlugin(ast)) {
|
|
1317
|
+
return { ok: false, error: "no static plugins array" };
|
|
588
1318
|
}
|
|
589
|
-
const outcome = patchFirstHeading(file);
|
|
590
|
-
if (outcome.ok) return { outcome, file };
|
|
591
1319
|
}
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// src/plan.ts
|
|
596
|
-
function createPlan(root, pm) {
|
|
597
|
-
const pmRun = pm === "pnpm" ? "pnpm dev" : pm === "yarn" ? "yarn dev" : pm === "bun" ? "bun run dev" : "npm run dev";
|
|
598
|
-
return {
|
|
599
|
-
root,
|
|
600
|
-
pm,
|
|
601
|
-
pmRun,
|
|
602
|
-
installCommand: "",
|
|
603
|
-
modify: [],
|
|
604
|
-
create: [],
|
|
605
|
-
warnings: [],
|
|
606
|
-
tier: "full",
|
|
607
|
-
failedSteps: []
|
|
608
|
-
};
|
|
1320
|
+
writeFileSync5(filePath, printTs(ast, source), "utf8");
|
|
1321
|
+
return { ok: true, skipped: alreadyPlugin && depsPatched };
|
|
609
1322
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
import { dirname, join as join6 } from "path";
|
|
619
|
-
import { fileURLToPath } from "url";
|
|
620
|
-
var CLI_ROOT = join6(dirname(fileURLToPath(import.meta.url)), "..");
|
|
621
|
-
function loadTemplate(name) {
|
|
622
|
-
return readFileSync8(join6(CLI_ROOT, "templates", name), "utf8");
|
|
1323
|
+
function viteConfigHasNuvio(filePath) {
|
|
1324
|
+
const source = readFileSync8(filePath, "utf8");
|
|
1325
|
+
try {
|
|
1326
|
+
const ast = parseTs(source, filePath);
|
|
1327
|
+
return hasNuvioImport(ast) && hasNuvioPluginCall(ast);
|
|
1328
|
+
} catch {
|
|
1329
|
+
return /nuvio\s*\(/.test(source);
|
|
1330
|
+
}
|
|
623
1331
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
1332
|
+
|
|
1333
|
+
// src/scan-ids.ts
|
|
1334
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
1335
|
+
import { join as join8 } from "path";
|
|
1336
|
+
import fg2 from "fast-glob";
|
|
1337
|
+
var ID_GLOB = ["src/**/*.{tsx,jsx}"];
|
|
1338
|
+
function projectHasPageTitleId(root) {
|
|
1339
|
+
const files = fg2.sync(ID_GLOB, { cwd: root, absolute: true });
|
|
1340
|
+
for (const file of files) {
|
|
1341
|
+
const text = readFileSync9(file, "utf8");
|
|
1342
|
+
if (/data-nuvio-id=["']page\.title["']/.test(text)) {
|
|
1343
|
+
return true;
|
|
1344
|
+
}
|
|
628
1345
|
}
|
|
629
|
-
return
|
|
1346
|
+
return false;
|
|
630
1347
|
}
|
|
631
|
-
function
|
|
632
|
-
const
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
const readme = join6(dir, "README.md");
|
|
648
|
-
writeFileSync5(
|
|
649
|
-
readme,
|
|
650
|
-
render(loadTemplate("README.pointer.md.tpl"), vars),
|
|
651
|
-
"utf8"
|
|
652
|
-
);
|
|
653
|
-
created.push("nuvio/README.md");
|
|
654
|
-
const agent = join6(dir, "AGENT.md");
|
|
655
|
-
if (!existsSync5(agent) || opts.forceAgent) {
|
|
656
|
-
writeFileSync5(agent, render(loadTemplate("AGENT.md.tpl"), vars), "utf8");
|
|
657
|
-
created.push("nuvio/AGENT.md");
|
|
1348
|
+
function findHeadingFiles(root) {
|
|
1349
|
+
const files = fg2.sync(ID_GLOB, { cwd: root, absolute: true });
|
|
1350
|
+
const ordered = [
|
|
1351
|
+
join8(root, "src/App.tsx"),
|
|
1352
|
+
join8(root, "src/App.jsx"),
|
|
1353
|
+
...files.filter(
|
|
1354
|
+
(f) => !f.endsWith("App.tsx") && !f.endsWith("App.jsx")
|
|
1355
|
+
)
|
|
1356
|
+
];
|
|
1357
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1358
|
+
const out = [];
|
|
1359
|
+
for (const f of ordered) {
|
|
1360
|
+
if (!seen.has(f) && files.includes(f)) {
|
|
1361
|
+
seen.add(f);
|
|
1362
|
+
out.push(f);
|
|
1363
|
+
}
|
|
658
1364
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
writeFileSync5(
|
|
662
|
-
todo,
|
|
663
|
-
render(loadTemplate("SETUP_TODO.md.tpl"), vars),
|
|
664
|
-
"utf8"
|
|
665
|
-
);
|
|
666
|
-
created.push("nuvio/SETUP_TODO.md");
|
|
1365
|
+
for (const f of files) {
|
|
1366
|
+
if (!seen.has(f)) out.push(f);
|
|
667
1367
|
}
|
|
668
|
-
return
|
|
1368
|
+
return out;
|
|
669
1369
|
}
|
|
670
1370
|
|
|
671
1371
|
// src/verify.ts
|
|
672
|
-
|
|
1372
|
+
function optimizeDepsSatisfied(viteConfigPath, packageJsonPath) {
|
|
1373
|
+
if (viteConfigHasOverlayOptimizeExclude(viteConfigPath)) return true;
|
|
1374
|
+
const pkg = readPackageJson(packageJsonPath);
|
|
1375
|
+
return nuvioOverlayLinkKind(pkg) === "workspace";
|
|
1376
|
+
}
|
|
673
1377
|
function verifyProject(root, packageJsonPath, viteConfigPath) {
|
|
674
|
-
const pkg =
|
|
675
|
-
const
|
|
676
|
-
const depsOk = Boolean(dev?.["@nuvio/vite-plugin"]) && Boolean(dev?.["@nuvio/overlay"]);
|
|
677
|
-
const appFile = resolveAppFile(root);
|
|
1378
|
+
const pkg = readPackageJson(packageJsonPath);
|
|
1379
|
+
const depsOk = hasNuvioPackages(pkg);
|
|
678
1380
|
const mainEntry = resolveMainEntry(root);
|
|
679
1381
|
return {
|
|
680
1382
|
deps: depsOk ? "OK" : "MISSING",
|
|
681
1383
|
vite: viteConfigHasNuvio(viteConfigPath) ? "OK" : "TODO",
|
|
682
1384
|
overlayCss: mainEntry && (mainHasOverlayStyles(mainEntry) || !overlayInstalledFromNpm(packageJsonPath)) ? "OK" : "TODO",
|
|
683
|
-
optimizeDeps:
|
|
684
|
-
shell:
|
|
1385
|
+
optimizeDeps: optimizeDepsSatisfied(viteConfigPath, packageJsonPath) ? "OK" : "TODO",
|
|
1386
|
+
shell: projectHasDevShell(root) ? "OK" : "TODO",
|
|
685
1387
|
starterId: projectHasPageTitleId(root) ? "OK" : "MISSING"
|
|
686
1388
|
};
|
|
687
1389
|
}
|
|
688
1390
|
function printVerification(v) {
|
|
689
1391
|
console.log("Verification:");
|
|
690
1392
|
console.log(
|
|
691
|
-
`
|
|
1393
|
+
` dependencies: @nuvio/vite-plugin, @nuvio/overlay \u2014 ${v.deps}`
|
|
692
1394
|
);
|
|
693
1395
|
console.log(` vite.config: nuvio() \u2014 ${v.vite}`);
|
|
694
1396
|
console.log(` main.tsx: @nuvio/overlay/style.css \u2014 ${v.overlayCss}`);
|
|
@@ -697,214 +1399,341 @@ function printVerification(v) {
|
|
|
697
1399
|
console.log(` Starter id page.title \u2014 ${v.starterId}`);
|
|
698
1400
|
}
|
|
699
1401
|
|
|
700
|
-
// src/
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
var SHUTDOWN_TIMEOUT_MS = 3e3;
|
|
726
|
-
var client = null;
|
|
727
|
-
var sessionAnonymousId = null;
|
|
728
|
-
var shutdownDone = false;
|
|
729
|
-
var signalHandlersRegistered = false;
|
|
730
|
-
function telemetryDebug(message, detail) {
|
|
731
|
-
if (process.env.NUVIO_TELEMETRY_DEBUG !== "1") return;
|
|
732
|
-
if (detail !== void 0) {
|
|
733
|
-
console.error(`[nuvio telemetry] ${message}`, detail);
|
|
734
|
-
return;
|
|
1402
|
+
// src/doctor.ts
|
|
1403
|
+
async function checkDevServerReachable(port) {
|
|
1404
|
+
const url = `http://127.0.0.1:${port}/`;
|
|
1405
|
+
try {
|
|
1406
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(1500) });
|
|
1407
|
+
if (res.ok) {
|
|
1408
|
+
return {
|
|
1409
|
+
id: "dev_server",
|
|
1410
|
+
label: `Dev server reachable (${url})`,
|
|
1411
|
+
status: "pass"
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
return {
|
|
1415
|
+
id: "dev_server",
|
|
1416
|
+
label: "Dev server reachable",
|
|
1417
|
+
status: "warn",
|
|
1418
|
+
detail: `HTTP ${res.status} from ${url}`
|
|
1419
|
+
};
|
|
1420
|
+
} catch {
|
|
1421
|
+
return {
|
|
1422
|
+
id: "dev_server",
|
|
1423
|
+
label: "Dev server reachable",
|
|
1424
|
+
status: "warn",
|
|
1425
|
+
detail: `Start pnpm dev \u2014 could not reach ${url}`
|
|
1426
|
+
};
|
|
735
1427
|
}
|
|
736
|
-
console.error(`[nuvio telemetry] ${message}`);
|
|
737
|
-
}
|
|
738
|
-
function isTelemetryEnabled() {
|
|
739
|
-
const flag = process.env.NUVIO_TELEMETRY;
|
|
740
|
-
if (flag === "0") return false;
|
|
741
|
-
if (flag?.toLowerCase() === "false") return false;
|
|
742
|
-
return true;
|
|
743
|
-
}
|
|
744
|
-
function posthogToken() {
|
|
745
|
-
return process.env.NUVIO_POSTHOG_TOKEN ?? NUVIO_POSTHOG_TOKEN;
|
|
746
1428
|
}
|
|
747
|
-
function
|
|
748
|
-
|
|
1429
|
+
function summarize(result) {
|
|
1430
|
+
const total = result.checks.length;
|
|
1431
|
+
const passed = result.passCount;
|
|
1432
|
+
const label = result.failCount > 0 ? "nuvio not ready" : result.warnCount > 0 ? "nuvio partially ready" : "nuvio ready";
|
|
1433
|
+
console.log(`
|
|
1434
|
+
Result: ${passed}/${total} passed \u2014 ${label}`);
|
|
749
1435
|
}
|
|
750
|
-
function
|
|
751
|
-
|
|
1436
|
+
async function runDoctor(opts) {
|
|
1437
|
+
let scan;
|
|
752
1438
|
try {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
if (
|
|
756
|
-
|
|
757
|
-
return
|
|
1439
|
+
scan = scanProject(opts.cwd);
|
|
1440
|
+
} catch (e) {
|
|
1441
|
+
if (e instanceof PreflightError) {
|
|
1442
|
+
console.error(e.message);
|
|
1443
|
+
return 1;
|
|
758
1444
|
}
|
|
759
|
-
|
|
1445
|
+
throw e;
|
|
760
1446
|
}
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1447
|
+
const { ctx, detectedLibraries, index } = scan;
|
|
1448
|
+
const pkg = ctx.packageJson;
|
|
1449
|
+
const projectName = String(pkg.name ?? "project");
|
|
1450
|
+
const verification = verifyProject(
|
|
1451
|
+
ctx.root,
|
|
1452
|
+
ctx.packageJsonPath,
|
|
1453
|
+
ctx.viteConfigPath
|
|
1454
|
+
);
|
|
1455
|
+
const checks = [
|
|
1456
|
+
{
|
|
1457
|
+
id: "deps_plugin",
|
|
1458
|
+
label: "@nuvio/vite-plugin installed",
|
|
1459
|
+
status: verification.deps === "OK" ? "pass" : "fail"
|
|
1460
|
+
},
|
|
1461
|
+
{
|
|
1462
|
+
id: "deps_overlay",
|
|
1463
|
+
label: "@nuvio/overlay installed",
|
|
1464
|
+
status: verification.deps === "OK" ? "pass" : "fail"
|
|
1465
|
+
},
|
|
1466
|
+
{
|
|
1467
|
+
id: "vite_plugin",
|
|
1468
|
+
label: "vite.config contains nuvio()",
|
|
1469
|
+
status: verification.vite === "OK" ? "pass" : "fail"
|
|
1470
|
+
},
|
|
1471
|
+
{
|
|
1472
|
+
id: "optimize_deps",
|
|
1473
|
+
label: "optimizeDeps.exclude includes @nuvio/overlay",
|
|
1474
|
+
status: verification.optimizeDeps === "OK" ? "pass" : "fail",
|
|
1475
|
+
detail: verification.optimizeDeps === "OK" && nuvioOverlayLinkKind(pkg) === "workspace" ? "workspace install \u2014 optional" : void 0
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
id: "overlay_css",
|
|
1479
|
+
label: "main entry imports @nuvio/overlay/style.css",
|
|
1480
|
+
status: verification.overlayCss === "OK" ? "pass" : "fail",
|
|
1481
|
+
detail: verification.overlayCss === "OK" && nuvioOverlayLinkKind(pkg) === "workspace" ? "workspace install \u2014 optional" : void 0
|
|
1482
|
+
},
|
|
1483
|
+
{
|
|
1484
|
+
id: "dev_shell",
|
|
1485
|
+
label: "NuvioDevShell mounted in app",
|
|
1486
|
+
status: verification.shell === "OK" ? "pass" : "fail"
|
|
1487
|
+
},
|
|
1488
|
+
{
|
|
1489
|
+
id: "tailwind",
|
|
1490
|
+
label: "Tailwind detected",
|
|
1491
|
+
status: ctx.tailwindOk ? "pass" : "warn",
|
|
1492
|
+
detail: ctx.tailwindOk ? void 0 : "Style edits may not apply visually without Tailwind"
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
id: "editable_hosts",
|
|
1496
|
+
label: "At least one data-nuvio-id indexed",
|
|
1497
|
+
status: index.entries.length > 0 ? "pass" : "fail",
|
|
1498
|
+
detail: index.entries.length > 0 ? `${index.entries.length} host(s)` : "Run dev \u2192 Make Editable, or add ids manually"
|
|
1499
|
+
}
|
|
1500
|
+
];
|
|
1501
|
+
if (detectedLibraries.length > 0) {
|
|
1502
|
+
checks.push({
|
|
1503
|
+
id: "libraries",
|
|
1504
|
+
label: "Component libraries detected",
|
|
1505
|
+
status: "pass",
|
|
1506
|
+
detail: detectedLibraries.join(", ")
|
|
1507
|
+
});
|
|
771
1508
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
if (!client) {
|
|
779
|
-
client = new PostHog(token, {
|
|
780
|
-
host: POSTHOG_HOST,
|
|
781
|
-
flushAt: 1,
|
|
782
|
-
flushInterval: 0
|
|
1509
|
+
if (index.duplicateErrors.length > 0) {
|
|
1510
|
+
checks.push({
|
|
1511
|
+
id: "duplicate_ids",
|
|
1512
|
+
label: "No duplicate data-nuvio-id values",
|
|
1513
|
+
status: "fail",
|
|
1514
|
+
detail: `${index.duplicateErrors.length} duplicate id(s) \u2014 run nuvio scan`
|
|
783
1515
|
});
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
1516
|
+
} else {
|
|
1517
|
+
checks.push({
|
|
1518
|
+
id: "duplicate_ids",
|
|
1519
|
+
label: "No duplicate data-nuvio-id values",
|
|
1520
|
+
status: "pass"
|
|
787
1521
|
});
|
|
788
1522
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
function sanitizeProps(props) {
|
|
792
|
-
if (!props) return void 0;
|
|
793
|
-
const out = {};
|
|
794
|
-
for (const [key, value] of Object.entries(props)) {
|
|
795
|
-
if (FORBIDDEN_PROP_KEYS.has(key)) continue;
|
|
796
|
-
if (value === void 0) continue;
|
|
797
|
-
if (typeof value === "string" && /[/\\]/.test(value)) continue;
|
|
798
|
-
out[key] = value;
|
|
1523
|
+
if (opts.checkDevServer !== false) {
|
|
1524
|
+
checks.push(await checkDevServerReachable(opts.devServerPort ?? 5173));
|
|
799
1525
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
const props = {
|
|
810
|
-
nuvio_version: NUVIO_VERSION,
|
|
811
|
-
os: process.platform,
|
|
812
|
-
arch: os.arch(),
|
|
813
|
-
node: process.version,
|
|
814
|
-
command
|
|
1526
|
+
const passCount = checks.filter((c) => c.status === "pass").length;
|
|
1527
|
+
const warnCount = checks.filter((c) => c.status === "warn").length;
|
|
1528
|
+
const failCount = checks.filter((c) => c.status === "fail").length;
|
|
1529
|
+
const result = {
|
|
1530
|
+
projectName,
|
|
1531
|
+
checks,
|
|
1532
|
+
passCount,
|
|
1533
|
+
warnCount,
|
|
1534
|
+
failCount
|
|
815
1535
|
};
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
arch: os.arch(),
|
|
824
|
-
node: process.version
|
|
1536
|
+
const pm = detectPackageManager(ctx.root);
|
|
1537
|
+
const telemetry = {
|
|
1538
|
+
...buildCliTelemetryProps(pm, ctx),
|
|
1539
|
+
pass_count: passCount,
|
|
1540
|
+
warn_count: warnCount,
|
|
1541
|
+
fail_count: failCount,
|
|
1542
|
+
ready: failCount === 0
|
|
825
1543
|
};
|
|
826
|
-
|
|
827
|
-
if (
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
props.has_tailwind = project.tailwindOk;
|
|
1544
|
+
captureCliEvent("doctor_run", telemetry);
|
|
1545
|
+
if (opts.json) {
|
|
1546
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1547
|
+
return failCount > 0 ? 1 : 0;
|
|
831
1548
|
}
|
|
832
|
-
|
|
1549
|
+
console.log(`nuvio doctor \u2014 ${projectName}
|
|
1550
|
+
`);
|
|
1551
|
+
for (const check of checks) {
|
|
1552
|
+
const icon = check.status === "pass" ? "\u2705" : check.status === "warn" ? "\u26A0" : "\u274C";
|
|
1553
|
+
const suffix = check.detail ? ` \u2014 ${check.detail}` : "";
|
|
1554
|
+
console.log(` ${icon} ${check.label}${suffix}`);
|
|
1555
|
+
}
|
|
1556
|
+
summarize(result);
|
|
1557
|
+
return failCount > 0 ? 1 : 0;
|
|
833
1558
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1559
|
+
|
|
1560
|
+
// src/init.ts
|
|
1561
|
+
import { createInterface } from "readline";
|
|
1562
|
+
|
|
1563
|
+
// src/install-packages.ts
|
|
1564
|
+
import { spawnSync } from "child_process";
|
|
1565
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
1566
|
+
function parseInstalledVersion(pkg, name) {
|
|
1567
|
+
const dev = pkg.devDependencies;
|
|
1568
|
+
const deps = pkg.dependencies;
|
|
1569
|
+
const raw = dev?.[name] ?? deps?.[name];
|
|
1570
|
+
if (!raw) return null;
|
|
1571
|
+
return raw.replace(/^[\^~]/, "");
|
|
1572
|
+
}
|
|
1573
|
+
function packagesNeedInstall(packageJsonPath, targetVersion) {
|
|
1574
|
+
const pkg = JSON.parse(readFileSync10(packageJsonPath, "utf8"));
|
|
1575
|
+
for (const name of ["@nuvio/vite-plugin", "@nuvio/overlay"]) {
|
|
1576
|
+
const v = parseInstalledVersion(pkg, name);
|
|
1577
|
+
if (v !== targetVersion) return true;
|
|
841
1578
|
}
|
|
842
|
-
return
|
|
1579
|
+
return false;
|
|
843
1580
|
}
|
|
844
|
-
function
|
|
845
|
-
|
|
1581
|
+
function runInstall(root, pm, version) {
|
|
1582
|
+
const cmd = installCommand(pm, version);
|
|
1583
|
+
const result = spawnSync(cmd, {
|
|
1584
|
+
cwd: root,
|
|
1585
|
+
shell: true,
|
|
1586
|
+
stdio: "inherit",
|
|
1587
|
+
env: process.env
|
|
1588
|
+
});
|
|
1589
|
+
if (result.status !== 0) {
|
|
1590
|
+
return {
|
|
1591
|
+
ok: false,
|
|
1592
|
+
message: `Install failed. Try manually:
|
|
1593
|
+
${cmd}`
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
return { ok: true };
|
|
846
1597
|
}
|
|
847
|
-
|
|
1598
|
+
|
|
1599
|
+
// src/patch-starter-id.ts
|
|
1600
|
+
import * as t3 from "@babel/types";
|
|
1601
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
|
|
1602
|
+
function patchFirstHeading(filePath) {
|
|
1603
|
+
const source = readFileSync11(filePath, "utf8");
|
|
1604
|
+
let ast;
|
|
848
1605
|
try {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1606
|
+
ast = parseTs(source, filePath);
|
|
1607
|
+
} catch {
|
|
1608
|
+
return { ok: false, error: "parse failed" };
|
|
1609
|
+
}
|
|
1610
|
+
let patched = false;
|
|
1611
|
+
babel_traverse_default(ast, {
|
|
1612
|
+
JSXOpeningElement(path) {
|
|
1613
|
+
if (patched) return;
|
|
1614
|
+
const name = path.node.name;
|
|
1615
|
+
if (!t3.isJSXIdentifier(name)) return;
|
|
1616
|
+
if (name.name !== "h1" && name.name !== "h2") return;
|
|
1617
|
+
for (const attr of path.node.attributes) {
|
|
1618
|
+
if (t3.isJSXAttribute(attr) && t3.isJSXIdentifier(attr.name, { name: "data-nuvio-id" })) {
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
path.node.attributes.push(
|
|
1623
|
+
t3.jsxAttribute(
|
|
1624
|
+
t3.jsxIdentifier("data-nuvio-id"),
|
|
1625
|
+
t3.stringLiteral("page.title")
|
|
1626
|
+
)
|
|
1627
|
+
);
|
|
1628
|
+
patched = true;
|
|
852
1629
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1630
|
+
});
|
|
1631
|
+
if (!patched) return { ok: false, error: "no h1/h2" };
|
|
1632
|
+
writeFileSync6(filePath, printTs(ast, source), "utf8");
|
|
1633
|
+
return { ok: true };
|
|
1634
|
+
}
|
|
1635
|
+
function patchStarterId(root) {
|
|
1636
|
+
const files = findHeadingFiles(root);
|
|
1637
|
+
for (const file of files) {
|
|
1638
|
+
const source = readFileSync11(file, "utf8");
|
|
1639
|
+
if (!/<h[12][\s>]/.test(source) && !/<>[\s\S]*<h[12]/.test(source)) {
|
|
1640
|
+
try {
|
|
1641
|
+
const ast = parseTs(source, file);
|
|
1642
|
+
let has = false;
|
|
1643
|
+
babel_traverse_default(ast, {
|
|
1644
|
+
JSXOpeningElement(path) {
|
|
1645
|
+
const name = path.node.name;
|
|
1646
|
+
if (t3.isJSXIdentifier(name) && (name.name === "h1" || name.name === "h2"))
|
|
1647
|
+
has = true;
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
if (!has) continue;
|
|
1651
|
+
} catch {
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
857
1654
|
}
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
distinctId,
|
|
861
|
-
event,
|
|
862
|
-
properties: sanitizeProps(props)
|
|
863
|
-
});
|
|
864
|
-
telemetryDebug(`captured ${event}`, { distinctId });
|
|
865
|
-
} catch (error) {
|
|
866
|
-
telemetryDebug(`capture failed for ${event}`, error);
|
|
1655
|
+
const outcome = patchFirstHeading(file);
|
|
1656
|
+
if (outcome.ok) return { outcome, file };
|
|
867
1657
|
}
|
|
1658
|
+
return { outcome: { ok: false, error: "no heading" } };
|
|
868
1659
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
]);
|
|
1660
|
+
|
|
1661
|
+
// src/plan.ts
|
|
1662
|
+
function createPlan(root, pm) {
|
|
1663
|
+
const pmRun = pm === "pnpm" ? "pnpm dev" : pm === "yarn" ? "yarn dev" : pm === "bun" ? "bun run dev" : "npm run dev";
|
|
1664
|
+
return {
|
|
1665
|
+
root,
|
|
1666
|
+
pm,
|
|
1667
|
+
pmRun,
|
|
1668
|
+
installCommand: "",
|
|
1669
|
+
modify: [],
|
|
1670
|
+
create: [],
|
|
1671
|
+
warnings: [],
|
|
1672
|
+
tier: "full",
|
|
1673
|
+
failedSteps: []
|
|
1674
|
+
};
|
|
885
1675
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1676
|
+
|
|
1677
|
+
// src/write-nuvio-folder.ts
|
|
1678
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
|
|
1679
|
+
import { dirname, join as join9 } from "path";
|
|
1680
|
+
import { fileURLToPath } from "url";
|
|
1681
|
+
import { DEFAULT_BRAND_CONFIG as DEFAULT_BRAND_CONFIG3, serializeBrandConfig } from "@nuvio/shared";
|
|
1682
|
+
var CLI_ROOT = join9(dirname(fileURLToPath(import.meta.url)), "..");
|
|
1683
|
+
function loadTemplate(name) {
|
|
1684
|
+
return readFileSync12(join9(CLI_ROOT, "templates", name), "utf8");
|
|
1685
|
+
}
|
|
1686
|
+
function render(tpl, vars) {
|
|
1687
|
+
let out = tpl;
|
|
1688
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
1689
|
+
out = out.replaceAll(`{{${key}}}`, value);
|
|
894
1690
|
}
|
|
1691
|
+
return out;
|
|
895
1692
|
}
|
|
896
|
-
function
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
})();
|
|
1693
|
+
function writeNuvioFolder(opts) {
|
|
1694
|
+
const dir = join9(opts.root, "nuvio");
|
|
1695
|
+
const created = [];
|
|
1696
|
+
mkdirSync2(dir, { recursive: true });
|
|
1697
|
+
const vars = {
|
|
1698
|
+
NUVIO_VERSION: opts.version,
|
|
1699
|
+
PM_RUN: opts.pmRun,
|
|
1700
|
+
FAILED_STEPS: opts.failedSteps.join(", ") || "(none)"
|
|
905
1701
|
};
|
|
906
|
-
|
|
907
|
-
|
|
1702
|
+
const startHere = join9(dir, "START_HERE.md");
|
|
1703
|
+
writeFileSync7(
|
|
1704
|
+
startHere,
|
|
1705
|
+
render(loadTemplate("START_HERE.md.tpl"), vars),
|
|
1706
|
+
"utf8"
|
|
1707
|
+
);
|
|
1708
|
+
created.push("nuvio/START_HERE.md");
|
|
1709
|
+
const readme = join9(dir, "README.md");
|
|
1710
|
+
writeFileSync7(
|
|
1711
|
+
readme,
|
|
1712
|
+
render(loadTemplate("README.pointer.md.tpl"), vars),
|
|
1713
|
+
"utf8"
|
|
1714
|
+
);
|
|
1715
|
+
created.push("nuvio/README.md");
|
|
1716
|
+
const agent = join9(dir, "AGENT.md");
|
|
1717
|
+
if (!existsSync7(agent) || opts.forceAgent) {
|
|
1718
|
+
writeFileSync7(agent, render(loadTemplate("AGENT.md.tpl"), vars), "utf8");
|
|
1719
|
+
created.push("nuvio/AGENT.md");
|
|
1720
|
+
}
|
|
1721
|
+
if (opts.failedSteps.length > 0) {
|
|
1722
|
+
const todo = join9(dir, "SETUP_TODO.md");
|
|
1723
|
+
writeFileSync7(
|
|
1724
|
+
todo,
|
|
1725
|
+
render(loadTemplate("SETUP_TODO.md.tpl"), vars),
|
|
1726
|
+
"utf8"
|
|
1727
|
+
);
|
|
1728
|
+
created.push("nuvio/SETUP_TODO.md");
|
|
1729
|
+
}
|
|
1730
|
+
const brand = join9(dir, "brand.json");
|
|
1731
|
+
if (!existsSync7(brand)) {
|
|
1732
|
+
writeFileSync7(brand, `${JSON.stringify(serializeBrandConfig(DEFAULT_BRAND_CONFIG3), null, 2)}
|
|
1733
|
+
`, "utf8");
|
|
1734
|
+
created.push("nuvio/brand.json");
|
|
1735
|
+
}
|
|
1736
|
+
return created;
|
|
908
1737
|
}
|
|
909
1738
|
|
|
910
1739
|
// src/init.ts
|
|
@@ -924,10 +1753,10 @@ async function confirm(plan) {
|
|
|
924
1753
|
console.log(`
|
|
925
1754
|
Install: ${plan.installCommand || "(skip)"}`);
|
|
926
1755
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
927
|
-
return new Promise((
|
|
1756
|
+
return new Promise((resolve5) => {
|
|
928
1757
|
rl.question("\nProceed? [y/N] ", (answer) => {
|
|
929
1758
|
rl.close();
|
|
930
|
-
|
|
1759
|
+
resolve5(/^y(es)?$/i.test(answer.trim()));
|
|
931
1760
|
});
|
|
932
1761
|
});
|
|
933
1762
|
}
|
|
@@ -1152,14 +1981,165 @@ async function runInit(opts) {
|
|
|
1152
1981
|
return plan.tier === "partial" || plan.tier === "full" ? 0 : 1;
|
|
1153
1982
|
}
|
|
1154
1983
|
|
|
1984
|
+
// src/scan-cmd.ts
|
|
1985
|
+
function runScan(opts) {
|
|
1986
|
+
let scan;
|
|
1987
|
+
try {
|
|
1988
|
+
scan = scanProject(opts.cwd);
|
|
1989
|
+
} catch (e) {
|
|
1990
|
+
if (e instanceof PreflightError) {
|
|
1991
|
+
console.error(e.message);
|
|
1992
|
+
return 1;
|
|
1993
|
+
}
|
|
1994
|
+
throw e;
|
|
1995
|
+
}
|
|
1996
|
+
const { ctx, detectedLibraries, index } = scan;
|
|
1997
|
+
const projectName = String(ctx.packageJson.name ?? "project");
|
|
1998
|
+
const hosts = index.entries.map((entry) => ({
|
|
1999
|
+
id: entry.id,
|
|
2000
|
+
file: relPath(ctx.root, entry.file),
|
|
2001
|
+
line: entry.line,
|
|
2002
|
+
column: entry.column,
|
|
2003
|
+
libraryHint: entry.libraryHint,
|
|
2004
|
+
classNameMode: entry.classNameMode
|
|
2005
|
+
}));
|
|
2006
|
+
const result = {
|
|
2007
|
+
projectName,
|
|
2008
|
+
hosts,
|
|
2009
|
+
hostCount: hosts.length,
|
|
2010
|
+
duplicateErrors: index.duplicateErrors.map((dup) => ({
|
|
2011
|
+
id: dup.id,
|
|
2012
|
+
occurrences: dup.occurrences.map((o) => ({
|
|
2013
|
+
file: relPath(ctx.root, o.file),
|
|
2014
|
+
line: o.line
|
|
2015
|
+
}))
|
|
2016
|
+
})),
|
|
2017
|
+
detectedLibraries,
|
|
2018
|
+
scannedFileCount: index.scannedFileCount
|
|
2019
|
+
};
|
|
2020
|
+
const pm = detectPackageManager(ctx.root);
|
|
2021
|
+
const telemetry = {
|
|
2022
|
+
...buildCliTelemetryProps(pm, ctx),
|
|
2023
|
+
host_count: result.hostCount,
|
|
2024
|
+
duplicate_count: result.duplicateErrors.length,
|
|
2025
|
+
library_count: detectedLibraries.length
|
|
2026
|
+
};
|
|
2027
|
+
captureCliEvent("scan_run", telemetry);
|
|
2028
|
+
if (opts.json) {
|
|
2029
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2030
|
+
return result.duplicateErrors.length > 0 ? 1 : 0;
|
|
2031
|
+
}
|
|
2032
|
+
console.log(`nuvio scan \u2014 ${result.hostCount} editable host(s)
|
|
2033
|
+
`);
|
|
2034
|
+
for (const host of hosts) {
|
|
2035
|
+
console.log(
|
|
2036
|
+
` ${host.id.padEnd(28)} ${host.file}:${host.line}`
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
if (result.duplicateErrors.length > 0) {
|
|
2040
|
+
console.log("");
|
|
2041
|
+
for (const dup of result.duplicateErrors) {
|
|
2042
|
+
const places = dup.occurrences.map((o) => `${o.file}:${o.line}`).join(", ");
|
|
2043
|
+
console.log(` \u274C duplicate id: ${dup.id} (${places}) \u2014 fix before apply`);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
if (detectedLibraries.length > 0) {
|
|
2047
|
+
console.log(`
|
|
2048
|
+
Libraries: ${detectedLibraries.join(", ")}`);
|
|
2049
|
+
}
|
|
2050
|
+
if (result.hostCount === 0) {
|
|
2051
|
+
console.log(
|
|
2052
|
+
"\n No hosts found \u2014 use Make Editable in the browser or add data-nuvio-id manually."
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
return result.duplicateErrors.length > 0 ? 1 : 0;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
// src/stats.ts
|
|
2059
|
+
import { readRuntimeVersions } from "@nuvio/vite-plugin/scan";
|
|
2060
|
+
function runStats(opts) {
|
|
2061
|
+
let scan;
|
|
2062
|
+
try {
|
|
2063
|
+
scan = scanProject(opts.cwd);
|
|
2064
|
+
} catch (e) {
|
|
2065
|
+
if (e instanceof PreflightError) {
|
|
2066
|
+
console.error(e.message);
|
|
2067
|
+
return 1;
|
|
2068
|
+
}
|
|
2069
|
+
throw e;
|
|
2070
|
+
}
|
|
2071
|
+
const { ctx, detectedLibraries, index } = scan;
|
|
2072
|
+
const projectName = String(ctx.packageJson.name ?? "project");
|
|
2073
|
+
const taggedFiles = new Set(
|
|
2074
|
+
index.entries.map((e) => relPath(ctx.root, e.file))
|
|
2075
|
+
).size;
|
|
2076
|
+
const classNameModes = aggregateClassNameModes(index.entries);
|
|
2077
|
+
const tableHosts = index.entries.filter(isTableHost).length;
|
|
2078
|
+
const versions = readRuntimeVersions(ctx.root);
|
|
2079
|
+
const result = {
|
|
2080
|
+
projectName,
|
|
2081
|
+
editableHosts: index.entries.length,
|
|
2082
|
+
taggedFiles,
|
|
2083
|
+
scannedFiles: index.scannedFileCount,
|
|
2084
|
+
duplicateIds: index.duplicateErrors.length,
|
|
2085
|
+
tableHosts,
|
|
2086
|
+
detectedLibraries,
|
|
2087
|
+
tailwindVersion: versions.tailwindVersion,
|
|
2088
|
+
classNameModes
|
|
2089
|
+
};
|
|
2090
|
+
const pm = detectPackageManager(ctx.root);
|
|
2091
|
+
const telemetry = {
|
|
2092
|
+
...buildCliTelemetryProps(pm, ctx),
|
|
2093
|
+
editable_hosts: result.editableHosts,
|
|
2094
|
+
tagged_files: result.taggedFiles,
|
|
2095
|
+
duplicate_ids: result.duplicateIds,
|
|
2096
|
+
table_hosts: result.tableHosts,
|
|
2097
|
+
library_count: detectedLibraries.length
|
|
2098
|
+
};
|
|
2099
|
+
captureCliEvent("stats_run", telemetry);
|
|
2100
|
+
if (opts.json) {
|
|
2101
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2102
|
+
return 0;
|
|
2103
|
+
}
|
|
2104
|
+
console.log("nuvio stats\n");
|
|
2105
|
+
console.log(` Editable hosts: ${result.editableHosts}`);
|
|
2106
|
+
console.log(` Tagged files: ${result.taggedFiles}`);
|
|
2107
|
+
console.log(` Files scanned: ${result.scannedFiles}`);
|
|
2108
|
+
console.log(
|
|
2109
|
+
` Libraries detected: ${result.detectedLibraries.length > 0 ? result.detectedLibraries.join(", ") : "none"}`
|
|
2110
|
+
);
|
|
2111
|
+
console.log(` Table hosts: ${result.tableHosts}`);
|
|
2112
|
+
console.log(` Duplicate ids: ${result.duplicateIds}`);
|
|
2113
|
+
if (result.tailwindVersion) {
|
|
2114
|
+
console.log(` Tailwind version: ${result.tailwindVersion}`);
|
|
2115
|
+
}
|
|
2116
|
+
const modeParts = Object.entries(result.classNameModes).sort(([a], [b]) => a.localeCompare(b)).map(([mode, count]) => `${mode} ${count}`);
|
|
2117
|
+
if (modeParts.length > 0) {
|
|
2118
|
+
console.log(` Class modes: ${modeParts.join(", ")}`);
|
|
2119
|
+
}
|
|
2120
|
+
return 0;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
1155
2123
|
// src/cli.ts
|
|
1156
2124
|
function printHelp() {
|
|
1157
2125
|
console.log(`nuvio \u2014 CLI for React + Vite
|
|
1158
2126
|
|
|
1159
2127
|
Usage:
|
|
1160
2128
|
nuvio init [options]
|
|
2129
|
+
nuvio doctor [options]
|
|
2130
|
+
nuvio scan [options]
|
|
2131
|
+
nuvio stats [options]
|
|
2132
|
+
nuvio coverage verify [options]
|
|
2133
|
+
nuvio brand scan [options]
|
|
2134
|
+
nuvio brand apply [options]
|
|
2135
|
+
|
|
2136
|
+
Common options:
|
|
2137
|
+
--cwd <path> Project root (default: current directory)
|
|
2138
|
+
--json Machine-readable output (doctor, scan, stats)
|
|
2139
|
+
--verbose Show error stacks
|
|
2140
|
+
-h, --help Show help
|
|
1161
2141
|
|
|
1162
|
-
|
|
2142
|
+
Init options:
|
|
1163
2143
|
--yes Skip confirmation
|
|
1164
2144
|
--no-install Patch files only; do not run package manager install
|
|
1165
2145
|
--dry-run Show plan only (still prompts unless --yes / CI)
|
|
@@ -1167,16 +2147,39 @@ Options:
|
|
|
1167
2147
|
--strict Fail if Tailwind is not detected
|
|
1168
2148
|
--skip-tailwind-check Do not warn when Tailwind is missing
|
|
1169
2149
|
--force-agent Overwrite nuvio/AGENT.md
|
|
1170
|
-
--cwd <path> Project root (default: current directory)
|
|
1171
|
-
--verbose Show error stacks
|
|
1172
|
-
-h, --help Show help
|
|
1173
2150
|
|
|
1174
|
-
|
|
1175
|
-
|
|
2151
|
+
Doctor options:
|
|
2152
|
+
--skip-dev-server Skip localhost dev-server health check
|
|
2153
|
+
|
|
2154
|
+
Coverage verify options:
|
|
2155
|
+
--page <slug> Page slug (loads nuvio/pages/<slug>.pcc.yaml)
|
|
2156
|
+
--manifest <path> Explicit PCC manifest path (overrides --page)
|
|
2157
|
+
--all Verify every manifest in nuvio/pages/
|
|
2158
|
+
|
|
2159
|
+
Brand scan options:
|
|
2160
|
+
--page <slug> Page slug (loads nuvio/pages/<slug>.pcc.yaml)
|
|
2161
|
+
--manifest <path> Explicit PCC manifest path (overrides --page)
|
|
2162
|
+
--all Scan every manifest in nuvio/pages/
|
|
2163
|
+
|
|
2164
|
+
Brand apply options:
|
|
2165
|
+
--page <slug> Page slug (loads nuvio/pages/<slug>.pcc.yaml)
|
|
2166
|
+
--manifest <path> Explicit PCC manifest path (overrides --page)
|
|
2167
|
+
--all Apply to every manifest in nuvio/pages/
|
|
2168
|
+
--dry-run Report targets without writing source files
|
|
2169
|
+
|
|
2170
|
+
Examples:
|
|
1176
2171
|
pnpm dlx @nuvio/cli init --yes
|
|
2172
|
+
pnpm dlx @nuvio/cli doctor
|
|
2173
|
+
pnpm dlx @nuvio/cli scan --json
|
|
2174
|
+
pnpm dlx @nuvio/cli stats
|
|
2175
|
+
pnpm dlx @nuvio/cli coverage verify --page dashboard --cwd apps/tailadmin-dogfood
|
|
2176
|
+
pnpm dlx @nuvio/cli coverage verify --all --cwd apps/tailadmin-dogfood
|
|
2177
|
+
pnpm dlx @nuvio/cli brand scan --page dashboard --cwd apps/tailadmin-dogfood
|
|
2178
|
+
pnpm dlx @nuvio/cli brand scan --all --cwd apps/tailadmin-dogfood
|
|
2179
|
+
pnpm dlx @nuvio/cli brand apply --all --cwd apps/tailadmin-dogfood
|
|
1177
2180
|
`);
|
|
1178
2181
|
}
|
|
1179
|
-
function
|
|
2182
|
+
function parseInitArgs(argv) {
|
|
1180
2183
|
const args = argv.slice(2);
|
|
1181
2184
|
let command = null;
|
|
1182
2185
|
const opts = { cwd: process.cwd() };
|
|
@@ -1201,7 +2204,7 @@ function parseArgs(argv) {
|
|
|
1201
2204
|
else if (arg === "--pm") {
|
|
1202
2205
|
opts.pm = args[++i];
|
|
1203
2206
|
} else if (arg === "--cwd") {
|
|
1204
|
-
opts.cwd =
|
|
2207
|
+
opts.cwd = resolve4(args[++i] ?? ".");
|
|
1205
2208
|
} else if (arg.startsWith("-")) {
|
|
1206
2209
|
console.error(`Unknown option: ${arg}`);
|
|
1207
2210
|
help = true;
|
|
@@ -1209,10 +2212,161 @@ function parseArgs(argv) {
|
|
|
1209
2212
|
}
|
|
1210
2213
|
return { command, opts, help };
|
|
1211
2214
|
}
|
|
2215
|
+
function parseProjectCommandArgs(argv, command) {
|
|
2216
|
+
const args = argv.slice(2);
|
|
2217
|
+
const common = { cwd: process.cwd() };
|
|
2218
|
+
const doctor = { ...common };
|
|
2219
|
+
let help = false;
|
|
2220
|
+
let i = args[0] === command ? 1 : 0;
|
|
2221
|
+
for (; i < args.length; i++) {
|
|
2222
|
+
const arg = args[i];
|
|
2223
|
+
if (arg === "-h" || arg === "--help") {
|
|
2224
|
+
help = true;
|
|
2225
|
+
continue;
|
|
2226
|
+
}
|
|
2227
|
+
if (arg === "--json") {
|
|
2228
|
+
common.json = true;
|
|
2229
|
+
doctor.json = true;
|
|
2230
|
+
} else if (arg === "--verbose") {
|
|
2231
|
+
common.verbose = true;
|
|
2232
|
+
doctor.verbose = true;
|
|
2233
|
+
} else if (arg === "--cwd") {
|
|
2234
|
+
const cwd = resolve4(args[++i] ?? ".");
|
|
2235
|
+
common.cwd = cwd;
|
|
2236
|
+
doctor.cwd = cwd;
|
|
2237
|
+
} else if (arg === "--skip-dev-server") {
|
|
2238
|
+
doctor.skipDevServer = true;
|
|
2239
|
+
} else if (arg.startsWith("-")) {
|
|
2240
|
+
console.error(`Unknown option: ${arg}`);
|
|
2241
|
+
help = true;
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
return { command, common, doctor, help };
|
|
2245
|
+
}
|
|
2246
|
+
function parseCoverageVerifyArgs(argv) {
|
|
2247
|
+
const args = argv.slice(2);
|
|
2248
|
+
const common = { cwd: process.cwd() };
|
|
2249
|
+
let help = false;
|
|
2250
|
+
let page;
|
|
2251
|
+
let manifest;
|
|
2252
|
+
let all = false;
|
|
2253
|
+
let i = 0;
|
|
2254
|
+
if (args[0] === "coverage") {
|
|
2255
|
+
i = 1;
|
|
2256
|
+
}
|
|
2257
|
+
const subcommand = args[i] === "verify" ? "verify" : "";
|
|
2258
|
+
if (subcommand) {
|
|
2259
|
+
i += 1;
|
|
2260
|
+
}
|
|
2261
|
+
for (; i < args.length; i++) {
|
|
2262
|
+
const arg = args[i];
|
|
2263
|
+
if (arg === "-h" || arg === "--help") {
|
|
2264
|
+
help = true;
|
|
2265
|
+
continue;
|
|
2266
|
+
}
|
|
2267
|
+
if (arg === "--json") {
|
|
2268
|
+
common.json = true;
|
|
2269
|
+
} else if (arg === "--verbose") {
|
|
2270
|
+
common.verbose = true;
|
|
2271
|
+
} else if (arg === "--cwd") {
|
|
2272
|
+
common.cwd = resolve4(args[++i] ?? ".");
|
|
2273
|
+
} else if (arg === "--page") {
|
|
2274
|
+
page = args[++i];
|
|
2275
|
+
} else if (arg === "--manifest") {
|
|
2276
|
+
manifest = resolve4(args[++i] ?? "");
|
|
2277
|
+
} else if (arg === "--all") {
|
|
2278
|
+
all = true;
|
|
2279
|
+
} else if (arg.startsWith("-")) {
|
|
2280
|
+
console.error(`Unknown option: ${arg}`);
|
|
2281
|
+
help = true;
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
return { command: "coverage", subcommand, common, page, manifest, all, help };
|
|
2285
|
+
}
|
|
2286
|
+
function parseBrandArgs(argv) {
|
|
2287
|
+
const args = argv.slice(2);
|
|
2288
|
+
const common = { cwd: process.cwd() };
|
|
2289
|
+
let help = false;
|
|
2290
|
+
let page;
|
|
2291
|
+
let manifest;
|
|
2292
|
+
let all = false;
|
|
2293
|
+
let dryRun = false;
|
|
2294
|
+
let i = 0;
|
|
2295
|
+
if (args[0] === "brand") {
|
|
2296
|
+
i = 1;
|
|
2297
|
+
}
|
|
2298
|
+
const subArg = args[i];
|
|
2299
|
+
const subcommand = subArg === "scan" || subArg === "apply" ? subArg : "";
|
|
2300
|
+
if (subcommand) {
|
|
2301
|
+
i += 1;
|
|
2302
|
+
}
|
|
2303
|
+
for (; i < args.length; i++) {
|
|
2304
|
+
const arg = args[i];
|
|
2305
|
+
if (arg === "-h" || arg === "--help") {
|
|
2306
|
+
help = true;
|
|
2307
|
+
continue;
|
|
2308
|
+
}
|
|
2309
|
+
if (arg === "--json") {
|
|
2310
|
+
common.json = true;
|
|
2311
|
+
} else if (arg === "--verbose") {
|
|
2312
|
+
common.verbose = true;
|
|
2313
|
+
} else if (arg === "--cwd") {
|
|
2314
|
+
common.cwd = resolve4(args[++i] ?? ".");
|
|
2315
|
+
} else if (arg === "--page") {
|
|
2316
|
+
page = args[++i];
|
|
2317
|
+
} else if (arg === "--manifest") {
|
|
2318
|
+
manifest = resolve4(args[++i] ?? "");
|
|
2319
|
+
} else if (arg === "--all") {
|
|
2320
|
+
all = true;
|
|
2321
|
+
} else if (arg === "--dry-run") {
|
|
2322
|
+
dryRun = true;
|
|
2323
|
+
} else if (arg.startsWith("-")) {
|
|
2324
|
+
console.error(`Unknown option: ${arg}`);
|
|
2325
|
+
help = true;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
return { command: "brand", subcommand, common, page, manifest, all, dryRun, help };
|
|
2329
|
+
}
|
|
1212
2330
|
async function runCli(argv) {
|
|
1213
2331
|
registerTelemetrySignalHandlers();
|
|
1214
|
-
const
|
|
1215
|
-
|
|
2332
|
+
const rawCommand = argv[2] ?? null;
|
|
2333
|
+
const isCoverageCmd = rawCommand === "coverage";
|
|
2334
|
+
const isBrandCmd = rawCommand === "brand";
|
|
2335
|
+
const isProjectCmd = rawCommand === "doctor" || rawCommand === "scan" || rawCommand === "stats" || isCoverageCmd || isBrandCmd;
|
|
2336
|
+
let help = false;
|
|
2337
|
+
let command = rawCommand;
|
|
2338
|
+
let initOpts = { cwd: process.cwd() };
|
|
2339
|
+
let commonOpts = { cwd: process.cwd() };
|
|
2340
|
+
let doctorOpts = { cwd: process.cwd() };
|
|
2341
|
+
let coverageOpts = null;
|
|
2342
|
+
let brandOpts = null;
|
|
2343
|
+
if (isBrandCmd) {
|
|
2344
|
+
brandOpts = parseBrandArgs(argv);
|
|
2345
|
+
help = brandOpts.help;
|
|
2346
|
+
command = brandOpts.command;
|
|
2347
|
+
commonOpts = brandOpts.common;
|
|
2348
|
+
} else if (isCoverageCmd) {
|
|
2349
|
+
coverageOpts = parseCoverageVerifyArgs(argv);
|
|
2350
|
+
help = coverageOpts.help;
|
|
2351
|
+
command = coverageOpts.command;
|
|
2352
|
+
commonOpts = coverageOpts.common;
|
|
2353
|
+
} else if (isProjectCmd) {
|
|
2354
|
+
const parsed = parseProjectCommandArgs(argv, rawCommand);
|
|
2355
|
+
help = parsed.help;
|
|
2356
|
+
command = parsed.command;
|
|
2357
|
+
commonOpts = parsed.common;
|
|
2358
|
+
doctorOpts = parsed.doctor;
|
|
2359
|
+
} else {
|
|
2360
|
+
const parsed = parseInitArgs(argv);
|
|
2361
|
+
help = parsed.help;
|
|
2362
|
+
command = parsed.command;
|
|
2363
|
+
initOpts = parsed.opts;
|
|
2364
|
+
}
|
|
2365
|
+
const cwd = isProjectCmd ? commonOpts.cwd : initOpts.cwd;
|
|
2366
|
+
captureCliInvoked(
|
|
2367
|
+
resolveCliInvokedCommand(help, command),
|
|
2368
|
+
isProjectCmd ? void 0 : initOpts.pm
|
|
2369
|
+
);
|
|
1216
2370
|
try {
|
|
1217
2371
|
if (help) {
|
|
1218
2372
|
printHelp();
|
|
@@ -1222,19 +2376,78 @@ async function runCli(argv) {
|
|
|
1222
2376
|
printHelp();
|
|
1223
2377
|
return 1;
|
|
1224
2378
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
2379
|
+
switch (command) {
|
|
2380
|
+
case "init":
|
|
2381
|
+
return await runInit(initOpts);
|
|
2382
|
+
case "doctor":
|
|
2383
|
+
return await runDoctor({
|
|
2384
|
+
cwd: doctorOpts.cwd,
|
|
2385
|
+
json: doctorOpts.json,
|
|
2386
|
+
checkDevServer: !doctorOpts.skipDevServer
|
|
2387
|
+
});
|
|
2388
|
+
case "scan":
|
|
2389
|
+
return runScan({ cwd: commonOpts.cwd, json: commonOpts.json });
|
|
2390
|
+
case "stats":
|
|
2391
|
+
return runStats({ cwd: commonOpts.cwd, json: commonOpts.json });
|
|
2392
|
+
case "coverage": {
|
|
2393
|
+
if (!coverageOpts || coverageOpts.subcommand !== "verify") {
|
|
2394
|
+
console.error("Usage: nuvio coverage verify --page <slug>");
|
|
2395
|
+
printHelp();
|
|
2396
|
+
return 1;
|
|
2397
|
+
}
|
|
2398
|
+
if (!coverageOpts.page && !coverageOpts.manifest && !coverageOpts.all) {
|
|
2399
|
+
console.error("Either --page, --manifest, or --all is required");
|
|
2400
|
+
return 2;
|
|
2401
|
+
}
|
|
2402
|
+
return runCoverageVerify({
|
|
2403
|
+
cwd: coverageOpts.common.cwd,
|
|
2404
|
+
page: coverageOpts.page,
|
|
2405
|
+
manifest: coverageOpts.manifest,
|
|
2406
|
+
all: coverageOpts.all,
|
|
2407
|
+
json: coverageOpts.common.json
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
case "brand": {
|
|
2411
|
+
if (!brandOpts || brandOpts.subcommand !== "scan" && brandOpts.subcommand !== "apply") {
|
|
2412
|
+
console.error("Usage: nuvio brand scan|apply --page <slug>");
|
|
2413
|
+
printHelp();
|
|
2414
|
+
return 1;
|
|
2415
|
+
}
|
|
2416
|
+
if (!brandOpts.page && !brandOpts.manifest && !brandOpts.all) {
|
|
2417
|
+
console.error("Either --page, --manifest, or --all is required");
|
|
2418
|
+
return 2;
|
|
2419
|
+
}
|
|
2420
|
+
if (brandOpts.subcommand === "scan") {
|
|
2421
|
+
return runBrandScan({
|
|
2422
|
+
cwd: brandOpts.common.cwd,
|
|
2423
|
+
page: brandOpts.page,
|
|
2424
|
+
manifest: brandOpts.manifest,
|
|
2425
|
+
all: brandOpts.all,
|
|
2426
|
+
json: brandOpts.common.json
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
return runBrandApply({
|
|
2430
|
+
cwd: brandOpts.common.cwd,
|
|
2431
|
+
page: brandOpts.page,
|
|
2432
|
+
manifest: brandOpts.manifest,
|
|
2433
|
+
all: brandOpts.all,
|
|
2434
|
+
dryRun: brandOpts.dryRun,
|
|
2435
|
+
json: brandOpts.common.json
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
default:
|
|
2439
|
+
console.error(`Unknown command: ${command}`);
|
|
2440
|
+
printHelp();
|
|
2441
|
+
return 1;
|
|
1229
2442
|
}
|
|
1230
|
-
return await runInit(opts);
|
|
1231
2443
|
} catch (e) {
|
|
1232
|
-
const pm = detectPackageManager(
|
|
2444
|
+
const pm = detectPackageManager(cwd, initOpts.pm);
|
|
1233
2445
|
captureCliEvent("nuvio_init_failed", {
|
|
1234
2446
|
...buildCliTelemetryProps(pm),
|
|
1235
2447
|
error_code: "unexpected_error"
|
|
1236
2448
|
});
|
|
1237
|
-
|
|
2449
|
+
const verbose = isProjectCmd ? commonOpts.verbose : initOpts.verbose;
|
|
2450
|
+
if (verbose) console.error(e);
|
|
1238
2451
|
else console.error("Something went wrong. Run with --verbose for details.");
|
|
1239
2452
|
return 2;
|
|
1240
2453
|
} finally {
|