@nuvio/vite-plugin 0.1.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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 fs2 from "fs";
3
- import path2 from "path";
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 = path2.resolve(serverRoot, rel);
221
- if (!fs2.existsSync(appTsx)) {
44
+ const appTsx = path.resolve(serverRoot, rel);
45
+ if (!fs.existsSync(appTsx)) {
222
46
  continue;
223
47
  }
224
48
  try {
225
- const code = fs2.readFileSync(appTsx, "utf8");
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" ? path2.dirname(server.config.configFile) : "";
271
- const serverRoot = path2.resolve(server.config.root);
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
- path2.resolve(fromConfigFile || serverRoot),
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 v${indexVersion} \u2014 ${built.entries.length} id(s), ${built.scannedFileCount} file(s)`
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 = path2.resolve(fromConfigFile || serverRoot);
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
- fs2.writeFileSync(last.file, last.contents, "utf8");
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 = path2.resolve(fromConfigFile || serverRoot);
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 = fs2.readFileSync(entry.file, "utf8");
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
- fs2.writeFileSync(entry.file, result.source, "utf8");
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 };