@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.
- package/LICENSE +19 -0
- package/README.md +305 -0
- package/dist/babel/component-name.d.ts +42 -0
- package/dist/babel/component-name.d.ts.map +1 -0
- package/dist/babel/component-name.js +107 -0
- package/dist/babel/component-name.js.map +1 -0
- package/dist/babel/plugin.d.ts +69 -0
- package/dist/babel/plugin.d.ts.map +1 -0
- package/dist/babel/plugin.js +272 -0
- package/dist/babel/plugin.js.map +1 -0
- package/dist/babel/walk.d.ts +8 -0
- package/dist/babel/walk.d.ts.map +1 -0
- package/dist/babel/walk.js +50 -0
- package/dist/babel/walk.js.map +1 -0
- package/dist/devtools-json.d.ts +45 -0
- package/dist/devtools-json.d.ts.map +1 -0
- package/dist/devtools-json.js +117 -0
- package/dist/devtools-json.js.map +1 -0
- package/dist/esbuild.d.ts +3 -0
- package/dist/esbuild.d.ts.map +1 -0
- package/dist/esbuild.js +11 -0
- package/dist/esbuild.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +90 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.cjs +44 -0
- package/dist/loader.cjs.map +1 -0
- package/dist/loader.d.cts +2 -0
- package/dist/loader.d.cts.map +1 -0
- package/dist/magic-string.d.ts +23 -0
- package/dist/magic-string.d.ts.map +1 -0
- package/dist/magic-string.js +23 -0
- package/dist/magic-string.js.map +1 -0
- package/dist/nuxt.d.ts +14 -0
- package/dist/nuxt.d.ts.map +1 -0
- package/dist/nuxt.js +29 -0
- package/dist/nuxt.js.map +1 -0
- package/dist/options.d.ts +101 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +122 -0
- package/dist/options.js.map +1 -0
- package/dist/rollup.d.ts +3 -0
- package/dist/rollup.d.ts.map +1 -0
- package/dist/rollup.js +11 -0
- package/dist/rollup.js.map +1 -0
- package/dist/root.d.ts +38 -0
- package/dist/root.d.ts.map +1 -0
- package/dist/root.js +101 -0
- package/dist/root.js.map +1 -0
- package/dist/rspack.d.ts +3 -0
- package/dist/rspack.d.ts.map +1 -0
- package/dist/rspack.js +11 -0
- package/dist/rspack.js.map +1 -0
- package/dist/stamp.d.ts +66 -0
- package/dist/stamp.d.ts.map +1 -0
- package/dist/stamp.js +28 -0
- package/dist/stamp.js.map +1 -0
- package/dist/transform.d.ts +14 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/transform.js +54 -0
- package/dist/transform.js.map +1 -0
- package/dist/vite.d.ts +3 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +11 -0
- package/dist/vite.js.map +1 -0
- package/dist/vue/transform.d.ts +50 -0
- package/dist/vue/transform.d.ts.map +1 -0
- package/dist/vue/transform.js +188 -0
- package/dist/vue/transform.js.map +1 -0
- package/dist/webpack.d.ts +3 -0
- package/dist/webpack.d.ts.map +1 -0
- package/dist/webpack.js +15 -0
- package/dist/webpack.js.map +1 -0
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"esbuild.d.ts","sourceRoot":"","sources":["../src/esbuild.ts"],"names":[],"mappings":";AAUA,wBAAoD"}
|
package/dist/esbuild.js
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|