@archie/devtools 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -262,8 +262,10 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
262
262
  includeHostSourceRef = false,
263
263
  origin = "screen",
264
264
  templateOrigin = false,
265
- allowLegacyIds = true
265
+ allowLegacyIds = true,
266
+ onMissingVid
266
267
  } = opts;
268
+ let missingVidWarned = false;
267
269
  function getJSXTagName(el) {
268
270
  const { name } = el;
269
271
  if (t.isJSXIdentifier(name)) return name.name;
@@ -350,11 +352,17 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
350
352
  }
351
353
  return "STATIC";
352
354
  }
353
- const SHELL_FILE_RE = /(^|\/)shell\.jsx$/;
355
+ const SHELL_FILE_RE = /(^|\/)shell\.(jsx|tsx)$/;
354
356
  const SHELL_DIR_RE = /(^|\/)components\/shell\//;
357
+ const SHELL_BASENAME_RE = /shell/i;
355
358
  const CHROME_ANCESTOR_TAGS = /* @__PURE__ */ new Set(["nav", "aside", "header"]);
356
- function computeIsChrome(file, path) {
359
+ function isChromeFilePath(file) {
357
360
  if (SHELL_FILE_RE.test(file) || SHELL_DIR_RE.test(file)) return true;
361
+ const base = file.split("/").pop() ?? file;
362
+ return SHELL_BASENAME_RE.test(base);
363
+ }
364
+ function computeIsChrome(file, path) {
365
+ if (isChromeFilePath(file)) return true;
358
366
  let current = path.parentPath;
359
367
  while (current) {
360
368
  if (current.isJSXElement()) {
@@ -368,17 +376,65 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
368
376
  function computeContainerKind(path) {
369
377
  const elPath = path.parentPath;
370
378
  if (!elPath?.isJSXElement()) return "none";
371
- const children = meaningfulChildren(t, elPath.node.children);
372
- if (children.length === 0) return "none";
373
- const allPlain = children.every((c) => t.isJSXElement(c) || t.isJSXText(c));
379
+ if (!elPath.node.closingElement) return "none";
380
+ const children = elPath.node.children;
381
+ const allPlain = children.every((c) => {
382
+ if (t.isJSXElement(c)) return true;
383
+ if (t.isJSXText(c)) return c.value.trim() === "";
384
+ return false;
385
+ });
374
386
  return allPlain ? "plain" : "expression";
375
387
  }
376
- function computeLockReason(isKitOrigin, isChrome, dynamicType, containerKind) {
388
+ const kitBoundNamesCache = /* @__PURE__ */ new WeakMap();
389
+ function computeKitBoundNames(programPath) {
390
+ const cached = kitBoundNamesCache.get(programPath.node);
391
+ if (cached) return cached;
392
+ const names = /* @__PURE__ */ new Set();
393
+ for (const stmt of programPath.node.body) {
394
+ if (!t.isVariableDeclaration(stmt)) continue;
395
+ for (const decl of stmt.declarations) {
396
+ if (!t.isObjectPattern(decl.id)) continue;
397
+ if (!decl.init || !t.isIdentifier(decl.init) || decl.init.name !== "Kit") continue;
398
+ for (const prop of decl.id.properties) {
399
+ if (!t.isObjectProperty(prop)) continue;
400
+ const value = t.isIdentifier(prop.value) ? prop.value : t.isIdentifier(prop.key) ? prop.key : null;
401
+ if (value) names.add(value.name);
402
+ }
403
+ }
404
+ }
405
+ kitBoundNamesCache.set(programPath.node, names);
406
+ return names;
407
+ }
408
+ function isKitComponentTagName(path) {
409
+ const name = path.node.name;
410
+ let head = null;
411
+ if (t.isJSXIdentifier(name)) {
412
+ head = name.name;
413
+ } else if (t.isJSXMemberExpression(name)) {
414
+ let cur = name;
415
+ while (t.isJSXMemberExpression(cur)) cur = cur.object;
416
+ if (t.isJSXIdentifier(cur)) head = cur.name;
417
+ }
418
+ if (!head || !/^[A-Z]/.test(head)) return false;
419
+ if (head === "Kit") return true;
420
+ const programPath = path.scope.getProgramParent().path;
421
+ if (programPath.isProgram() && computeKitBoundNames(programPath).has(head)) return true;
422
+ const binding = path.scope.getBinding(head);
423
+ if (binding) {
424
+ const bp = binding.path;
425
+ const isImport = bp.isImportSpecifier() || bp.isImportDefaultSpecifier() || bp.isImportNamespaceSpecifier();
426
+ if (isImport) {
427
+ const decl = bp.parentPath;
428
+ if (decl?.isImportDeclaration() && /kit-runtime/.test(decl.node.source.value)) return true;
429
+ }
430
+ }
431
+ return false;
432
+ }
433
+ function computeLockReason(isKitOrigin, isChrome, dynamicType) {
377
434
  if (isKitOrigin) return "KIT_INTERNAL";
378
435
  if (isChrome) return "CHROME_STRUCTURAL";
379
436
  if (dynamicType === "ARRAY") return "DYNAMIC_ARRAY";
380
437
  if (dynamicType === "CONDITIONAL") return "DYNAMIC_CONDITIONAL";
381
- if (containerKind === "expression") return "EXPRESSION_CONTAINER";
382
438
  return void 0;
383
439
  }
384
440
  function isMovable(path) {
@@ -1028,6 +1084,10 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
1028
1084
  }
1029
1085
  }
1030
1086
  const vid = readVidAttr(path.node.attributes);
1087
+ if (!vid && onMissingVid && !missingVidWarned) {
1088
+ missingVidWarned = true;
1089
+ onMissingVid({ file, line, col, componentName, instrumented: allowLegacyIds });
1090
+ }
1031
1091
  if (!vid && !allowLegacyIds) return;
1032
1092
  const nodeId = vid ?? computeNodeId(file, line, col, componentName);
1033
1093
  const isKitOrigin = origin === "kit" || hasSlotAttr;
@@ -1055,7 +1115,8 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
1055
1115
  const dynamicType = computeDynamicType(path);
1056
1116
  const isChrome = computeIsChrome(file, path);
1057
1117
  const containerKind = computeContainerKind(path);
1058
- const lockReason = computeLockReason(isKitOrigin, isChrome, dynamicType, containerKind);
1118
+ const isKitComponentContainer = isKitComponentTagName(path);
1119
+ const lockReason = computeLockReason(isKitOrigin, isChrome, dynamicType);
1059
1120
  metaObj.properties.push(
1060
1121
  t.objectProperty(t.identifier("vid"), t.stringLiteral(vid)),
1061
1122
  t.objectProperty(t.identifier("isKitOrigin"), t.booleanLiteral(isKitOrigin)),
@@ -1063,6 +1124,11 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
1063
1124
  t.objectProperty(t.identifier("isChrome"), t.booleanLiteral(isChrome)),
1064
1125
  t.objectProperty(t.identifier("containerKind"), t.stringLiteral(containerKind))
1065
1126
  );
1127
+ if (isKitComponentContainer) {
1128
+ metaObj.properties.push(
1129
+ t.objectProperty(t.identifier("isKitContainer"), t.booleanLiteral(true))
1130
+ );
1131
+ }
1066
1132
  if (lockReason) {
1067
1133
  metaObj.properties.push(
1068
1134
  t.objectProperty(t.identifier("lockReason"), t.stringLiteral(lockReason))
@@ -1156,7 +1222,17 @@ function archieDevTools(options = {}) {
1156
1222
  };
1157
1223
  let babelCore = null;
1158
1224
  let includeHostSourceRef = false;
1225
+ let isDevServer = false;
1159
1226
  const transformedByHook = /* @__PURE__ */ new Set();
1227
+ const warnedMissingVidFiles = /* @__PURE__ */ new Set();
1228
+ function warnMissingVid(info) {
1229
+ if (warnedMissingVidFiles.has(info.file)) return;
1230
+ warnedMissingVidFiles.add(info.file);
1231
+ const outcome = info.instrumented ? "the visual editor falls back to legacy file:line:col addressing for it, which breaks on every source edit" : "this file is not instrumented for the visual editor at all";
1232
+ console.warn(
1233
+ `[Archie DevTools] Missing data-vid on <${info.componentName}> at ${info.file}:${info.line}:${info.col}. This file was not stamped with stable element ids \u2014 ${outcome}. Re-run the Genesis stamper.`
1234
+ );
1235
+ }
1160
1236
  const plugin = {
1161
1237
  name: "vite-plugin-archie-devtools",
1162
1238
  enforce: "pre",
@@ -1172,6 +1248,7 @@ function archieDevTools(options = {}) {
1172
1248
  },
1173
1249
  configResolved(config) {
1174
1250
  includeHostSourceRef = !config.isProduction;
1251
+ isDevServer = config.command === "serve";
1175
1252
  },
1176
1253
  configureServer(server) {
1177
1254
  server.middlewares.use(LOCAL_INSPECTOR_SERVE_PATH, (_req, res) => {
@@ -1207,7 +1284,10 @@ function archieDevTools(options = {}) {
1207
1284
  const result = await babel.transformAsync(code, {
1208
1285
  filename: id,
1209
1286
  plugins: [
1210
- (b) => babelPluginEditorMeta(b, { includeHostSourceRef }),
1287
+ (b) => babelPluginEditorMeta(b, {
1288
+ includeHostSourceRef,
1289
+ onMissingVid: isDevServer ? warnMissingVid : void 0
1290
+ }),
1211
1291
  ...dndPages.test(id) ? [(b) => babelPluginDndAuto(b, { importSource: INTERNAL_DND_SCOPE_IMPORT_SOURCE })] : []
1212
1292
  ],
1213
1293
  parserOpts: {
@@ -1247,7 +1327,8 @@ function archieDevTools(options = {}) {
1247
1327
  if (!/node_modules/.test(context.id) && !transformedByHook.has(context.id)) {
1248
1328
  babelOptions.plugins.push(
1249
1329
  (babel) => babelPluginEditorMeta(babel, {
1250
- includeHostSourceRef
1330
+ includeHostSourceRef,
1331
+ onMissingVid: isDevServer ? warnMissingVid : void 0
1251
1332
  })
1252
1333
  );
1253
1334
  }
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  __dirname,
3
3
  babelPluginEditorMeta
4
- } from "./chunk-THKKWDMJ.mjs";
4
+ } from "./chunk-U4KG6UDY.mjs";
5
5
 
6
6
  // src/index.ts
7
7
  import { readFileSync } from "fs";
@@ -186,7 +186,17 @@ function archieDevTools(options = {}) {
186
186
  };
187
187
  let babelCore = null;
188
188
  let includeHostSourceRef = false;
189
+ let isDevServer = false;
189
190
  const transformedByHook = /* @__PURE__ */ new Set();
191
+ const warnedMissingVidFiles = /* @__PURE__ */ new Set();
192
+ function warnMissingVid(info) {
193
+ if (warnedMissingVidFiles.has(info.file)) return;
194
+ warnedMissingVidFiles.add(info.file);
195
+ const outcome = info.instrumented ? "the visual editor falls back to legacy file:line:col addressing for it, which breaks on every source edit" : "this file is not instrumented for the visual editor at all";
196
+ console.warn(
197
+ `[Archie DevTools] Missing data-vid on <${info.componentName}> at ${info.file}:${info.line}:${info.col}. This file was not stamped with stable element ids \u2014 ${outcome}. Re-run the Genesis stamper.`
198
+ );
199
+ }
190
200
  const plugin = {
191
201
  name: "vite-plugin-archie-devtools",
192
202
  enforce: "pre",
@@ -202,6 +212,7 @@ function archieDevTools(options = {}) {
202
212
  },
203
213
  configResolved(config) {
204
214
  includeHostSourceRef = !config.isProduction;
215
+ isDevServer = config.command === "serve";
205
216
  },
206
217
  configureServer(server) {
207
218
  server.middlewares.use(LOCAL_INSPECTOR_SERVE_PATH, (_req, res) => {
@@ -237,7 +248,10 @@ function archieDevTools(options = {}) {
237
248
  const result = await babel.transformAsync(code, {
238
249
  filename: id,
239
250
  plugins: [
240
- (b) => babelPluginEditorMeta(b, { includeHostSourceRef }),
251
+ (b) => babelPluginEditorMeta(b, {
252
+ includeHostSourceRef,
253
+ onMissingVid: isDevServer ? warnMissingVid : void 0
254
+ }),
241
255
  ...dndPages.test(id) ? [(b) => babelPluginDndAuto(b, { importSource: INTERNAL_DND_SCOPE_IMPORT_SOURCE })] : []
242
256
  ],
243
257
  parserOpts: {
@@ -277,7 +291,8 @@ function archieDevTools(options = {}) {
277
291
  if (!/node_modules/.test(context.id) && !transformedByHook.has(context.id)) {
278
292
  babelOptions.plugins.push(
279
293
  (babel) => babelPluginEditorMeta(babel, {
280
- includeHostSourceRef
294
+ includeHostSourceRef,
295
+ onMissingVid: isDevServer ? warnMissingVid : void 0
281
296
  })
282
297
  );
283
298
  }