@gettrace/cli 2.0.6 → 2.0.8
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 +74 -3414
- package/instrument/trace-instrument.cjs +474 -0
- package/package.json +9 -5
- package/scripts/postinstall.js +110 -20
- package/dist/ast.d.ts +0 -48
- package/dist/ast.js +0 -203
- package/dist/ast.js.map +0 -1
- package/dist/file-lock.d.ts +0 -23
- package/dist/file-lock.js +0 -47
- package/dist/file-lock.js.map +0 -1
- package/dist/format.d.ts +0 -20
- package/dist/format.js +0 -68
- package/dist/format.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js.map +0 -1
- package/dist/lsp.d.ts +0 -46
- package/dist/lsp.js +0 -267
- package/dist/lsp.js.map +0 -1
- package/dist/search.d.ts +0 -31
- package/dist/search.js +0 -169
- package/dist/search.js.map +0 -1
- package/native-host/.homedir +0 -1
- package/native-host/native-host-debug.log +0 -4
package/scripts/postinstall.js
CHANGED
|
@@ -34,8 +34,10 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
34
34
|
|
|
35
35
|
// The extension IDs that are allowed to use this native host.
|
|
36
36
|
const ALLOWED_EXTENSION_IDS = [
|
|
37
|
-
// Development / unpacked extension ID
|
|
37
|
+
// Development / unpacked extension ID (macOS)
|
|
38
38
|
'jefgjgdcelekglimbmgplkaaabdfpgfm',
|
|
39
|
+
// Development / unpacked extension ID (Windows)
|
|
40
|
+
'fppbikhdjehmiidhjholkiaeeieoilfb',
|
|
39
41
|
// Add the published CWS extension ID here when you have it:
|
|
40
42
|
// 'PRODUCTION_EXTENSION_ID',
|
|
41
43
|
];
|
|
@@ -49,11 +51,18 @@ if (process.env.TRACE_EXTENSION_IDS) {
|
|
|
49
51
|
|
|
50
52
|
const HOST_NAME = 'dev.gettrace.host';
|
|
51
53
|
|
|
52
|
-
// Install native host files into
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
// Install native host files into platform-specific directories
|
|
55
|
+
// macOS/Linux: ~/.local/share/trace/native-host/ (no TCC permission issues)
|
|
56
|
+
// Windows: %LOCALAPPDATA%\trace\native-host\ (standard Windows app data location)
|
|
57
|
+
function getInstallDir() {
|
|
58
|
+
if (platform() === 'win32') {
|
|
59
|
+
const localAppData = process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local');
|
|
60
|
+
return join(localAppData, 'trace', 'native-host');
|
|
61
|
+
}
|
|
62
|
+
return join(homedir(), '.local', 'share', 'trace', 'native-host');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const INSTALL_DIR = getInstallDir();
|
|
57
66
|
const HOST_ENTRY = join(INSTALL_DIR, 'host-entry');
|
|
58
67
|
const HOST_CJS_DEST = join(INSTALL_DIR, 'host.cjs');
|
|
59
68
|
|
|
@@ -96,6 +105,84 @@ function generateHostEntry() {
|
|
|
96
105
|
writeFileSync(join(INSTALL_DIR, 'package.json'), readFileSync(pkgSrc), { mode: 0o644 });
|
|
97
106
|
}
|
|
98
107
|
|
|
108
|
+
// On Windows, create a .bat launcher that Chrome can execute
|
|
109
|
+
if (platform() === 'win32') {
|
|
110
|
+
generateWindowsHostEntry(nodePath, tracePath);
|
|
111
|
+
} else {
|
|
112
|
+
generateUnixHostEntry(nodePath, tracePath);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function generateWindowsHostEntry(nodePath, tracePath) {
|
|
117
|
+
const HOST_BAT = join(INSTALL_DIR, 'host-entry.bat');
|
|
118
|
+
const HOST_JS = join(INSTALL_DIR, 'host-entry.js');
|
|
119
|
+
|
|
120
|
+
// Create the JavaScript loader file
|
|
121
|
+
const jsContent = [
|
|
122
|
+
`// Auto-generated by @gettrace/cli postinstall — do not edit manually`,
|
|
123
|
+
`// Generated: ${new Date().toISOString()}`,
|
|
124
|
+
`// Node: ${nodePath}`,
|
|
125
|
+
tracePath ? `// Trace: ${tracePath}` : `// Trace: (resolves from PATH)`,
|
|
126
|
+
`'use strict';`,
|
|
127
|
+
``,
|
|
128
|
+
`// Fix PATH before anything else — Chrome launches with a minimal environment`,
|
|
129
|
+
tracePath
|
|
130
|
+
? `process.env.PATH = '${dirname(tracePath).replace(/\\/g, '\\\\')};${dirname(nodePath).replace(/\\/g, '\\\\')};' + (process.env.PATH || 'C:\\\\Windows\\\\System32');`
|
|
131
|
+
: `process.env.PATH = '${dirname(nodePath).replace(/\\/g, '\\\\')};;' + (process.env.PATH || 'C:\\\\Windows\\\\System32');`,
|
|
132
|
+
``,
|
|
133
|
+
...(() => {
|
|
134
|
+
const monorepoAgent = resolve(
|
|
135
|
+
__dirname, '..', '..', 'packages', 'trace-agent', 'dist', 'node', 'index.js'
|
|
136
|
+
);
|
|
137
|
+
const localCli = resolve(__dirname, '..', 'dist', 'index.js');
|
|
138
|
+
if (existsSync(monorepoAgent) && existsSync(localCli)) {
|
|
139
|
+
return [
|
|
140
|
+
`// Dev mode: use local CLI build instead of the global npm package`,
|
|
141
|
+
`process.env.TRACE_DEV_MODE = '1';`,
|
|
142
|
+
`process.env.TRACE_CLI_PATH = '${localCli.replace(/\\/g, '\\\\')}';`,
|
|
143
|
+
``,
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
return [];
|
|
147
|
+
})(),
|
|
148
|
+
`// Diagnostic logging — written synchronously so we catch crashes before async`,
|
|
149
|
+
`const fs = require('fs');`,
|
|
150
|
+
`const path = require('path');`,
|
|
151
|
+
`const logPath = path.join('${homedir().replace(/\\/g, '\\\\')}', 'trace-native-host.log');`,
|
|
152
|
+
`function log(msg) {`,
|
|
153
|
+
` const line = new Date().toISOString() + ' ' + msg + '\\n';`,
|
|
154
|
+
` try { fs.appendFileSync(logPath, line); } catch(_) {}`,
|
|
155
|
+
`}`,
|
|
156
|
+
``,
|
|
157
|
+
`log('[host-entry] started, node=' + process.version + ' pid=' + process.pid);`,
|
|
158
|
+
``,
|
|
159
|
+
`try {`,
|
|
160
|
+
` require('${HOST_CJS_DEST.replace(/\\/g, '\\\\')}');`,
|
|
161
|
+
` log('[host-entry] host.cjs loaded OK');`,
|
|
162
|
+
`} catch (err) {`,
|
|
163
|
+
` log('[host-entry] FATAL: ' + (err.stack || err));`,
|
|
164
|
+
` process.exit(1);`,
|
|
165
|
+
`}`,
|
|
166
|
+
].join('\n');
|
|
167
|
+
|
|
168
|
+
writeFileSync(HOST_JS, jsContent, { encoding: 'utf8' });
|
|
169
|
+
|
|
170
|
+
// Create the .bat launcher that Chrome will execute
|
|
171
|
+
const batContent = [
|
|
172
|
+
`@echo off`,
|
|
173
|
+
`REM Auto-generated by @gettrace/cli postinstall`,
|
|
174
|
+
`REM Node: ${nodePath}`,
|
|
175
|
+
`"${nodePath}" "${HOST_JS}" %*`,
|
|
176
|
+
].join('\r\n');
|
|
177
|
+
|
|
178
|
+
writeFileSync(HOST_BAT, batContent, { encoding: 'utf8' });
|
|
179
|
+
|
|
180
|
+
console.log(`✓ Generated Windows native host: ${HOST_BAT}`);
|
|
181
|
+
console.log(` Node binary: ${nodePath}`);
|
|
182
|
+
if (tracePath) console.log(` Trace binary: ${tracePath}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function generateUnixHostEntry(nodePath, tracePath) {
|
|
99
186
|
// Log paths — write to home dir only (always writable by Chrome)
|
|
100
187
|
const logPath1 = `${homedir()}/trace-native-host.log`;
|
|
101
188
|
|
|
@@ -112,16 +199,6 @@ function generateHostEntry() {
|
|
|
112
199
|
? `process.env.PATH = '${dirname(tracePath)}:${dirname(nodePath)}:' + (process.env.PATH || '/usr/bin:/bin');`
|
|
113
200
|
: `process.env.PATH = '${dirname(nodePath)}:/opt/homebrew/bin:/usr/local/bin:' + (process.env.PATH || '/usr/bin:/bin');`,
|
|
114
201
|
``,
|
|
115
|
-
// Inject dev mode env vars ONLY when running from the trace monorepo
|
|
116
|
-
// (the dev workflow). Detect that by the presence of the local agent
|
|
117
|
-
// build that dev-mode resolution actually targets:
|
|
118
|
-
// <repo>/packages/trace-agent/dist/node/index.js. In a published npm
|
|
119
|
-
// install this path does not exist (the package lives under
|
|
120
|
-
// node_modules/@gettrace/cli), so production NEVER enables dev mode.
|
|
121
|
-
//
|
|
122
|
-
// NOTE: do NOT key this off `<pkg>/dist/index.js` — the published CLI
|
|
123
|
-
// ships that file too, which previously made every install spuriously
|
|
124
|
-
// enable dev mode and fail to locate the agent.
|
|
125
202
|
...(() => {
|
|
126
203
|
const monorepoAgent = resolve(
|
|
127
204
|
__dirname, '..', '..', 'packages', 'trace-agent', 'dist', 'node', 'index.js'
|
|
@@ -179,10 +256,15 @@ function getNativeHostDir() {
|
|
|
179
256
|
}
|
|
180
257
|
|
|
181
258
|
function buildManifest() {
|
|
259
|
+
// On Windows, Chrome needs to execute a .bat file; Unix uses the shebang script
|
|
260
|
+
const executablePath = platform() === 'win32'
|
|
261
|
+
? join(INSTALL_DIR, 'host-entry.bat')
|
|
262
|
+
: HOST_ENTRY;
|
|
263
|
+
|
|
182
264
|
return JSON.stringify({
|
|
183
265
|
name: HOST_NAME,
|
|
184
266
|
description: 'Trace native host — spawns trace dev in the user\'s project directory',
|
|
185
|
-
path:
|
|
267
|
+
path: executablePath,
|
|
186
268
|
type: 'stdio',
|
|
187
269
|
allowed_origins: ALLOWED_EXTENSION_IDS.map(id => `chrome-extension://${id}/`),
|
|
188
270
|
}, null, 2);
|
|
@@ -190,15 +272,23 @@ function buildManifest() {
|
|
|
190
272
|
|
|
191
273
|
function registerOnWindows() {
|
|
192
274
|
const manifest = buildManifest();
|
|
193
|
-
|
|
275
|
+
// Store manifest in the same directory as the host files for consistency
|
|
276
|
+
const manifestPath = join(INSTALL_DIR, `${HOST_NAME}.json`);
|
|
194
277
|
mkdirSync(dirname(manifestPath), { recursive: true });
|
|
195
278
|
writeFileSync(manifestPath, manifest, 'utf8');
|
|
279
|
+
console.log(`✓ Native host manifest: ${manifestPath}`);
|
|
280
|
+
|
|
196
281
|
const regKey = `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${HOST_NAME}`;
|
|
197
282
|
try {
|
|
198
|
-
|
|
199
|
-
|
|
283
|
+
// Use double backslashes for Windows paths in registry
|
|
284
|
+
const manifestPathWin = manifestPath.replace(/\//g, '\\');
|
|
285
|
+
execSync(`reg add "${regKey}" /ve /t REG_SZ /d "${manifestPathWin}" /f`, { stdio: 'pipe' });
|
|
286
|
+
console.log(`✓ Native host registered in Windows registry`);
|
|
287
|
+
console.log(` Registry key: ${regKey}`);
|
|
288
|
+
console.log(` Manifest path: ${manifestPathWin}`);
|
|
200
289
|
} catch (e) {
|
|
201
290
|
console.warn('⚠ Could not register native host in Windows registry:', e.message);
|
|
291
|
+
console.warn(' Try running as Administrator or manually import the registry key');
|
|
202
292
|
}
|
|
203
293
|
}
|
|
204
294
|
|
package/dist/ast.d.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export interface ClassNameEdit {
|
|
2
|
-
/** Current className string to replace. Matched literally; partial match used as fallback. */
|
|
3
|
-
oldValue?: string;
|
|
4
|
-
/** New className string to write into the attribute. */
|
|
5
|
-
newValue: string;
|
|
6
|
-
/** Source line (1-indexed) from Fiber resolvedLine. Narrows the search strongly. */
|
|
7
|
-
lineHint?: number;
|
|
8
|
-
}
|
|
9
|
-
export interface ASTEditResult {
|
|
10
|
-
success: boolean;
|
|
11
|
-
code?: string;
|
|
12
|
-
error?: string;
|
|
13
|
-
/** Which scoring strategy produced the winning match. */
|
|
14
|
-
strategy?: 'line_exact' | 'line_proximity' | 'value_exact' | 'value_partial';
|
|
15
|
-
/** Line number of the matched JSXOpeningElement. */
|
|
16
|
-
matchedLine?: number;
|
|
17
|
-
}
|
|
18
|
-
export interface CSSRuleEdit {
|
|
19
|
-
/** CSS selector to find (exact match). Appends new rule if not found. */
|
|
20
|
-
selector: string;
|
|
21
|
-
/** Properties to set. Existing properties are updated; new ones are appended. */
|
|
22
|
-
properties: Record<string, string>;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Parse JSX/TSX source and replace a className attribute value.
|
|
26
|
-
*
|
|
27
|
-
* Matching priority (higher score wins):
|
|
28
|
-
* 1. Line-exact — node is within ±1 line of lineHint AND className contains oldValue (+100 line, +80 value)
|
|
29
|
-
* 2. Line-nearby — node is within ±5 lines of lineHint (+50 line)
|
|
30
|
-
* 3. Value-exact — className === oldValue (any line) (+80 value)
|
|
31
|
-
* 4. Value-partial— className.includes(oldValue) (any line) (+40 value)
|
|
32
|
-
* 5. Any — first className found (when no oldValue given) (+20)
|
|
33
|
-
*
|
|
34
|
-
* Returns { success: false } without modifying source if no match scores > 0.
|
|
35
|
-
*/
|
|
36
|
-
export declare function editJSXClassName(source: string, edit: ClassNameEdit): ASTEditResult;
|
|
37
|
-
/**
|
|
38
|
-
* Edit CSS properties within a specific selector block.
|
|
39
|
-
*
|
|
40
|
-
* - If the selector exists: updates existing properties in-place, appends new ones.
|
|
41
|
-
* - If the selector does not exist: appends a new rule at the end of the file.
|
|
42
|
-
* - Leaves all properties not mentioned in `edit.properties` untouched.
|
|
43
|
-
* - Strips any existing `!important` from injected values (clean code output).
|
|
44
|
-
*
|
|
45
|
-
* Uses a regex-based approach — a full CSS AST is overkill for targeted property
|
|
46
|
-
* edits and would add another large dep.
|
|
47
|
-
*/
|
|
48
|
-
export declare function editCSSRule(source: string, edit: CSSRuleEdit): ASTEditResult;
|
package/dist/ast.js
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// TRACE AST ENGINE
|
|
3
|
-
// Precise, AST-based source code modification for JSX/TSX files.
|
|
4
|
-
//
|
|
5
|
-
// Complements fuzzyReplace (EDIT_FILE) with guaranteed-correct
|
|
6
|
-
// structural edits that survive Prettier reformatting and
|
|
7
|
-
// dynamic className expressions that string matching cannot handle.
|
|
8
|
-
//
|
|
9
|
-
// Two exported functions:
|
|
10
|
-
// editJSXClassName — update a className attribute in JSX/TSX
|
|
11
|
-
// editCSSRule — patch properties inside a CSS selector block
|
|
12
|
-
//
|
|
13
|
-
// Deps (runtime): @babel/parser, @babel/traverse, @babel/generator, @babel/types
|
|
14
|
-
// Used by: EDIT_CLASSNAME handler in index.ts
|
|
15
|
-
// ============================================================
|
|
16
|
-
import { parse } from '@babel/parser';
|
|
17
|
-
import { createRequire } from 'module';
|
|
18
|
-
import * as t from '@babel/types';
|
|
19
|
-
// @babel/traverse and @babel/generator ship CJS-only bundles.
|
|
20
|
-
// Use createRequire so they work inside an ESM project (module:NodeNext).
|
|
21
|
-
const _require = createRequire(import.meta.url);
|
|
22
|
-
const _traverse = _require('@babel/traverse');
|
|
23
|
-
const _generate = _require('@babel/generator');
|
|
24
|
-
const traverse = (_traverse.default ?? _traverse);
|
|
25
|
-
const generate = (_generate.default ?? _generate);
|
|
26
|
-
// ── JSX className editing ─────────────────────────────────────────────────
|
|
27
|
-
/**
|
|
28
|
-
* Parse JSX/TSX source and replace a className attribute value.
|
|
29
|
-
*
|
|
30
|
-
* Matching priority (higher score wins):
|
|
31
|
-
* 1. Line-exact — node is within ±1 line of lineHint AND className contains oldValue (+100 line, +80 value)
|
|
32
|
-
* 2. Line-nearby — node is within ±5 lines of lineHint (+50 line)
|
|
33
|
-
* 3. Value-exact — className === oldValue (any line) (+80 value)
|
|
34
|
-
* 4. Value-partial— className.includes(oldValue) (any line) (+40 value)
|
|
35
|
-
* 5. Any — first className found (when no oldValue given) (+20)
|
|
36
|
-
*
|
|
37
|
-
* Returns { success: false } without modifying source if no match scores > 0.
|
|
38
|
-
*/
|
|
39
|
-
export function editJSXClassName(source, edit) {
|
|
40
|
-
let ast;
|
|
41
|
-
try {
|
|
42
|
-
ast = parse(source, {
|
|
43
|
-
sourceType: 'module',
|
|
44
|
-
plugins: ['jsx', 'typescript'],
|
|
45
|
-
errorRecovery: true, // tolerate minor syntax errors in source file
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
catch (err) {
|
|
49
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
50
|
-
return { success: false, error: `Parse error: ${msg}` };
|
|
51
|
-
}
|
|
52
|
-
const candidates = [];
|
|
53
|
-
traverse(ast, {
|
|
54
|
-
JSXOpeningElement(nodePath) {
|
|
55
|
-
for (const attr of nodePath.node.attributes) {
|
|
56
|
-
if (!t.isJSXAttribute(attr))
|
|
57
|
-
continue;
|
|
58
|
-
if (!t.isJSXIdentifier(attr.name, { name: 'className' }))
|
|
59
|
-
continue;
|
|
60
|
-
const line = attr.loc?.start.line ?? 0;
|
|
61
|
-
let score = 0;
|
|
62
|
-
// ── Score by line proximity ──
|
|
63
|
-
if (edit.lineHint && line > 0) {
|
|
64
|
-
const dist = Math.abs(line - edit.lineHint);
|
|
65
|
-
if (dist <= 1)
|
|
66
|
-
score += 100;
|
|
67
|
-
else if (dist <= 5)
|
|
68
|
-
score += 50;
|
|
69
|
-
else if (dist <= 15)
|
|
70
|
-
score += 15;
|
|
71
|
-
}
|
|
72
|
-
// ── Score by value match ──
|
|
73
|
-
const currentValue = _extractClassNameString(attr.value);
|
|
74
|
-
if (edit.oldValue) {
|
|
75
|
-
if (currentValue === edit.oldValue)
|
|
76
|
-
score += 80;
|
|
77
|
-
else if (currentValue?.includes(edit.oldValue))
|
|
78
|
-
score += 40;
|
|
79
|
-
// Dynamic expression (null) — can't verify value, give small score
|
|
80
|
-
else if (currentValue === null && edit.lineHint)
|
|
81
|
-
score += 20;
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
// No oldValue specified — any className is a candidate
|
|
85
|
-
score += 20;
|
|
86
|
-
}
|
|
87
|
-
if (score > 0) {
|
|
88
|
-
candidates.push({ nodePath, attrNode: attr, score, line });
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
if (candidates.length === 0) {
|
|
94
|
-
return { success: false, error: 'No className attribute found matching the given hints.' };
|
|
95
|
-
}
|
|
96
|
-
// Pick the highest-scoring candidate
|
|
97
|
-
candidates.sort((a, b) => b.score - a.score);
|
|
98
|
-
const best = candidates[0];
|
|
99
|
-
// Classify strategy for caller transparency
|
|
100
|
-
let strategy;
|
|
101
|
-
if (best.score >= 180)
|
|
102
|
-
strategy = 'line_exact';
|
|
103
|
-
else if (best.score >= 100)
|
|
104
|
-
strategy = 'line_proximity';
|
|
105
|
-
else if (best.score >= 80)
|
|
106
|
-
strategy = 'value_exact';
|
|
107
|
-
else
|
|
108
|
-
strategy = 'value_partial';
|
|
109
|
-
// Apply the edit — replace with StringLiteral
|
|
110
|
-
best.attrNode.value = t.stringLiteral(edit.newValue);
|
|
111
|
-
// Generate source back. retainLines keeps line numbers stable so diffs
|
|
112
|
-
// are minimal; jsescOption minimal avoids unnecessary unicode escaping.
|
|
113
|
-
const output = generate(ast, { retainLines: true, jsescOption: { minimal: true } }, source);
|
|
114
|
-
return {
|
|
115
|
-
success: true,
|
|
116
|
-
code: output.code,
|
|
117
|
-
strategy,
|
|
118
|
-
matchedLine: best.line,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Extract a plain string from a className JSXAttribute value.
|
|
123
|
-
* Returns null for dynamic expressions (template literals with interpolations,
|
|
124
|
-
* variables, function calls) — these cannot safely be string-replaced.
|
|
125
|
-
*/
|
|
126
|
-
function _extractClassNameString(value) {
|
|
127
|
-
if (!value)
|
|
128
|
-
return '';
|
|
129
|
-
if (t.isStringLiteral(value))
|
|
130
|
-
return value.value;
|
|
131
|
-
if (t.isJSXExpressionContainer(value)) {
|
|
132
|
-
const expr = value.expression;
|
|
133
|
-
if (t.isStringLiteral(expr))
|
|
134
|
-
return expr.value;
|
|
135
|
-
// Simple template literal with no expressions: `bg-red-500 p-4`
|
|
136
|
-
if (t.isTemplateLiteral(expr) && expr.expressions.length === 0) {
|
|
137
|
-
return expr.quasis[0]?.value.cooked ?? null;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return null; // dynamic — caller should use EDIT_FILE fuzzy replacer instead
|
|
141
|
-
}
|
|
142
|
-
// ── CSS rule editing ──────────────────────────────────────────────────────
|
|
143
|
-
/**
|
|
144
|
-
* Edit CSS properties within a specific selector block.
|
|
145
|
-
*
|
|
146
|
-
* - If the selector exists: updates existing properties in-place, appends new ones.
|
|
147
|
-
* - If the selector does not exist: appends a new rule at the end of the file.
|
|
148
|
-
* - Leaves all properties not mentioned in `edit.properties` untouched.
|
|
149
|
-
* - Strips any existing `!important` from injected values (clean code output).
|
|
150
|
-
*
|
|
151
|
-
* Uses a regex-based approach — a full CSS AST is overkill for targeted property
|
|
152
|
-
* edits and would add another large dep.
|
|
153
|
-
*/
|
|
154
|
-
export function editCSSRule(source, edit) {
|
|
155
|
-
// Clean properties: strip !important (Trace injected them for browser specificity;
|
|
156
|
-
// they don't belong in authored source files)
|
|
157
|
-
const props = {};
|
|
158
|
-
for (const [k, v] of Object.entries(edit.properties)) {
|
|
159
|
-
props[k] = v.replace(/\s*!important\s*$/, '').trim();
|
|
160
|
-
}
|
|
161
|
-
// Escape selector for use in RegExp
|
|
162
|
-
const escapedSel = edit.selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
163
|
-
const ruleRegex = new RegExp(`(${escapedSel}\\s*\\{)([^}]*)(\\})`, 'ms');
|
|
164
|
-
const match = ruleRegex.exec(source);
|
|
165
|
-
if (!match) {
|
|
166
|
-
// Selector not found — append new rule
|
|
167
|
-
const newRule = _buildCSSRule(edit.selector, props);
|
|
168
|
-
return {
|
|
169
|
-
success: true,
|
|
170
|
-
code: source.trimEnd() + '\n\n' + newRule + '\n',
|
|
171
|
-
strategy: 'value_partial', // reuse field to signal "appended"
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
const [full, open, body, close] = match;
|
|
175
|
-
let patchedBody = body;
|
|
176
|
-
for (const [prop, val] of Object.entries(props)) {
|
|
177
|
-
// Match `prop:` with optional surrounding whitespace
|
|
178
|
-
const propRegex = new RegExp(`([ \\t]*${prop.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*:\\s*)[^;\\n]+;?`, 'm');
|
|
179
|
-
if (propRegex.test(patchedBody)) {
|
|
180
|
-
patchedBody = patchedBody.replace(propRegex, `$1${val};`);
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
// Append missing property before closing brace
|
|
184
|
-
const indent = _detectIndent(patchedBody);
|
|
185
|
-
patchedBody = patchedBody.trimEnd() + `\n${indent}${prop}: ${val};`;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return {
|
|
189
|
-
success: true,
|
|
190
|
-
code: source.replace(full, open + patchedBody + close),
|
|
191
|
-
strategy: 'value_exact',
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
function _buildCSSRule(selector, props) {
|
|
195
|
-
const lines = Object.entries(props).map(([k, v]) => ` ${k}: ${v};`).join('\n');
|
|
196
|
-
return `${selector} {\n${lines}\n}`;
|
|
197
|
-
}
|
|
198
|
-
function _detectIndent(body) {
|
|
199
|
-
// Detect the indentation used by existing properties in this block
|
|
200
|
-
const match = body.match(/^([ \t]+)\S/m);
|
|
201
|
-
return match ? match[1] : ' ';
|
|
202
|
-
}
|
|
203
|
-
//# sourceMappingURL=ast.js.map
|
package/dist/ast.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ast.js","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"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"}
|
package/dist/file-lock.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-file async write-lock for the Trace IDE bridge.
|
|
3
|
-
*
|
|
4
|
-
* Problem: Node.js is single-threaded but WebSocket message handlers are
|
|
5
|
-
* async. Two EDIT_FILE messages arriving in quick succession will both
|
|
6
|
-
* start processing, and if a second message begins its read after the first
|
|
7
|
-
* has written but before the first's `await autoFormat` resolves, the second
|
|
8
|
-
* write lands on correctly-updated content. The real risk is a future where
|
|
9
|
-
* parallelism is added — e.g., multiple WS connections dispatching concurrent
|
|
10
|
-
* edits to the same file — where without a lock, the second write overwrites
|
|
11
|
-
* the first silently.
|
|
12
|
-
*
|
|
13
|
-
* Solution: Promise-chain semaphore keyed on the absolute file path.
|
|
14
|
-
* Each caller waits for the previous write to the same file to fully complete
|
|
15
|
-
* (including the async autoFormat step) before starting its own write.
|
|
16
|
-
*
|
|
17
|
-
* Usage:
|
|
18
|
-
* await withFileLock(filePath, async () => {
|
|
19
|
-
* fs.writeFileSync(filePath, newContent, 'utf-8');
|
|
20
|
-
* await autoFormat(filePath, projectPath);
|
|
21
|
-
* });
|
|
22
|
-
*/
|
|
23
|
-
export declare function withFileLock<T>(filePath: string, fn: () => Promise<T>): Promise<T>;
|
package/dist/file-lock.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-file async write-lock for the Trace IDE bridge.
|
|
3
|
-
*
|
|
4
|
-
* Problem: Node.js is single-threaded but WebSocket message handlers are
|
|
5
|
-
* async. Two EDIT_FILE messages arriving in quick succession will both
|
|
6
|
-
* start processing, and if a second message begins its read after the first
|
|
7
|
-
* has written but before the first's `await autoFormat` resolves, the second
|
|
8
|
-
* write lands on correctly-updated content. The real risk is a future where
|
|
9
|
-
* parallelism is added — e.g., multiple WS connections dispatching concurrent
|
|
10
|
-
* edits to the same file — where without a lock, the second write overwrites
|
|
11
|
-
* the first silently.
|
|
12
|
-
*
|
|
13
|
-
* Solution: Promise-chain semaphore keyed on the absolute file path.
|
|
14
|
-
* Each caller waits for the previous write to the same file to fully complete
|
|
15
|
-
* (including the async autoFormat step) before starting its own write.
|
|
16
|
-
*
|
|
17
|
-
* Usage:
|
|
18
|
-
* await withFileLock(filePath, async () => {
|
|
19
|
-
* fs.writeFileSync(filePath, newContent, 'utf-8');
|
|
20
|
-
* await autoFormat(filePath, projectPath);
|
|
21
|
-
* });
|
|
22
|
-
*/
|
|
23
|
-
// Map: absolute filePath → the Promise representing the current (or last) write.
|
|
24
|
-
const _locks = new Map();
|
|
25
|
-
export async function withFileLock(filePath, fn) {
|
|
26
|
-
// Grab any in-progress write for this file (or a resolved no-op if none).
|
|
27
|
-
const prior = _locks.get(filePath) ?? Promise.resolve();
|
|
28
|
-
// Create a "hold" promise that keeps this slot in the chain alive
|
|
29
|
-
// until our fn() completes and we call release().
|
|
30
|
-
let release;
|
|
31
|
-
const hold = new Promise(resolve => (release = resolve));
|
|
32
|
-
// Register this write as the current holder for this path.
|
|
33
|
-
// Anyone queueing after us will wait on `hold`.
|
|
34
|
-
_locks.set(filePath, hold);
|
|
35
|
-
try {
|
|
36
|
-
await prior; // Wait for any preceding write to finish
|
|
37
|
-
return await fn(); // Execute our write (includes autoFormat await)
|
|
38
|
-
}
|
|
39
|
-
finally {
|
|
40
|
-
release(); // Unblock the next write in the chain
|
|
41
|
-
// Clean up the map if nothing queued behind us
|
|
42
|
-
if (_locks.get(filePath) === hold) {
|
|
43
|
-
_locks.delete(filePath);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=file-lock.js.map
|
package/dist/file-lock.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock.js","sourceRoot":"","sources":["../src/file-lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,iFAAiF;AACjF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;AAEhD,MAAM,CAAC,KAAK,UAAU,YAAY,CAC9B,QAAgB,EAChB,EAAoB;IAEpB,0EAA0E;IAC1E,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAExD,kEAAkE;IAClE,kDAAkD;IAClD,IAAI,OAAoB,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAE/D,2DAA2D;IAC3D,gDAAgD;IAChD,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAE3B,IAAI,CAAC;QACD,MAAM,KAAK,CAAC,CAAO,yCAAyC;QAC5D,OAAO,MAAM,EAAE,EAAE,CAAC,CAAC,gDAAgD;IACvE,CAAC;YAAS,CAAC;QACP,OAAO,EAAE,CAAC,CAAS,sCAAsC;QACzD,+CAA+C;QAC/C,IAAI,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC;YAChC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;IACL,CAAC;AACL,CAAC"}
|
package/dist/format.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auto-format a project file after agent writes.
|
|
3
|
-
*
|
|
4
|
-
* Cascade: Prettier → ESLint --fix
|
|
5
|
-
*
|
|
6
|
-
* - Prettier handles JS/TS/CSS/HTML/JSON/Markdown formatting.
|
|
7
|
-
* - ESLint --fix handles import ordering, unused imports, and React-specific
|
|
8
|
-
* rules that Prettier does not enforce (runs only if Prettier is absent or
|
|
9
|
-
* fails its own config check).
|
|
10
|
-
*
|
|
11
|
-
* Both are discovered from the *project's own* node_modules — not a global
|
|
12
|
-
* install — so they respect the project's configured rules and parser options.
|
|
13
|
-
*
|
|
14
|
-
* Errors from either formatter are intentionally swallowed: a format failure
|
|
15
|
-
* must never block an agent's write. The function returns:
|
|
16
|
-
* 'prettier' — Prettier ran successfully
|
|
17
|
-
* 'eslint' — ESLint ran successfully (Prettier was absent/failed)
|
|
18
|
-
* null — file type is not formattable, or no formatter found
|
|
19
|
-
*/
|
|
20
|
-
export declare function autoFormat(filePath: string, projectPath: string): Promise<string | null>;
|
package/dist/format.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auto-format a project file after agent writes.
|
|
3
|
-
*
|
|
4
|
-
* Cascade: Prettier → ESLint --fix
|
|
5
|
-
*
|
|
6
|
-
* - Prettier handles JS/TS/CSS/HTML/JSON/Markdown formatting.
|
|
7
|
-
* - ESLint --fix handles import ordering, unused imports, and React-specific
|
|
8
|
-
* rules that Prettier does not enforce (runs only if Prettier is absent or
|
|
9
|
-
* fails its own config check).
|
|
10
|
-
*
|
|
11
|
-
* Both are discovered from the *project's own* node_modules — not a global
|
|
12
|
-
* install — so they respect the project's configured rules and parser options.
|
|
13
|
-
*
|
|
14
|
-
* Errors from either formatter are intentionally swallowed: a format failure
|
|
15
|
-
* must never block an agent's write. The function returns:
|
|
16
|
-
* 'prettier' — Prettier ran successfully
|
|
17
|
-
* 'eslint' — ESLint ran successfully (Prettier was absent/failed)
|
|
18
|
-
* null — file type is not formattable, or no formatter found
|
|
19
|
-
*/
|
|
20
|
-
import * as fs from 'fs';
|
|
21
|
-
import * as path from 'path';
|
|
22
|
-
import { exec } from 'child_process';
|
|
23
|
-
import { promisify } from 'util';
|
|
24
|
-
const execAsync = promisify(exec);
|
|
25
|
-
const JS_EXTS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.mts', '.cts']);
|
|
26
|
-
const CSS_EXTS = new Set(['.css', '.scss', '.less']);
|
|
27
|
-
const HTML_EXTS = new Set(['.html', '.htm', '.vue', '.svelte']);
|
|
28
|
-
const ALL_FORMATTABLE = new Set([
|
|
29
|
-
...JS_EXTS, ...CSS_EXTS, ...HTML_EXTS,
|
|
30
|
-
'.json', '.md', '.yaml', '.yml',
|
|
31
|
-
]);
|
|
32
|
-
export async function autoFormat(filePath, projectPath) {
|
|
33
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
34
|
-
if (!ALL_FORMATTABLE.has(ext))
|
|
35
|
-
return null;
|
|
36
|
-
// ── 1. Prettier (first choice) ────────────────────────────────────────────
|
|
37
|
-
// Handles JS/TS/CSS/HTML/JSON/Markdown. Respects .prettierrc / prettier.config.js.
|
|
38
|
-
const prettierBin = path.join(projectPath, 'node_modules', '.bin', 'prettier');
|
|
39
|
-
if (fs.existsSync(prettierBin)) {
|
|
40
|
-
try {
|
|
41
|
-
await execAsync(`"${prettierBin}" --write "${filePath}"`, { cwd: projectPath });
|
|
42
|
-
return 'prettier';
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// Prettier config error or unparseable file — fall through to ESLint.
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
// ── 2. ESLint --fix (JS/TS only) ─────────────────────────────────────────
|
|
49
|
-
// Handles import ordering (eslint-plugin-import), unused imports
|
|
50
|
-
// (eslint-plugin-unused-imports), and React-specific rules that Prettier
|
|
51
|
-
// does not enforce. Runs after Prettier so lint rules don't fight formatting.
|
|
52
|
-
if (JS_EXTS.has(ext)) {
|
|
53
|
-
const eslintBin = path.join(projectPath, 'node_modules', '.bin', 'eslint');
|
|
54
|
-
if (fs.existsSync(eslintBin)) {
|
|
55
|
-
try {
|
|
56
|
-
// --fix mutates the file; lint violations that can't be auto-fixed
|
|
57
|
-
// are reported to stderr (ignored here) but the file is still improved.
|
|
58
|
-
await execAsync(`"${eslintBin}" --fix "${filePath}"`, { cwd: projectPath });
|
|
59
|
-
return 'eslint';
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
// Lint violations are expected — fixable ones were still applied.
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
//# sourceMappingURL=format.js.map
|
package/dist/format.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEjC,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AAElC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AACxF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AACrD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAChE,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC5B,GAAG,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,SAAS;IACrC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM;CAClC,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB,EAAE,WAAmB;IAClE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,6EAA6E;IAC7E,mFAAmF;IACnF,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAC/E,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,IAAI,CAAC;YACD,MAAM,SAAS,CAAC,IAAI,WAAW,cAAc,QAAQ,GAAG,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC;YAChF,OAAO,UAAU,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACL,sEAAsE;QAC1E,CAAC;IACL,CAAC;IAED,4EAA4E;IAC5E,iEAAiE;IACjE,yEAAyE;IACzE,8EAA8E;IAC9E,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC3E,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACD,mEAAmE;gBACnE,wEAAwE;gBACxE,MAAM,SAAS,CAAC,IAAI,SAAS,YAAY,QAAQ,GAAG,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC;gBAC5E,OAAO,QAAQ,CAAC;YACpB,CAAC;YAAC,MAAM,CAAC;gBACL,kEAAkE;YACtE,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC"}
|
package/dist/index.d.ts
DELETED