@nuvio/vite-plugin 0.1.0 → 0.5.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 +15 -0
- package/dist/chunk-KTNIIC2D.js +15074 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +63 -207
- package/dist/nuvio-dev-session.d.ts +20 -0
- package/dist/nuvio-dev-session.js +462 -0
- package/package.json +8 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
|
|
3
3
|
interface NuvioPluginOptions {
|
|
4
|
+
/** Master switch; false disables index + WS server. */
|
|
5
|
+
enabled?: boolean;
|
|
4
6
|
/** Glob patterns relative to Vite config root (see DEFAULT_GLOBS). */
|
|
5
7
|
scanGlobs?: string[];
|
|
6
8
|
/** Log client ops (no source file contents). */
|
|
7
9
|
verbose?: boolean;
|
|
10
|
+
/** className parsing mode: strict literals (default) or simple cn/clsx strings. */
|
|
11
|
+
classNameMode?: "literal-only" | "cn-basic";
|
|
8
12
|
}
|
|
9
13
|
/**
|
|
10
14
|
* Nuvio Vite plugin — Phase 1: WebSocket protocol + dev-time source index + selection ack.
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildSourceIndex,
|
|
3
|
+
extractIdsFromSource,
|
|
4
|
+
pathnameFromUpgradeUrl,
|
|
5
|
+
pickBestSourceIndex,
|
|
6
|
+
readRuntimeVersions
|
|
7
|
+
} from "./chunk-KTNIIC2D.js";
|
|
8
|
+
|
|
1
9
|
// src/index.ts
|
|
2
|
-
import
|
|
3
|
-
import
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
4
12
|
import { WebSocket } from "ws";
|
|
5
13
|
import { WebSocketServer } from "ws";
|
|
6
14
|
import { applyPatchToSource } from "@nuvio/ast-engine";
|
|
@@ -11,190 +19,6 @@ import {
|
|
|
11
19
|
serializeServerMessage
|
|
12
20
|
} from "@nuvio/shared";
|
|
13
21
|
import { assertPathWithinRoot } from "@nuvio/shared/secure-path";
|
|
14
|
-
|
|
15
|
-
// src/upgrade-url.ts
|
|
16
|
-
function pathnameFromUpgradeUrl(url) {
|
|
17
|
-
if (!url) {
|
|
18
|
-
return "";
|
|
19
|
-
}
|
|
20
|
-
try {
|
|
21
|
-
return new URL(url, "http://localhost").pathname;
|
|
22
|
-
} catch {
|
|
23
|
-
return "";
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// src/source-index.ts
|
|
28
|
-
import * as fs from "fs";
|
|
29
|
-
import * as path from "path";
|
|
30
|
-
import { parse } from "@babel/parser";
|
|
31
|
-
import traverseImport from "@babel/traverse";
|
|
32
|
-
import fg from "fast-glob";
|
|
33
|
-
function getTraverseFn() {
|
|
34
|
-
if (typeof traverseImport === "function") {
|
|
35
|
-
return traverseImport;
|
|
36
|
-
}
|
|
37
|
-
const d = traverseImport.default;
|
|
38
|
-
if (typeof d === "function") {
|
|
39
|
-
return d;
|
|
40
|
-
}
|
|
41
|
-
throw new Error("[Nuvio] @babel/traverse did not resolve to a callable export");
|
|
42
|
-
}
|
|
43
|
-
var WRAPPER_TAGS = /* @__PURE__ */ new Set(["EditableText", "EditableContainer"]);
|
|
44
|
-
function extractIdsFromSource(fileAbs, code) {
|
|
45
|
-
const acc = [];
|
|
46
|
-
let ast;
|
|
47
|
-
try {
|
|
48
|
-
ast = parse(code, {
|
|
49
|
-
sourceType: "module",
|
|
50
|
-
plugins: ["typescript", "jsx"],
|
|
51
|
-
sourceFilename: fileAbs
|
|
52
|
-
});
|
|
53
|
-
} catch {
|
|
54
|
-
return acc;
|
|
55
|
-
}
|
|
56
|
-
const traverseFn = getTraverseFn();
|
|
57
|
-
traverseFn(ast, {
|
|
58
|
-
JSXOpeningElement(p) {
|
|
59
|
-
const opening = p.node;
|
|
60
|
-
let tagName = "";
|
|
61
|
-
if (opening.name.type === "JSXIdentifier") {
|
|
62
|
-
tagName = opening.name.name;
|
|
63
|
-
} else {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
for (const attr of opening.attributes) {
|
|
67
|
-
if (attr.type !== "JSXAttribute") {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
if (attr.name.type !== "JSXIdentifier") {
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
const prop = attr.name.name;
|
|
74
|
-
const loc = attr.loc?.start ?? opening.loc?.start;
|
|
75
|
-
const line = loc?.line ?? 1;
|
|
76
|
-
const column = loc?.column ?? 0;
|
|
77
|
-
if (prop === "data-nuvio-id" && attr.value?.type === "StringLiteral") {
|
|
78
|
-
const id = attr.value.value.trim();
|
|
79
|
-
if (id) {
|
|
80
|
-
acc.push({ id, file: path.resolve(fileAbs), line, column });
|
|
81
|
-
}
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
if (WRAPPER_TAGS.has(tagName) && prop === "id" && attr.value?.type === "StringLiteral") {
|
|
85
|
-
const id = attr.value.value.trim();
|
|
86
|
-
if (id) {
|
|
87
|
-
acc.push({ id, file: path.resolve(fileAbs), line, column });
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
return acc;
|
|
94
|
-
}
|
|
95
|
-
function buildSourceIndex(rootAbs, globPatterns) {
|
|
96
|
-
const root = path.resolve(rootAbs);
|
|
97
|
-
const parseErrors = [];
|
|
98
|
-
const normalizedPatterns = globPatterns.map((g) => {
|
|
99
|
-
const resolved = path.isAbsolute(g) ? path.resolve(g) : path.resolve(root, g);
|
|
100
|
-
return resolved.replace(/\\/g, "/");
|
|
101
|
-
});
|
|
102
|
-
const matched = fg.sync(normalizedPatterns, {
|
|
103
|
-
absolute: true,
|
|
104
|
-
ignore: ["**/node_modules/**", "**/dist/**"],
|
|
105
|
-
onlyFiles: true
|
|
106
|
-
});
|
|
107
|
-
const files = [...new Set(matched)];
|
|
108
|
-
const byId = /* @__PURE__ */ new Map();
|
|
109
|
-
for (const file of files) {
|
|
110
|
-
let code;
|
|
111
|
-
try {
|
|
112
|
-
code = fs.readFileSync(file, "utf8");
|
|
113
|
-
} catch (e) {
|
|
114
|
-
parseErrors.push({ file, message: String(e) });
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
let occurrences;
|
|
118
|
-
try {
|
|
119
|
-
occurrences = extractIdsFromSource(file, code);
|
|
120
|
-
} catch (e) {
|
|
121
|
-
parseErrors.push({ file, message: String(e) });
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
for (const occ of occurrences) {
|
|
125
|
-
const list = byId.get(occ.id) ?? [];
|
|
126
|
-
list.push(occ);
|
|
127
|
-
byId.set(occ.id, list);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
const duplicateErrors = [];
|
|
131
|
-
const entries = [];
|
|
132
|
-
for (const [id, list] of byId) {
|
|
133
|
-
if (list.length > 1) {
|
|
134
|
-
duplicateErrors.push({
|
|
135
|
-
id,
|
|
136
|
-
occurrences: list.map((o) => ({
|
|
137
|
-
file: o.file,
|
|
138
|
-
line: o.line,
|
|
139
|
-
column: o.column
|
|
140
|
-
}))
|
|
141
|
-
});
|
|
142
|
-
} else if (list.length === 1) {
|
|
143
|
-
entries.push(list[0]);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
entries.sort((a, b) => {
|
|
147
|
-
const fa = a.file.localeCompare(b.file);
|
|
148
|
-
if (fa !== 0) {
|
|
149
|
-
return fa;
|
|
150
|
-
}
|
|
151
|
-
if (a.line !== b.line) {
|
|
152
|
-
return a.line - b.line;
|
|
153
|
-
}
|
|
154
|
-
return a.column - b.column;
|
|
155
|
-
});
|
|
156
|
-
duplicateErrors.sort((a, b) => a.id.localeCompare(b.id));
|
|
157
|
-
return {
|
|
158
|
-
entries,
|
|
159
|
-
duplicateErrors,
|
|
160
|
-
parseErrors,
|
|
161
|
-
scannedFileCount: files.length
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
function indexQuality(b) {
|
|
165
|
-
return [b.entries.length, -b.duplicateErrors.length, b.scannedFileCount];
|
|
166
|
-
}
|
|
167
|
-
function isBetterIndex(candidate, current) {
|
|
168
|
-
const c = indexQuality(candidate);
|
|
169
|
-
const b = indexQuality(current);
|
|
170
|
-
for (let i = 0; i < 3; i++) {
|
|
171
|
-
if (c[i] !== b[i]) {
|
|
172
|
-
return c[i] > b[i];
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
function pickBestSourceIndex(rootCandidates, globPatterns) {
|
|
178
|
-
const roots = [...new Set(rootCandidates.map((r) => path.resolve(r)).filter((r) => r.length > 0))];
|
|
179
|
-
if (roots.length === 0) {
|
|
180
|
-
return {
|
|
181
|
-
entries: [],
|
|
182
|
-
duplicateErrors: [],
|
|
183
|
-
parseErrors: [],
|
|
184
|
-
scannedFileCount: 0
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
let best = buildSourceIndex(roots[0], globPatterns);
|
|
188
|
-
for (let i = 1; i < roots.length; i++) {
|
|
189
|
-
const built = buildSourceIndex(roots[i], globPatterns);
|
|
190
|
-
if (isBetterIndex(built, best)) {
|
|
191
|
-
best = built;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return best;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// src/index.ts
|
|
198
22
|
var APP_ENTRY_CANDIDATES = ["src/App.tsx", "src/app.tsx", "App.tsx"];
|
|
199
23
|
function nuvioWsMessageToText(data) {
|
|
200
24
|
if (typeof data === "string") {
|
|
@@ -212,18 +36,18 @@ function nuvioWsMessageToText(data) {
|
|
|
212
36
|
}
|
|
213
37
|
return String(data);
|
|
214
38
|
}
|
|
215
|
-
function supplementIndexFromAppTsx(serverRoot, built, emitWarn = console.warn) {
|
|
39
|
+
function supplementIndexFromAppTsx(serverRoot, built, classNameMode, emitWarn = console.warn) {
|
|
216
40
|
if (built.entries.length > 0) {
|
|
217
41
|
return built;
|
|
218
42
|
}
|
|
219
43
|
for (const rel of APP_ENTRY_CANDIDATES) {
|
|
220
|
-
const appTsx =
|
|
221
|
-
if (!
|
|
44
|
+
const appTsx = path.resolve(serverRoot, rel);
|
|
45
|
+
if (!fs.existsSync(appTsx)) {
|
|
222
46
|
continue;
|
|
223
47
|
}
|
|
224
48
|
try {
|
|
225
|
-
const code =
|
|
226
|
-
const hits = extractIdsFromSource(appTsx, code);
|
|
49
|
+
const code = fs.readFileSync(appTsx, "utf8");
|
|
50
|
+
const hits = extractIdsFromSource(appTsx, code, { classNameMode });
|
|
227
51
|
if (hits.length === 0) {
|
|
228
52
|
continue;
|
|
229
53
|
}
|
|
@@ -257,20 +81,30 @@ function isAllowedOrigin(origin) {
|
|
|
257
81
|
}
|
|
258
82
|
}
|
|
259
83
|
function nuvio(options) {
|
|
84
|
+
const envEnabled = process.env.NUVIO !== "0";
|
|
85
|
+
const enabled = options?.enabled ?? envEnabled;
|
|
260
86
|
const scanGlobs = options?.scanGlobs ?? DEFAULT_GLOBS;
|
|
261
87
|
const verbose = options?.verbose ?? false;
|
|
88
|
+
const classNameMode = options?.classNameMode ?? "literal-only";
|
|
262
89
|
let indexVersion = 0;
|
|
263
90
|
let cachedIndexPayload = null;
|
|
91
|
+
let runtimeDiagnostics = { overlayCssMode: "self-contained" };
|
|
264
92
|
const idToEntry = /* @__PURE__ */ new Map();
|
|
265
93
|
return {
|
|
266
94
|
name: "nuvio",
|
|
267
95
|
apply: "serve",
|
|
268
96
|
configureServer(server) {
|
|
97
|
+
if (!enabled) {
|
|
98
|
+
server.config.logger.info(
|
|
99
|
+
"[Nuvio] disabled (set `NUVIO=1` or `nuvio({ enabled: true })` to enable)."
|
|
100
|
+
);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
269
103
|
const log = server.config.logger;
|
|
270
|
-
const fromConfigFile = typeof server.config.configFile === "string" ?
|
|
271
|
-
const serverRoot =
|
|
104
|
+
const fromConfigFile = typeof server.config.configFile === "string" ? path.dirname(server.config.configFile) : "";
|
|
105
|
+
const serverRoot = path.resolve(server.config.root);
|
|
272
106
|
const rootCandidates = [
|
|
273
|
-
|
|
107
|
+
path.resolve(fromConfigFile || serverRoot),
|
|
274
108
|
serverRoot,
|
|
275
109
|
process.cwd()
|
|
276
110
|
];
|
|
@@ -285,10 +119,12 @@ function nuvio(options) {
|
|
|
285
119
|
}
|
|
286
120
|
};
|
|
287
121
|
const rebuildIndex = () => {
|
|
288
|
-
let built = pickBestSourceIndex(rootCandidates, scanGlobs);
|
|
289
|
-
built = supplementIndexFromAppTsx(serverRoot, built, log.warn);
|
|
122
|
+
let built = pickBestSourceIndex(rootCandidates, scanGlobs, { classNameMode });
|
|
123
|
+
built = supplementIndexFromAppTsx(serverRoot, built, classNameMode, log.warn);
|
|
290
124
|
if (built.entries.length === 0) {
|
|
291
|
-
const fallback = buildSourceIndex(serverRoot, ["src/**/*.{tsx,jsx}"]
|
|
125
|
+
const fallback = buildSourceIndex(serverRoot, ["src/**/*.{tsx,jsx}"], {
|
|
126
|
+
classNameMode
|
|
127
|
+
});
|
|
292
128
|
if (fallback.entries.length > 0) {
|
|
293
129
|
log.warn(
|
|
294
130
|
`[Nuvio] Multi-root scan yielded 0 ids; using serverRoot-only index (${fallback.entries.length} id(s)) from ${serverRoot}.`
|
|
@@ -301,12 +137,18 @@ function nuvio(options) {
|
|
|
301
137
|
for (const e of built.entries) {
|
|
302
138
|
idToEntry.set(e.id, e);
|
|
303
139
|
}
|
|
140
|
+
const versions = readRuntimeVersions(serverRoot);
|
|
141
|
+
runtimeDiagnostics = {
|
|
142
|
+
...versions,
|
|
143
|
+
overlayCssMode: "self-contained"
|
|
144
|
+
};
|
|
304
145
|
cachedIndexPayload = serializeServerMessage({
|
|
305
146
|
type: "indexReady",
|
|
306
147
|
protocolVersion: PROTOCOL_VERSION,
|
|
307
148
|
indexVersion,
|
|
308
149
|
entries: built.entries,
|
|
309
|
-
duplicateErrors: built.duplicateErrors
|
|
150
|
+
duplicateErrors: built.duplicateErrors,
|
|
151
|
+
diagnostics: runtimeDiagnostics
|
|
310
152
|
});
|
|
311
153
|
if (verbose) {
|
|
312
154
|
if (built.parseErrors.length > 0) {
|
|
@@ -322,7 +164,7 @@ function nuvio(options) {
|
|
|
322
164
|
);
|
|
323
165
|
} else {
|
|
324
166
|
log.info(
|
|
325
|
-
`[Nuvio] index
|
|
167
|
+
`[Nuvio] index v2 \u2014 ${built.entries.length} id(s), ${built.scannedFileCount} file(s)`
|
|
326
168
|
);
|
|
327
169
|
}
|
|
328
170
|
if (built.scannedFileCount === 0) {
|
|
@@ -405,7 +247,8 @@ function nuvio(options) {
|
|
|
405
247
|
serializeServerMessage({
|
|
406
248
|
type: "pong",
|
|
407
249
|
protocolVersion: PROTOCOL_VERSION,
|
|
408
|
-
requestId: msg.requestId
|
|
250
|
+
requestId: msg.requestId,
|
|
251
|
+
diagnostics: runtimeDiagnostics
|
|
409
252
|
})
|
|
410
253
|
);
|
|
411
254
|
if (cachedIndexPayload) {
|
|
@@ -442,13 +285,23 @@ function nuvio(options) {
|
|
|
442
285
|
ok: true,
|
|
443
286
|
file: entry.file,
|
|
444
287
|
line: entry.line,
|
|
445
|
-
column: entry.column
|
|
288
|
+
column: entry.column,
|
|
289
|
+
patchHostId: entry.patchHostId,
|
|
290
|
+
primaryTextTargetKey: entry.primaryTextTargetKey,
|
|
291
|
+
textTargets: entry.textTargets,
|
|
292
|
+
styleTargets: entry.styleTargets,
|
|
293
|
+
hierarchyRole: entry.hierarchyRole,
|
|
294
|
+
parentHostId: entry.parentHostId,
|
|
295
|
+
childTargetIds: entry.childTargetIds,
|
|
296
|
+
rowTargets: entry.rowTargets,
|
|
297
|
+
tableMeta: entry.tableMeta,
|
|
298
|
+
tableDataField: entry.tableDataField
|
|
446
299
|
})
|
|
447
300
|
);
|
|
448
301
|
return;
|
|
449
302
|
}
|
|
450
303
|
if (msg.type === "patchUndo") {
|
|
451
|
-
const writeGuardRoot =
|
|
304
|
+
const writeGuardRoot = path.resolve(fromConfigFile || serverRoot);
|
|
452
305
|
const last = undoStack.pop();
|
|
453
306
|
if (!last) {
|
|
454
307
|
ws.send(
|
|
@@ -465,7 +318,7 @@ function nuvio(options) {
|
|
|
465
318
|
}
|
|
466
319
|
try {
|
|
467
320
|
assertPathWithinRoot(writeGuardRoot, last.file);
|
|
468
|
-
|
|
321
|
+
fs.writeFileSync(last.file, last.contents, "utf8");
|
|
469
322
|
} catch (e) {
|
|
470
323
|
undoStack.push(last);
|
|
471
324
|
ws.send(
|
|
@@ -495,7 +348,7 @@ function nuvio(options) {
|
|
|
495
348
|
}
|
|
496
349
|
if (msg.type === "patchApply") {
|
|
497
350
|
const entry = idToEntry.get(msg.id);
|
|
498
|
-
const writeGuardRoot =
|
|
351
|
+
const writeGuardRoot = path.resolve(fromConfigFile || serverRoot);
|
|
499
352
|
const dryRun = msg.dryRun === true;
|
|
500
353
|
const patchAckExtras = dryRun ? { dryRun: true } : {};
|
|
501
354
|
if (!entry) {
|
|
@@ -533,7 +386,7 @@ function nuvio(options) {
|
|
|
533
386
|
}
|
|
534
387
|
let source;
|
|
535
388
|
try {
|
|
536
|
-
source =
|
|
389
|
+
source = fs.readFileSync(entry.file, "utf8");
|
|
537
390
|
} catch (e) {
|
|
538
391
|
ws.send(
|
|
539
392
|
serializeServerMessage({
|
|
@@ -549,7 +402,10 @@ function nuvio(options) {
|
|
|
549
402
|
);
|
|
550
403
|
return;
|
|
551
404
|
}
|
|
552
|
-
const result = await applyPatchToSource(source, entry.file, msg.id, msg.ops
|
|
405
|
+
const result = await applyPatchToSource(source, entry.file, msg.id, msg.ops, {
|
|
406
|
+
classNameMode,
|
|
407
|
+
activeBreakpoint: msg.activeBreakpoint
|
|
408
|
+
});
|
|
553
409
|
if (!result.ok) {
|
|
554
410
|
ws.send(
|
|
555
411
|
serializeServerMessage({
|
|
@@ -583,7 +439,7 @@ function nuvio(options) {
|
|
|
583
439
|
return;
|
|
584
440
|
}
|
|
585
441
|
try {
|
|
586
|
-
|
|
442
|
+
fs.writeFileSync(entry.file, result.source, "utf8");
|
|
587
443
|
} catch (e) {
|
|
588
444
|
ws.send(
|
|
589
445
|
serializeServerMessage({
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Server } from 'node:http';
|
|
2
|
+
|
|
3
|
+
declare const DEFAULT_GLOBS: string[];
|
|
4
|
+
type NuvioDevSessionOptions = {
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
root: string;
|
|
7
|
+
configDir?: string;
|
|
8
|
+
scanGlobs?: string[];
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
classNameMode?: "literal-only" | "cn-basic";
|
|
11
|
+
log?: Pick<Console, "info" | "warn">;
|
|
12
|
+
};
|
|
13
|
+
type NuvioDevSessionHandle = {
|
|
14
|
+
rebuildIndex: () => void;
|
|
15
|
+
close: () => void;
|
|
16
|
+
};
|
|
17
|
+
/** Attach Nuvio dev WebSocket + source index to any Node HTTP server (Vite or Next custom server). */
|
|
18
|
+
declare function attachNuvioDevSession(httpServer: Server, options: NuvioDevSessionOptions): NuvioDevSessionHandle;
|
|
19
|
+
|
|
20
|
+
export { DEFAULT_GLOBS as NUVIO_DEFAULT_SCAN_GLOBS, type NuvioDevSessionHandle, type NuvioDevSessionOptions, attachNuvioDevSession };
|