@frmwrkd/snag-anchor 0.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.
Files changed (75) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +305 -0
  3. package/dist/babel/component-name.d.ts +42 -0
  4. package/dist/babel/component-name.d.ts.map +1 -0
  5. package/dist/babel/component-name.js +107 -0
  6. package/dist/babel/component-name.js.map +1 -0
  7. package/dist/babel/plugin.d.ts +69 -0
  8. package/dist/babel/plugin.d.ts.map +1 -0
  9. package/dist/babel/plugin.js +272 -0
  10. package/dist/babel/plugin.js.map +1 -0
  11. package/dist/babel/walk.d.ts +8 -0
  12. package/dist/babel/walk.d.ts.map +1 -0
  13. package/dist/babel/walk.js +50 -0
  14. package/dist/babel/walk.js.map +1 -0
  15. package/dist/devtools-json.d.ts +45 -0
  16. package/dist/devtools-json.d.ts.map +1 -0
  17. package/dist/devtools-json.js +117 -0
  18. package/dist/devtools-json.js.map +1 -0
  19. package/dist/esbuild.d.ts +3 -0
  20. package/dist/esbuild.d.ts.map +1 -0
  21. package/dist/esbuild.js +11 -0
  22. package/dist/esbuild.js.map +1 -0
  23. package/dist/index.d.ts +31 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +90 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/loader.cjs +44 -0
  28. package/dist/loader.cjs.map +1 -0
  29. package/dist/loader.d.cts +2 -0
  30. package/dist/loader.d.cts.map +1 -0
  31. package/dist/magic-string.d.ts +23 -0
  32. package/dist/magic-string.d.ts.map +1 -0
  33. package/dist/magic-string.js +23 -0
  34. package/dist/magic-string.js.map +1 -0
  35. package/dist/nuxt.d.ts +14 -0
  36. package/dist/nuxt.d.ts.map +1 -0
  37. package/dist/nuxt.js +29 -0
  38. package/dist/nuxt.js.map +1 -0
  39. package/dist/options.d.ts +101 -0
  40. package/dist/options.d.ts.map +1 -0
  41. package/dist/options.js +122 -0
  42. package/dist/options.js.map +1 -0
  43. package/dist/rollup.d.ts +3 -0
  44. package/dist/rollup.d.ts.map +1 -0
  45. package/dist/rollup.js +11 -0
  46. package/dist/rollup.js.map +1 -0
  47. package/dist/root.d.ts +38 -0
  48. package/dist/root.d.ts.map +1 -0
  49. package/dist/root.js +101 -0
  50. package/dist/root.js.map +1 -0
  51. package/dist/rspack.d.ts +3 -0
  52. package/dist/rspack.d.ts.map +1 -0
  53. package/dist/rspack.js +11 -0
  54. package/dist/rspack.js.map +1 -0
  55. package/dist/stamp.d.ts +66 -0
  56. package/dist/stamp.d.ts.map +1 -0
  57. package/dist/stamp.js +28 -0
  58. package/dist/stamp.js.map +1 -0
  59. package/dist/transform.d.ts +14 -0
  60. package/dist/transform.d.ts.map +1 -0
  61. package/dist/transform.js +54 -0
  62. package/dist/transform.js.map +1 -0
  63. package/dist/vite.d.ts +3 -0
  64. package/dist/vite.d.ts.map +1 -0
  65. package/dist/vite.js +11 -0
  66. package/dist/vite.js.map +1 -0
  67. package/dist/vue/transform.d.ts +50 -0
  68. package/dist/vue/transform.d.ts.map +1 -0
  69. package/dist/vue/transform.js +188 -0
  70. package/dist/vue/transform.js.map +1 -0
  71. package/dist/webpack.d.ts +3 -0
  72. package/dist/webpack.d.ts.map +1 -0
  73. package/dist/webpack.js +15 -0
  74. package/dist/webpack.js.map +1 -0
  75. package/package.json +84 -0
@@ -0,0 +1,272 @@
1
+ /**
2
+ * The JSX visitor. This is tier 0, and tier 0 is the product.
3
+ *
4
+ * ## Written from scratch, on purpose
5
+ *
6
+ * doc 03 §2.1 **[V — verified against React source]**: React 19 **removed**
7
+ * `fiber._debugSource`. `jsxDEV`'s `source` argument was dropped in 19.0/19.1
8
+ * and removed from the signature entirely in 19.2, and `createElement`
9
+ * explicitly strips `__source`/`__self`. `@babel/preset-react` now defaults
10
+ * `developmentSourceSelf` to `false` for exactly that reason.
11
+ *
12
+ * That break took out `click-to-component`, `vite-plugin-react-click-to-component`,
13
+ * `react-dev-inspector`, `code-inspector`, `radon-ide` and LocatorJS's React
14
+ * path *simultaneously*. doc 02 §2.4: **do not depend on any of them.** Nothing
15
+ * in this file reads `_debugSource`, `__source`, `__self`, or imports any of
16
+ * those packages. It is a Babel visitor, and it is about forty lines.
17
+ *
18
+ * ## Two rules, kept separate — doc 02 §2.2
19
+ *
20
+ * 1. `data-snag="path:line:col"` goes on **intrinsic (lowercase) JSX elements
21
+ * only** — `<div>`, `<button>`, `<input>`. Never on `<PromoField />`: a
22
+ * `data-snag` prop on a component element is silently swallowed unless that
23
+ * component happens to spread its props onto a DOM node, and an attribute
24
+ * that lands nowhere is worse than no attribute, because the resolver cannot
25
+ * tell the difference between "not stamped" and "stamped and dropped".
26
+ * 2. `data-snag-cmp` carries the nearest enclosing component name
27
+ * (`./component-name.ts`).
28
+ *
29
+ * Fragments are skipped for free: `<>` parses to `JSXOpeningFragment`, a
30
+ * different node type this visitor never sees, and `<React.Fragment>` is a
31
+ * `JSXMemberExpression`, which is not intrinsic.
32
+ *
33
+ * ## Line and column semantics — read this before changing anything
34
+ *
35
+ * `line` is 1-based and points at the `<` of the element's **opening tag**.
36
+ * `col` is **1-based**: Babel reports `loc.start.column` 0-based, and this
37
+ * module adds one, because the consumer is `vscode://file/{path}:{line}:{col}`
38
+ * and every editor's "go to line:column" is 1-based. So the invariant a test
39
+ * can assert directly is:
40
+ *
41
+ * sourceLines[line - 1].charAt(col - 1) === '<'
42
+ *
43
+ * `@frmwrkd/snag-core`'s `AnchorSchema` documents `col` as "0-based or 1-based per the
44
+ * emitting tool; stored verbatim, never recomputed" — this is the emitting
45
+ * tool, and this is the choice.
46
+ *
47
+ * ## Two call paths, one decision core
48
+ *
49
+ * `stampJsx()` is the string-splicing transform the unplugin uses. `default
50
+ * export` is a genuine Babel `PluginObj` for anyone wiring this into their own
51
+ * `babel.config.js` (React Native, Next's Babel path, Storybook). Both route
52
+ * every decision through `shouldStamp()` / `buildAttributes()`, so they cannot
53
+ * disagree — `test/parity.test.ts` asserts exactly that.
54
+ */
55
+ import { parse } from '@babel/parser';
56
+ import { jsxAttribute, jsxIdentifier, stringLiteral } from '@babel/types';
57
+ import { MagicString } from '../magic-string.js';
58
+ import { DEFAULT_ATTRIBUTE, DEFAULT_COMPONENT_ATTRIBUTE } from '../options.js';
59
+ import { resolveRoot, toRepoRelative } from '../root.js';
60
+ import { buildAttributes, renderAttributes } from '../stamp.js';
61
+ import { resolveComponentName } from './component-name.js';
62
+ import { walk } from './walk.js';
63
+ export { buildAttributes, escapeAttributeValue } from '../stamp.js';
64
+ /* ------------------------------------------------------- decision primitives */
65
+ /**
66
+ * Babel's own `isCompatTag`: a JSX identifier is an intrinsic host element iff
67
+ * it starts with a lowercase ASCII letter. `<div>` and `<my-widget>` are
68
+ * intrinsic; `<Foo>`, `<_Foo>`, `<Foo.Bar>` and `<svg:circle>` are not.
69
+ */
70
+ export function isIntrinsicTagName(name) {
71
+ return /^[a-z]/.test(name);
72
+ }
73
+ export function tagInfo(name) {
74
+ switch (name.type) {
75
+ case 'JSXIdentifier':
76
+ return { tag: name.name, intrinsic: isIntrinsicTagName(name.name) };
77
+ case 'JSXMemberExpression': {
78
+ const parts = [];
79
+ let cursor = name;
80
+ while (cursor.type === 'JSXMemberExpression') {
81
+ parts.unshift(cursor.property.name);
82
+ cursor = cursor.object;
83
+ }
84
+ if (cursor.type === 'JSXIdentifier')
85
+ parts.unshift(cursor.name);
86
+ // `<Foo.Bar>` is always a component reference, never a host element.
87
+ return { tag: parts.join('.'), intrinsic: false };
88
+ }
89
+ case 'JSXNamespacedName':
90
+ // `<svg:circle>` only reaches the DOM through `throwIfNamespace: false`
91
+ // and is vanishingly rare. Declining to stamp is the honest answer.
92
+ return { tag: `${name.namespace.name}:${name.name.name}`, intrinsic: false };
93
+ default:
94
+ return null;
95
+ }
96
+ }
97
+ function jsxAttributeName(node) {
98
+ if (node.type !== 'JSXAttribute')
99
+ return null;
100
+ const name = node.name;
101
+ return name.type === 'JSXIdentifier' ? name.name : `${name.namespace.name}:${name.name.name}`;
102
+ }
103
+ export function hasAttribute(node, attribute) {
104
+ return node.attributes.some((attr) => jsxAttributeName(attr) === attribute);
105
+ }
106
+ /**
107
+ * doc 02 §2.2: "Skip if a `data-snag` attribute already exists (user override
108
+ * wins)." Extended to the component attribute too, so `production: 'minimal'`
109
+ * — where `data-snag` is never emitted — respects an override just the same.
110
+ */
111
+ export function shouldStamp(node, ctx) {
112
+ if (ctx.mode === 'off')
113
+ return false;
114
+ const info = tagInfo(node.name);
115
+ if (info === null)
116
+ return false;
117
+ if (ctx.intrinsicOnly && !info.intrinsic)
118
+ return false;
119
+ if (hasAttribute(node, ctx.attribute))
120
+ return false;
121
+ if (hasAttribute(node, ctx.componentAttribute))
122
+ return false;
123
+ return true;
124
+ }
125
+ /* --------------------------------------------------------------- the splice */
126
+ export function parserPluginsFor(filename) {
127
+ const dot = filename.lastIndexOf('.');
128
+ const ext = dot === -1 ? '' : filename.slice(dot).toLowerCase();
129
+ const plugins = [];
130
+ // JSX is not valid in `.ts`/`.mts`/`.cts` — `<T>(x)` there is a type
131
+ // assertion, and enabling `jsx` would misparse it.
132
+ const jsxAllowed = ext !== '.ts' && ext !== '.mts' && ext !== '.cts';
133
+ if (jsxAllowed)
134
+ plugins.push('jsx');
135
+ if (ext === '.ts' || ext === '.tsx' || ext === '.mts' || ext === '.cts') {
136
+ plugins.push('typescript');
137
+ }
138
+ return plugins;
139
+ }
140
+ /**
141
+ * Collect every stamp site in an already-parsed file, in source order.
142
+ *
143
+ * The insertion offset is the end of the **last existing attribute**, or the
144
+ * end of the tag name when there are none. That places the injected attribute
145
+ * last, after any `{...props}` spread, so the stamp always wins: an element
146
+ * that receives a forwarded `data-snag` from a parent still reports the line it
147
+ * is actually written on. It also sidesteps arithmetic on `/>` vs `>`.
148
+ */
149
+ export function collectStampSites(ast, ctx) {
150
+ const sites = [];
151
+ walk(ast, (node, ancestors) => {
152
+ if (node.type !== 'JSXOpeningElement')
153
+ return;
154
+ if (!shouldStamp(node, ctx))
155
+ return;
156
+ const loc = node.loc;
157
+ if (loc === null || loc === undefined)
158
+ return;
159
+ const last = node.attributes.length > 0 ? node.attributes[node.attributes.length - 1] : undefined;
160
+ const offset = last !== undefined ? last.end : node.name.end;
161
+ if (offset === null || offset === undefined)
162
+ return;
163
+ const info = tagInfo(node.name);
164
+ if (info === null)
165
+ return;
166
+ const line = loc.start.line;
167
+ const col = loc.start.column + 1; // Babel columns are 0-based; ours are not.
168
+ const component = resolveComponentName(ancestors);
169
+ const attributes = buildAttributes({ line, col, component }, ctx);
170
+ if (attributes.length === 0)
171
+ return;
172
+ sites.push({ offset, line, col, tag: info.tag, component, attributes });
173
+ });
174
+ return sites;
175
+ }
176
+ /**
177
+ * Transform a JSX/TSX source string.
178
+ *
179
+ * Returns `null` when nothing was stamped **and** when the file could not be
180
+ * parsed. A source transform that throws breaks the user's dev server; this one
181
+ * fails open and leaves the file exactly as it found it. The cost of a missed
182
+ * anchor is a tier-3 fallback; the cost of a thrown error is a broken build.
183
+ */
184
+ export function stampJsx(code, ctx, filename) {
185
+ const name = filename ?? ctx.file;
186
+ let ast;
187
+ try {
188
+ ast = parse(code, {
189
+ sourceType: 'unambiguous',
190
+ allowReturnOutsideFunction: true,
191
+ allowAwaitOutsideFunction: true,
192
+ allowSuperOutsideMethod: true,
193
+ plugins: parserPluginsFor(name),
194
+ });
195
+ }
196
+ catch {
197
+ return null;
198
+ }
199
+ const sites = collectStampSites(ast, ctx);
200
+ if (sites.length === 0)
201
+ return null;
202
+ const s = new MagicString(code);
203
+ for (const site of sites) {
204
+ s.appendLeft(site.offset, renderAttributes(site.attributes));
205
+ }
206
+ return {
207
+ code: s.toString(),
208
+ map: s.generateMap({ hires: true, source: name, includeContent: false }),
209
+ sites,
210
+ };
211
+ }
212
+ const CONTEXT_KEY = 'snag-anchor:context';
213
+ /** Ancestors of `path`, outermost first — the shape `resolveComponentName` wants. */
214
+ function ancestorNodes(path) {
215
+ const out = [];
216
+ let current = path.parentPath;
217
+ while (current !== null) {
218
+ out.push(current.node);
219
+ current = current.parentPath;
220
+ }
221
+ return out.reverse();
222
+ }
223
+ /**
224
+ * `@frmwrkd/snag-anchor/babel` — usable directly in a `babel.config.js`:
225
+ *
226
+ * ```js
227
+ * plugins: [['@frmwrkd/snag-anchor/babel', { mode: 'full' }]]
228
+ * ```
229
+ */
230
+ export default function snagAnchorBabelPlugin() {
231
+ return {
232
+ name: 'snag-anchor',
233
+ pre() {
234
+ const opts = (this.opts ?? {});
235
+ const filename = this.filename;
236
+ const root = resolveRoot(opts.root);
237
+ const file = opts.file ?? (filename !== undefined ? toRepoRelative(filename, root) : '');
238
+ // No filename and no explicit `file` means we cannot name a path. Emitting
239
+ // `":42:7"` would be a fabricated anchor, so stamp nothing at all.
240
+ const requestedMode = opts.mode ?? 'full';
241
+ const mode = requestedMode === 'full' && file === '' ? 'off' : requestedMode;
242
+ const ctx = {
243
+ file,
244
+ attribute: opts.attribute ?? DEFAULT_ATTRIBUTE,
245
+ componentAttribute: opts.componentAttribute ?? DEFAULT_COMPONENT_ATTRIBUTE,
246
+ intrinsicOnly: opts.intrinsicOnly ?? true,
247
+ mode,
248
+ };
249
+ this.set(CONTEXT_KEY, ctx);
250
+ },
251
+ visitor: {
252
+ JSXOpeningElement(path, state) {
253
+ const ctx = state.get(CONTEXT_KEY);
254
+ if (ctx === undefined)
255
+ return;
256
+ if (!shouldStamp(path.node, ctx))
257
+ return;
258
+ const loc = path.node.loc;
259
+ if (loc === null || loc === undefined)
260
+ return;
261
+ const component = resolveComponentName(ancestorNodes(path));
262
+ const attributes = buildAttributes({ line: loc.start.line, col: loc.start.column + 1, component }, ctx);
263
+ if (attributes.length === 0)
264
+ return;
265
+ for (const attr of attributes) {
266
+ path.node.attributes.push(jsxAttribute(jsxIdentifier(attr.name), stringLiteral(attr.value)));
267
+ }
268
+ },
269
+ },
270
+ };
271
+ }
272
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/babel/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Node } from '@babel/types';
2
+ export type NodeVisitor = (node: Node, ancestors: readonly Node[]) => void;
3
+ /**
4
+ * @param visit Called for every node, with `ancestors` **outermost first**.
5
+ * The array is the live traversal stack — read it, do not retain it.
6
+ */
7
+ export declare function walk(root: Node, visit: NodeVisitor): void;
8
+ //# sourceMappingURL=walk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../../src/babel/walk.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,IAAI,EAAE,KAAK,IAAI,CAAC;AAU3E;;;GAGG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAyBzD"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * A depth-first AST walk that hands each visitor an ancestor stack.
3
+ *
4
+ * `@babel/traverse` would do this, but it is a CommonJS module whose ESM
5
+ * default export is the well-known `traverse.default ?? traverse` footgun, it
6
+ * builds a `NodePath` and a `Scope` for every node, and it requeues nodes on
7
+ * mutation. None of that is needed here: this transform never mutates the AST
8
+ * (it splices the *source string*) and it only needs the ancestor chain of JSX
9
+ * opening elements.
10
+ *
11
+ * Child keys come from `@babel/types`' own `VISITOR_KEYS`, so the walk visits
12
+ * exactly the nodes Babel considers children — no hand-maintained key table to
13
+ * drift out of date when a new syntax node lands.
14
+ */
15
+ import { VISITOR_KEYS } from '@babel/types';
16
+ function isNode(value) {
17
+ return (typeof value === 'object' &&
18
+ value !== null &&
19
+ typeof value.type === 'string');
20
+ }
21
+ /**
22
+ * @param visit Called for every node, with `ancestors` **outermost first**.
23
+ * The array is the live traversal stack — read it, do not retain it.
24
+ */
25
+ export function walk(root, visit) {
26
+ const ancestors = [];
27
+ const step = (node) => {
28
+ visit(node, ancestors);
29
+ const keys = VISITOR_KEYS[node.type];
30
+ if (keys === undefined || keys.length === 0)
31
+ return;
32
+ ancestors.push(node);
33
+ const record = node;
34
+ for (const key of keys) {
35
+ const value = record[key];
36
+ if (Array.isArray(value)) {
37
+ for (const child of value) {
38
+ if (isNode(child))
39
+ step(child);
40
+ }
41
+ }
42
+ else if (isNode(value)) {
43
+ step(value);
44
+ }
45
+ }
46
+ ancestors.pop();
47
+ };
48
+ step(root);
49
+ }
50
+ //# sourceMappingURL=walk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk.js","sourceRoot":"","sources":["../../src/babel/walk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAK5C,SAAS,MAAM,CAAC,KAAc;IAC5B,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,OAAQ,KAA4B,CAAC,IAAI,KAAK,QAAQ,CACvD,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,IAAI,CAAC,IAAU,EAAE,KAAkB;IACjD,MAAM,SAAS,GAAW,EAAE,CAAC;IAE7B,MAAM,IAAI,GAAG,CAAC,IAAU,EAAQ,EAAE;QAChC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAEvB,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEpD,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,MAAM,MAAM,GAAG,IAA0C,CAAC;QAC1D,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;oBAC1B,IAAI,MAAM,CAAC,KAAK,CAAC;wBAAE,IAAI,CAAC,KAAK,CAAC,CAAC;gBACjC,CAAC;YACH,CAAC;iBAAM,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,IAAI,CAAC,KAAK,CAAC,CAAC;YACd,CAAC;QACH,CAAC;QACD,SAAS,CAAC,GAAG,EAAE,CAAC;IAClB,CAAC,CAAC;IAEF,IAAI,CAAC,IAAI,CAAC,CAAC;AACb,CAAC"}
@@ -0,0 +1,45 @@
1
+ export declare const DEVTOOLS_JSON_PATH = "/.well-known/appspecific/com.chrome.devtools.json";
2
+ export interface DevtoolsJson {
3
+ workspace: {
4
+ /** Absolute, POSIX-normalised project root. */
5
+ root: string;
6
+ uuid: string;
7
+ };
8
+ }
9
+ /**
10
+ * UUID v5 (SHA-1, name-based) — deterministic for a given name.
11
+ *
12
+ * `node:crypto.randomUUID()` is v4 and would change on every restart, which is
13
+ * the one thing this value must not do.
14
+ */
15
+ export declare function uuidV5(name: string, namespace?: string): string;
16
+ /** Stable for a given absolute root; different for every other root. */
17
+ export declare function devtoolsWorkspaceUuid(absoluteRoot: string): string;
18
+ export declare function devtoolsJson(absoluteRoot: string): DevtoolsJson;
19
+ /**
20
+ * The slice of a Node request/response this middleware actually touches. Typed
21
+ * structurally so the module does not need `connect`, `vite` or `webpack-dev-server`
22
+ * types — all three pass something that satisfies this.
23
+ */
24
+ export interface DevtoolsJsonRequest {
25
+ url?: string | undefined;
26
+ method?: string | undefined;
27
+ }
28
+ export interface DevtoolsJsonResponse {
29
+ statusCode: number;
30
+ setHeader(name: string, value: string): unknown;
31
+ end(chunk?: string): unknown;
32
+ }
33
+ export type DevtoolsJsonMiddleware = (req: DevtoolsJsonRequest, res: DevtoolsJsonResponse, next: () => void) => void;
34
+ /**
35
+ * Connect-style middleware. Responds to GET (and HEAD) on
36
+ * `/.well-known/appspecific/com.chrome.devtools.json`; delegates everything
37
+ * else untouched.
38
+ *
39
+ * Register it **unmounted** — `server.middlewares.use(mw)`, never
40
+ * `server.middlewares.use(PATH, mw)`. Connect rewrites `req.url` to `/` for a
41
+ * mounted middleware, and a handler that also answered `/` would swallow the
42
+ * application's index route.
43
+ */
44
+ export declare function createDevtoolsJsonMiddleware(absoluteRoot: string): DevtoolsJsonMiddleware;
45
+ //# sourceMappingURL=devtools-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools-json.d.ts","sourceRoot":"","sources":["../src/devtools-json.ts"],"names":[],"mappings":"AA+BA,eAAO,MAAM,kBAAkB,sDAAsD,CAAC;AAEtF,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE;QACT,+CAA+C;QAC/C,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AA4BD;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAsB,GAAG,MAAM,CAY9E;AAED,wEAAwE;AACxE,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY,CAO/D;AAID;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAChD,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC9B;AAED,MAAM,MAAM,sBAAsB,GAAG,CACnC,GAAG,EAAE,mBAAmB,EACxB,GAAG,EAAE,oBAAoB,EACzB,IAAI,EAAE,MAAM,IAAI,KACb,IAAI,CAAC;AAEV;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,YAAY,EAAE,MAAM,GAAG,sBAAsB,CAyBzF"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * `/.well-known/appspecific/com.chrome.devtools.json` — doc 02 §2.5.
3
+ *
4
+ * ```json
5
+ * { "workspace": { "root": "/Users/theo/dev/myapp", "uuid": "<stable-uuid>" } }
6
+ * ```
7
+ *
8
+ * ## Why this exists at all
9
+ *
10
+ * Every `data-snag` attribute is repo-relative (`src/App.tsx:42:7`) so that the
11
+ * same string works in a container, on a preview deploy, and on a teammate's
12
+ * machine. But Claude Code has to *open a file*, and `editor_scheme`'s `{abs}`
13
+ * placeholder needs an absolute path. This endpoint is the one place the
14
+ * absolute root is published, once, per origin — the extension fetches it from
15
+ * the page origin and joins the two halves back together.
16
+ *
17
+ * It is served by **this plugin, not by snagd**: the extension reads it from
18
+ * the app's origin, so it must come from the app's dev server. It only works on
19
+ * localhost, which is exactly the case that matters.
20
+ *
21
+ * ## Why the uuid must be stable across restarts
22
+ *
23
+ * Chrome DevTools binds a workspace folder to this uuid. A fresh uuid on every
24
+ * `vite dev` would silently unbind the folder and the developer would re-approve
25
+ * the workspace on every restart. So it is not random: it is a **UUID v5**
26
+ * derived deterministically from the absolute project root, which means the
27
+ * same checkout always produces the same uuid and two different checkouts never
28
+ * collide.
29
+ */
30
+ import { createHash } from 'node:crypto';
31
+ export const DEVTOOLS_JSON_PATH = '/.well-known/appspecific/com.chrome.devtools.json';
32
+ /** RFC 4122 namespace ID for URLs — the namespace UUID v5 is derived under. */
33
+ const URL_NAMESPACE = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
34
+ function uuidToBytes(uuid) {
35
+ const hex = uuid.replace(/-/g, '');
36
+ const bytes = new Uint8Array(16);
37
+ for (let i = 0; i < 16; i += 1) {
38
+ bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
39
+ }
40
+ return bytes;
41
+ }
42
+ function bytesToUuid(bytes) {
43
+ const hex = [];
44
+ for (let i = 0; i < 16; i += 1) {
45
+ hex.push((bytes[i] ?? 0).toString(16).padStart(2, '0'));
46
+ }
47
+ return [
48
+ hex.slice(0, 4).join(''),
49
+ hex.slice(4, 6).join(''),
50
+ hex.slice(6, 8).join(''),
51
+ hex.slice(8, 10).join(''),
52
+ hex.slice(10, 16).join(''),
53
+ ].join('-');
54
+ }
55
+ /**
56
+ * UUID v5 (SHA-1, name-based) — deterministic for a given name.
57
+ *
58
+ * `node:crypto.randomUUID()` is v4 and would change on every restart, which is
59
+ * the one thing this value must not do.
60
+ */
61
+ export function uuidV5(name, namespace = URL_NAMESPACE) {
62
+ const hash = createHash('sha1');
63
+ hash.update(uuidToBytes(namespace));
64
+ hash.update(Buffer.from(name, 'utf8'));
65
+ const digest = new Uint8Array(hash.digest()).slice(0, 16);
66
+ // Version 5 …
67
+ digest[6] = ((digest[6] ?? 0) & 0x0f) | 0x50;
68
+ // … and the RFC 4122 variant.
69
+ digest[8] = ((digest[8] ?? 0) & 0x3f) | 0x80;
70
+ return bytesToUuid(digest);
71
+ }
72
+ /** Stable for a given absolute root; different for every other root. */
73
+ export function devtoolsWorkspaceUuid(absoluteRoot) {
74
+ return uuidV5(`snag:${absoluteRoot}`);
75
+ }
76
+ export function devtoolsJson(absoluteRoot) {
77
+ return {
78
+ workspace: {
79
+ root: absoluteRoot,
80
+ uuid: devtoolsWorkspaceUuid(absoluteRoot),
81
+ },
82
+ };
83
+ }
84
+ /**
85
+ * Connect-style middleware. Responds to GET (and HEAD) on
86
+ * `/.well-known/appspecific/com.chrome.devtools.json`; delegates everything
87
+ * else untouched.
88
+ *
89
+ * Register it **unmounted** — `server.middlewares.use(mw)`, never
90
+ * `server.middlewares.use(PATH, mw)`. Connect rewrites `req.url` to `/` for a
91
+ * mounted middleware, and a handler that also answered `/` would swallow the
92
+ * application's index route.
93
+ */
94
+ export function createDevtoolsJsonMiddleware(absoluteRoot) {
95
+ const body = JSON.stringify(devtoolsJson(absoluteRoot));
96
+ return (req, res, next) => {
97
+ const method = (req.method ?? 'GET').toUpperCase();
98
+ if (method !== 'GET' && method !== 'HEAD') {
99
+ next();
100
+ return;
101
+ }
102
+ const url = req.url ?? '';
103
+ const pathname = url.split('?')[0]?.split('#')[0] ?? '';
104
+ if (pathname !== DEVTOOLS_JSON_PATH) {
105
+ next();
106
+ return;
107
+ }
108
+ res.statusCode = 200;
109
+ res.setHeader('content-type', 'application/json; charset=utf-8');
110
+ // Localhost only, but the extension fetches it cross-origin from its own
111
+ // context; without this the fetch is opaque and the root is unreadable.
112
+ res.setHeader('access-control-allow-origin', '*');
113
+ res.setHeader('cache-control', 'no-store');
114
+ res.end(method === 'HEAD' ? '' : body);
115
+ };
116
+ }
117
+ //# sourceMappingURL=devtools-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools-json.js","sourceRoot":"","sources":["../src/devtools-json.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ declare const _default: (options?: import("./options.js").AnchorOptions | undefined) => import("unplugin").EsbuildPlugin;
2
+ export default _default;
3
+ //# sourceMappingURL=esbuild.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"esbuild.d.ts","sourceRoot":"","sources":["../src/esbuild.ts"],"names":[],"mappings":";AAUA,wBAAoD"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ```ts
3
+ * import { build } from 'esbuild';
4
+ * import snagAnchor from '@frmwrkd/snag-anchor/esbuild';
5
+ * await build({ plugins: [snagAnchor()] });
6
+ * ```
7
+ */
8
+ import { createEsbuildPlugin } from 'unplugin';
9
+ import { unpluginFactory } from './index.js';
10
+ export default createEsbuildPlugin(unpluginFactory);
11
+ //# sourceMappingURL=esbuild.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"esbuild.js","sourceRoot":"","sources":["../src/esbuild.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAe,mBAAmB,CAAC,eAAe,CAAC,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type { UnpluginFactory, UnpluginInstance } from 'unplugin';
2
+ import type { AnchorOptions } from './options.js';
3
+ export declare const ANCHOR_ATTRIBUTE = "data-snag";
4
+ export declare const COMPONENT_ATTRIBUTE = "data-snag-cmp";
5
+ export type { AnchorOptions, AnchorMode, ResolvedAnchorOptions } from './options.js';
6
+ export { createFilter, DEFAULT_ATTRIBUTE, DEFAULT_COMPONENT_ATTRIBUTE, DEFAULT_EXCLUDE, DEFAULT_INCLUDE, isProductionEnv, normaliseId, resolveMode, resolveOptions, } from './options.js';
7
+ export { findGitRoot, resolveRoot, toPosix, toRepoRelative } from './root.js';
8
+ export type { AnchorAttribute, AnchorSourceMap, StampContext, StampSite } from './stamp.js';
9
+ export { buildAttributes, escapeAttributeValue, renderAttributes } from './stamp.js';
10
+ export { transformSource, stampContextFor } from './transform.js';
11
+ export type { AnchorTransformResult } from './transform.js';
12
+ export { default as snagAnchorBabelPlugin, stampJsx } from './babel/plugin.js';
13
+ export type { SnagBabelPluginOptions, StampResult } from './babel/plugin.js';
14
+ export { resolveComponentName } from './babel/component-name.js';
15
+ export { stampVue, sfcComponentName } from './vue/transform.js';
16
+ export type { VueStampResult } from './vue/transform.js';
17
+ export { createDevtoolsJsonMiddleware, devtoolsJson, devtoolsWorkspaceUuid, DEVTOOLS_JSON_PATH, uuidV5, } from './devtools-json.js';
18
+ export type { DevtoolsJson, DevtoolsJsonMiddleware } from './devtools-json.js';
19
+ export declare const unpluginFactory: UnpluginFactory<AnchorOptions | undefined, false>;
20
+ export declare const unplugin: UnpluginInstance<AnchorOptions | undefined, false>;
21
+ export default unplugin;
22
+ /**
23
+ * `emitRepoMap` (doc 02 §2.3, "run @frmwrkd/snag-repomap after build") is accepted and
24
+ * carried on `ResolvedAnchorOptions`, but nothing is wired to it yet:
25
+ * `@frmwrkd/snag-repomap` is milestone M3 and is not a dependency of this package. The
26
+ * option exists so a user's config does not have to change when it lands.
27
+ */
28
+ export declare const EMIT_REPO_MAP_IS_WIRED = false;
29
+ /** Re-exported so integrators can assert the endpoint without importing the module path. */
30
+ export declare const DEVTOOLS_JSON_ENDPOINT = "/.well-known/appspecific/com.chrome.devtools.json";
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAKlE,OAAO,KAAK,EAAE,aAAa,EAAyB,MAAM,cAAc,CAAC;AAGzE,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAC5C,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AAEnD,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,2BAA2B,EAC3B,eAAe,EACf,eAAe,EACf,eAAe,EACf,WAAW,EACX,WAAW,EACX,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC9E,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC/E,YAAY,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EACL,4BAA4B,EAC5B,YAAY,EACZ,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,GACP,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAgB/E,eAAO,MAAM,eAAe,EAAE,eAAe,CAAC,aAAa,GAAG,SAAS,EAAE,KAAK,CA8C7E,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,gBAAgB,CAAC,aAAa,GAAG,SAAS,EAAE,KAAK,CACvC,CAAC;AAElC,eAAe,QAAQ,CAAC;AAExB;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,QAAQ,CAAC;AAE5C,4FAA4F;AAC5F,eAAO,MAAM,sBAAsB,sDAAqB,CAAC"}