@glasstrace/sdk 1.1.1 → 1.1.2
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/README.md +78 -1
- package/dist/{chunk-DIM4JRXM.js → chunk-2M57EO6U.js} +2 -2
- package/dist/{chunk-P22UQ2OJ.js → chunk-3LILTM3T.js} +1 -1
- package/dist/chunk-3LILTM3T.js.map +1 -0
- package/dist/{chunk-MXDZHFJQ.js → chunk-C567H5EQ.js} +2 -2
- package/dist/{chunk-7SZQN6IU.js → chunk-NB7GJE4S.js} +2 -2
- package/dist/chunk-NB7GJE4S.js.map +1 -0
- package/dist/{chunk-ZRDQ6ZKI.js → chunk-UJ2JC7PZ.js} +92 -473
- package/dist/chunk-UJ2JC7PZ.js.map +1 -0
- package/dist/{chunk-Y26HJUPD.js → chunk-Z35HKVSO.js} +135 -8
- package/dist/chunk-Z35HKVSO.js.map +1 -0
- package/dist/cli/init.cjs +463 -440
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.js +434 -63
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/mcp-add.cjs +14 -2
- package/dist/cli/mcp-add.cjs.map +1 -1
- package/dist/cli/mcp-add.js +17 -5
- package/dist/cli/mcp-add.js.map +1 -1
- package/dist/cli/status.cjs.map +1 -1
- package/dist/cli/status.js +1 -3
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/uninit.cjs +3 -3
- package/dist/cli/uninit.cjs.map +1 -1
- package/dist/cli/uninit.js +3 -3
- package/dist/cli/validate.cjs +14162 -2
- package/dist/cli/validate.cjs.map +1 -1
- package/dist/cli/validate.d.cts +7 -3
- package/dist/cli/validate.d.ts +7 -3
- package/dist/cli/validate.js +25 -2
- package/dist/cli/validate.js.map +1 -1
- package/dist/index.cjs +134 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/{monorepo-GSL6JD3G.js → monorepo-PFVNPQ6X.js} +3 -5
- package/dist/node-entry.cjs +134 -5
- package/dist/node-entry.cjs.map +1 -1
- package/dist/node-entry.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-7SZQN6IU.js.map +0 -1
- package/dist/chunk-P22UQ2OJ.js.map +0 -1
- package/dist/chunk-Y26HJUPD.js.map +0 -1
- package/dist/chunk-ZRDQ6ZKI.js.map +0 -1
- /package/dist/{chunk-DIM4JRXM.js.map → chunk-2M57EO6U.js.map} +0 -0
- /package/dist/{chunk-MXDZHFJQ.js.map → chunk-C567H5EQ.js.map} +0 -0
- /package/dist/{monorepo-GSL6JD3G.js.map → monorepo-PFVNPQ6X.js.map} +0 -0
package/dist/cli/init.cjs
CHANGED
|
@@ -31,6 +31,26 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
31
|
));
|
|
32
32
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
33
|
|
|
34
|
+
// src/cli/constants.ts
|
|
35
|
+
function formatAgentName(name) {
|
|
36
|
+
const displayNames = {
|
|
37
|
+
claude: "Claude Code",
|
|
38
|
+
codex: "Codex",
|
|
39
|
+
gemini: "Gemini",
|
|
40
|
+
cursor: "Cursor",
|
|
41
|
+
windsurf: "Windsurf",
|
|
42
|
+
generic: "Generic helper"
|
|
43
|
+
};
|
|
44
|
+
return displayNames[name];
|
|
45
|
+
}
|
|
46
|
+
var NEXT_CONFIG_NAMES;
|
|
47
|
+
var init_constants = __esm({
|
|
48
|
+
"src/cli/constants.ts"() {
|
|
49
|
+
"use strict";
|
|
50
|
+
NEXT_CONFIG_NAMES = ["next.config.ts", "next.config.js", "next.config.mjs"];
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
34
54
|
// ../../node_modules/zod/v4/core/core.js
|
|
35
55
|
// @__NO_SIDE_EFFECTS__
|
|
36
56
|
function $constructor(name, initializer3, params) {
|
|
@@ -14932,454 +14952,50 @@ var init_mcp_runtime = __esm({
|
|
|
14932
14952
|
}
|
|
14933
14953
|
});
|
|
14934
14954
|
|
|
14935
|
-
// src/
|
|
14936
|
-
function
|
|
14937
|
-
const displayNames = {
|
|
14938
|
-
claude: "Claude Code",
|
|
14939
|
-
codex: "Codex",
|
|
14940
|
-
gemini: "Gemini",
|
|
14941
|
-
cursor: "Cursor",
|
|
14942
|
-
windsurf: "Windsurf",
|
|
14943
|
-
generic: "Generic"
|
|
14944
|
-
};
|
|
14945
|
-
return displayNames[name];
|
|
14946
|
-
}
|
|
14947
|
-
var NEXT_CONFIG_NAMES;
|
|
14948
|
-
var init_constants = __esm({
|
|
14949
|
-
"src/cli/constants.ts"() {
|
|
14950
|
-
"use strict";
|
|
14951
|
-
init_mcp_runtime();
|
|
14952
|
-
NEXT_CONFIG_NAMES = ["next.config.ts", "next.config.js", "next.config.mjs"];
|
|
14953
|
-
}
|
|
14954
|
-
});
|
|
14955
|
-
|
|
14956
|
-
// src/cli/scaffolder.ts
|
|
14957
|
-
function hasRegisterGlasstraceCall(content) {
|
|
14958
|
-
return content.split("\n").some((line) => {
|
|
14959
|
-
const uncommented = line.replace(/\/\/.*$/, "");
|
|
14960
|
-
return /\bregisterGlasstrace\s*\(/.test(uncommented);
|
|
14961
|
-
});
|
|
14962
|
-
}
|
|
14963
|
-
function injectRegisterGlasstrace(content) {
|
|
14964
|
-
if (hasRegisterGlasstraceCall(content)) {
|
|
14965
|
-
return { injected: false, content };
|
|
14966
|
-
}
|
|
14967
|
-
const registerFnRegex = /export\s+(?:async\s+)?function\s+register\s*\([^)]*\)\s*\{/;
|
|
14968
|
-
const match = registerFnRegex.exec(content);
|
|
14969
|
-
if (!match) {
|
|
14970
|
-
return { injected: false, content };
|
|
14971
|
-
}
|
|
14972
|
-
const afterBrace = content.slice(match.index + match[0].length);
|
|
14973
|
-
const indentMatch = /\n([ \t]+)/.exec(afterBrace);
|
|
14974
|
-
const indent = indentMatch ? indentMatch[1] : " ";
|
|
14975
|
-
const importLine = 'import { registerGlasstrace } from "@glasstrace/sdk";\n';
|
|
14976
|
-
const hasGlasstraceImport2 = content.includes("@glasstrace/sdk");
|
|
14977
|
-
const insertPoint = match.index + match[0].length;
|
|
14978
|
-
const callInjection = `
|
|
14979
|
-
${indent}// Glasstrace must be registered before other instrumentation
|
|
14980
|
-
${indent}registerGlasstrace();
|
|
14981
|
-
`;
|
|
14982
|
-
let modified;
|
|
14983
|
-
if (hasGlasstraceImport2) {
|
|
14984
|
-
const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
|
|
14985
|
-
const importMatch = importRegex.exec(content);
|
|
14986
|
-
if (importMatch) {
|
|
14987
|
-
const specifiers = importMatch[1];
|
|
14988
|
-
const alreadyImported = specifiers.split(",").some((s) => s.trim() === "registerGlasstrace");
|
|
14989
|
-
if (alreadyImported) {
|
|
14990
|
-
modified = content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
|
|
14991
|
-
} else {
|
|
14992
|
-
const existingImports = specifiers.trimEnd();
|
|
14993
|
-
const separator = existingImports.endsWith(",") ? " " : ", ";
|
|
14994
|
-
const updatedImport = `import { ${existingImports.trim()}${separator}registerGlasstrace } from "@glasstrace/sdk"`;
|
|
14995
|
-
modified = content.replace(importMatch[0], updatedImport);
|
|
14996
|
-
const newMatch = registerFnRegex.exec(modified);
|
|
14997
|
-
if (newMatch) {
|
|
14998
|
-
const newInsertPoint = newMatch.index + newMatch[0].length;
|
|
14999
|
-
modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
|
|
15000
|
-
}
|
|
15001
|
-
}
|
|
15002
|
-
} else {
|
|
15003
|
-
modified = importLine + content;
|
|
15004
|
-
const newMatch = registerFnRegex.exec(modified);
|
|
15005
|
-
if (newMatch) {
|
|
15006
|
-
const newInsertPoint = newMatch.index + newMatch[0].length;
|
|
15007
|
-
modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
|
|
15008
|
-
}
|
|
15009
|
-
}
|
|
15010
|
-
} else {
|
|
15011
|
-
modified = importLine + content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
|
|
15012
|
-
}
|
|
15013
|
-
return { injected: true, content: modified };
|
|
15014
|
-
}
|
|
15015
|
-
function resolveInstrumentationTarget(projectRoot) {
|
|
15016
|
-
const rootExisting = [];
|
|
15017
|
-
const srcExisting = [];
|
|
15018
|
-
for (const name of INSTRUMENTATION_FILENAMES) {
|
|
15019
|
-
const rootPath = path.join(projectRoot, name);
|
|
15020
|
-
if (isRegularFile(rootPath)) {
|
|
15021
|
-
rootExisting.push(rootPath);
|
|
15022
|
-
}
|
|
15023
|
-
const srcPath = path.join(projectRoot, "src", name);
|
|
15024
|
-
if (isRegularFile(srcPath)) {
|
|
15025
|
-
srcExisting.push(srcPath);
|
|
15026
|
-
}
|
|
15027
|
-
}
|
|
15028
|
-
const existing = [...rootExisting, ...srcExisting];
|
|
15029
|
-
if (rootExisting.length > 0 && srcExisting.length > 0) {
|
|
15030
|
-
return {
|
|
15031
|
-
target: null,
|
|
15032
|
-
layout: null,
|
|
15033
|
-
existing,
|
|
15034
|
-
rootExisting,
|
|
15035
|
-
srcExisting,
|
|
15036
|
-
conflict: true
|
|
15037
|
-
};
|
|
15038
|
-
}
|
|
15039
|
-
if (srcExisting.length > 0) {
|
|
15040
|
-
return {
|
|
15041
|
-
target: srcExisting[0],
|
|
15042
|
-
layout: "src",
|
|
15043
|
-
existing,
|
|
15044
|
-
rootExisting,
|
|
15045
|
-
srcExisting,
|
|
15046
|
-
conflict: false
|
|
15047
|
-
};
|
|
15048
|
-
}
|
|
15049
|
-
if (rootExisting.length > 0) {
|
|
15050
|
-
return {
|
|
15051
|
-
target: rootExisting[0],
|
|
15052
|
-
layout: "root",
|
|
15053
|
-
existing,
|
|
15054
|
-
rootExisting,
|
|
15055
|
-
srcExisting,
|
|
15056
|
-
conflict: false
|
|
15057
|
-
};
|
|
15058
|
-
}
|
|
15059
|
-
const srcDir = path.join(projectRoot, "src");
|
|
15060
|
-
const layout = isDirectory(srcDir) ? "src" : "root";
|
|
15061
|
-
const target = layout === "src" ? path.join(projectRoot, "src", "instrumentation.ts") : path.join(projectRoot, "instrumentation.ts");
|
|
15062
|
-
return {
|
|
15063
|
-
target,
|
|
15064
|
-
layout,
|
|
15065
|
-
existing,
|
|
15066
|
-
rootExisting,
|
|
15067
|
-
srcExisting,
|
|
15068
|
-
conflict: false
|
|
15069
|
-
};
|
|
15070
|
-
}
|
|
15071
|
-
function isDirectory(p) {
|
|
14955
|
+
// src/agent-detection/detect.ts
|
|
14956
|
+
async function pathExists(path10, mode = import_node_fs.constants.R_OK) {
|
|
15072
14957
|
try {
|
|
15073
|
-
|
|
14958
|
+
await (0, import_promises.access)(path10, mode);
|
|
14959
|
+
return true;
|
|
15074
14960
|
} catch {
|
|
15075
14961
|
return false;
|
|
15076
14962
|
}
|
|
15077
14963
|
}
|
|
15078
|
-
function
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
if (
|
|
15082
|
-
return
|
|
14964
|
+
async function findGitRoot(startDir) {
|
|
14965
|
+
let current = (0, import_node_path.resolve)(startDir);
|
|
14966
|
+
while (true) {
|
|
14967
|
+
if (await pathExists((0, import_node_path.join)(current, ".git"), import_node_fs.constants.F_OK)) {
|
|
14968
|
+
return current;
|
|
15083
14969
|
}
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
|
|
15087
|
-
}
|
|
15088
|
-
}
|
|
15089
|
-
function appendRegisterFunction(content) {
|
|
15090
|
-
const importLine = 'import { registerGlasstrace } from "@glasstrace/sdk";\n';
|
|
15091
|
-
const functionBlock = "\nexport async function register() {\n // Glasstrace must be registered before Prisma instrumentation\n // to ensure all ORM spans are captured correctly.\n // If you use @prisma/instrumentation, import it after this call.\n registerGlasstrace();\n}\n";
|
|
15092
|
-
let withImport = content;
|
|
15093
|
-
const hasGlasstraceImport2 = content.includes("@glasstrace/sdk");
|
|
15094
|
-
if (!hasGlasstraceImport2) {
|
|
15095
|
-
withImport = importLine + content;
|
|
15096
|
-
} else {
|
|
15097
|
-
const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
|
|
15098
|
-
const importMatch = importRegex.exec(content);
|
|
15099
|
-
if (importMatch) {
|
|
15100
|
-
const specifiers = importMatch[1];
|
|
15101
|
-
const alreadyImported = specifiers.split(",").some((s) => s.trim() === "registerGlasstrace");
|
|
15102
|
-
if (!alreadyImported) {
|
|
15103
|
-
const existingImports = specifiers.trimEnd();
|
|
15104
|
-
const separator = existingImports.endsWith(",") ? " " : ", ";
|
|
15105
|
-
const updatedImport = `import { ${existingImports.trim()}${separator}registerGlasstrace } from "@glasstrace/sdk"`;
|
|
15106
|
-
withImport = content.replace(importMatch[0], updatedImport);
|
|
15107
|
-
}
|
|
15108
|
-
} else {
|
|
15109
|
-
withImport = importLine + content;
|
|
14970
|
+
const parent = (0, import_node_path.dirname)(current);
|
|
14971
|
+
if (parent === current) {
|
|
14972
|
+
break;
|
|
15110
14973
|
}
|
|
14974
|
+
current = parent;
|
|
15111
14975
|
}
|
|
15112
|
-
|
|
15113
|
-
return withImport + trailingNewline + functionBlock;
|
|
14976
|
+
return (0, import_node_path.resolve)(startDir);
|
|
15114
14977
|
}
|
|
15115
|
-
|
|
15116
|
-
if (!process.stdin.isTTY) return defaultValue;
|
|
15117
|
-
const readline2 = await import("node:readline");
|
|
15118
|
-
const rl = readline2.createInterface({
|
|
15119
|
-
input: process.stdin,
|
|
15120
|
-
output: process.stdout
|
|
15121
|
-
});
|
|
14978
|
+
function isCliAvailable(binary) {
|
|
15122
14979
|
return new Promise((resolve2) => {
|
|
15123
|
-
const
|
|
15124
|
-
|
|
15125
|
-
|
|
15126
|
-
const trimmed = answer.trim().toLowerCase();
|
|
15127
|
-
if (trimmed === "") {
|
|
15128
|
-
resolve2(defaultValue);
|
|
15129
|
-
return;
|
|
15130
|
-
}
|
|
15131
|
-
resolve2(trimmed === "y" || trimmed === "yes");
|
|
14980
|
+
const command = process.platform === "win32" ? "where" : "which";
|
|
14981
|
+
(0, import_node_child_process.execFile)(command, [binary], (error48) => {
|
|
14982
|
+
resolve2(error48 === null);
|
|
15132
14983
|
});
|
|
15133
14984
|
});
|
|
15134
14985
|
}
|
|
15135
|
-
async function
|
|
15136
|
-
const
|
|
15137
|
-
|
|
15138
|
-
|
|
15139
|
-
|
|
15140
|
-
|
|
15141
|
-
|
|
15142
|
-
|
|
15143
|
-
|
|
15144
|
-
|
|
15145
|
-
};
|
|
14986
|
+
async function detectAgents(projectRoot) {
|
|
14987
|
+
const resolvedRoot = (0, import_node_path.resolve)(projectRoot);
|
|
14988
|
+
let rootStat;
|
|
14989
|
+
try {
|
|
14990
|
+
rootStat = await (0, import_promises.stat)(resolvedRoot);
|
|
14991
|
+
} catch (err) {
|
|
14992
|
+
const code = err.code;
|
|
14993
|
+
throw new Error(
|
|
14994
|
+
`projectRoot does not exist: ${resolvedRoot}` + (code ? ` (${code})` : "")
|
|
14995
|
+
);
|
|
15146
14996
|
}
|
|
15147
|
-
|
|
15148
|
-
|
|
15149
|
-
if (filePath === null || layout === null) {
|
|
15150
|
-
return { action: "unrecognized" };
|
|
15151
|
-
}
|
|
15152
|
-
const force = options.force === true;
|
|
15153
|
-
const prompt = options.prompt ?? defaultInstrumentationPrompt;
|
|
15154
|
-
if (!fs.existsSync(filePath)) {
|
|
15155
|
-
const content = `import { registerGlasstrace } from "@glasstrace/sdk";
|
|
15156
|
-
|
|
15157
|
-
export async function register() {
|
|
15158
|
-
// Glasstrace must be registered before Prisma instrumentation
|
|
15159
|
-
// to ensure all ORM spans are captured correctly.
|
|
15160
|
-
// If you use @prisma/instrumentation, import it after this call.
|
|
15161
|
-
registerGlasstrace();
|
|
15162
|
-
}
|
|
15163
|
-
`;
|
|
15164
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
15165
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
15166
|
-
return { action: "created", filePath, layout };
|
|
15167
|
-
}
|
|
15168
|
-
const existing = fs.readFileSync(filePath, "utf-8");
|
|
15169
|
-
if (hasRegisterGlasstraceCall(existing)) {
|
|
15170
|
-
return { action: "already-registered", filePath, layout };
|
|
15171
|
-
}
|
|
15172
|
-
if (!force) {
|
|
15173
|
-
const approved = await prompt(
|
|
15174
|
-
`Merge registerGlasstrace() into ${path.relative(projectRoot, filePath)}?`,
|
|
15175
|
-
false
|
|
15176
|
-
);
|
|
15177
|
-
if (!approved) {
|
|
15178
|
-
return { action: "skipped", filePath, layout };
|
|
15179
|
-
}
|
|
15180
|
-
}
|
|
15181
|
-
const injectResult = injectRegisterGlasstrace(existing);
|
|
15182
|
-
if (injectResult.injected) {
|
|
15183
|
-
fs.writeFileSync(filePath, injectResult.content, "utf-8");
|
|
15184
|
-
return { action: "injected", filePath, layout };
|
|
15185
|
-
}
|
|
15186
|
-
const appended = appendRegisterFunction(existing);
|
|
15187
|
-
fs.writeFileSync(filePath, appended, "utf-8");
|
|
15188
|
-
return { action: "appended", filePath, layout };
|
|
15189
|
-
}
|
|
15190
|
-
async function scaffoldNextConfig(projectRoot) {
|
|
15191
|
-
let configPath;
|
|
15192
|
-
let configName;
|
|
15193
|
-
for (const name of NEXT_CONFIG_NAMES) {
|
|
15194
|
-
const candidate = path.join(projectRoot, name);
|
|
15195
|
-
if (fs.existsSync(candidate)) {
|
|
15196
|
-
configPath = candidate;
|
|
15197
|
-
configName = name;
|
|
15198
|
-
break;
|
|
15199
|
-
}
|
|
15200
|
-
}
|
|
15201
|
-
if (configPath === void 0 || configName === void 0) {
|
|
15202
|
-
return null;
|
|
15203
|
-
}
|
|
15204
|
-
const existing = fs.readFileSync(configPath, "utf-8");
|
|
15205
|
-
if (existing.trim().length === 0) {
|
|
15206
|
-
return { modified: false, reason: "empty-file" };
|
|
15207
|
-
}
|
|
15208
|
-
if (existing.includes("withGlasstraceConfig")) {
|
|
15209
|
-
return { modified: false, reason: "already-wrapped" };
|
|
15210
|
-
}
|
|
15211
|
-
const isESM = configName.endsWith(".ts") || configName.endsWith(".mjs");
|
|
15212
|
-
if (isESM) {
|
|
15213
|
-
const importLine = 'import { withGlasstraceConfig } from "@glasstrace/sdk";\n';
|
|
15214
|
-
const wrapResult2 = wrapExport(existing);
|
|
15215
|
-
if (!wrapResult2.wrapped) {
|
|
15216
|
-
return { modified: false, reason: "no-export" };
|
|
15217
|
-
}
|
|
15218
|
-
const modified2 = importLine + "\n" + wrapResult2.content;
|
|
15219
|
-
fs.writeFileSync(configPath, modified2, "utf-8");
|
|
15220
|
-
return { modified: true };
|
|
15221
|
-
}
|
|
15222
|
-
const requireLine = 'const { withGlasstraceConfig } = require("@glasstrace/sdk");\n';
|
|
15223
|
-
const wrapResult = wrapCJSExport(existing);
|
|
15224
|
-
if (!wrapResult.wrapped) {
|
|
15225
|
-
return { modified: false, reason: "no-export" };
|
|
15226
|
-
}
|
|
15227
|
-
const modified = requireLine + "\n" + wrapResult.content;
|
|
15228
|
-
fs.writeFileSync(configPath, modified, "utf-8");
|
|
15229
|
-
return { modified: true };
|
|
15230
|
-
}
|
|
15231
|
-
function wrapExport(content) {
|
|
15232
|
-
const marker = "export default";
|
|
15233
|
-
const idx = content.lastIndexOf(marker);
|
|
15234
|
-
if (idx === -1) {
|
|
15235
|
-
return { content, wrapped: false };
|
|
15236
|
-
}
|
|
15237
|
-
const preamble = content.slice(0, idx);
|
|
15238
|
-
const exprRaw = content.slice(idx + marker.length);
|
|
15239
|
-
const expr = exprRaw.trim().replace(/;?\s*$/, "");
|
|
15240
|
-
if (expr.length === 0) {
|
|
15241
|
-
return { content, wrapped: false };
|
|
15242
|
-
}
|
|
15243
|
-
return {
|
|
15244
|
-
content: preamble + `export default withGlasstraceConfig(${expr});
|
|
15245
|
-
`,
|
|
15246
|
-
wrapped: true
|
|
15247
|
-
};
|
|
15248
|
-
}
|
|
15249
|
-
function wrapCJSExport(content) {
|
|
15250
|
-
const cjsMarker = "module.exports";
|
|
15251
|
-
const cjsIdx = content.lastIndexOf(cjsMarker);
|
|
15252
|
-
if (cjsIdx === -1) {
|
|
15253
|
-
return { content, wrapped: false };
|
|
15254
|
-
}
|
|
15255
|
-
const preamble = content.slice(0, cjsIdx);
|
|
15256
|
-
const afterMarker = content.slice(cjsIdx + cjsMarker.length);
|
|
15257
|
-
const eqMatch = /^\s*=\s*/.exec(afterMarker);
|
|
15258
|
-
if (!eqMatch) {
|
|
15259
|
-
return { content, wrapped: false };
|
|
15260
|
-
}
|
|
15261
|
-
const exprRaw = afterMarker.slice(eqMatch[0].length);
|
|
15262
|
-
const expr = exprRaw.trim().replace(/;?\s*$/, "");
|
|
15263
|
-
if (expr.length === 0) {
|
|
15264
|
-
return { content, wrapped: false };
|
|
15265
|
-
}
|
|
15266
|
-
return {
|
|
15267
|
-
content: preamble + `module.exports = withGlasstraceConfig(${expr});
|
|
15268
|
-
`,
|
|
15269
|
-
wrapped: true
|
|
15270
|
-
};
|
|
15271
|
-
}
|
|
15272
|
-
async function scaffoldEnvLocal(projectRoot) {
|
|
15273
|
-
const filePath = path.join(projectRoot, ".env.local");
|
|
15274
|
-
if (fs.existsSync(filePath)) {
|
|
15275
|
-
const existing = fs.readFileSync(filePath, "utf-8");
|
|
15276
|
-
if (/^\s*#?\s*GLASSTRACE_API_KEY\s*=/m.test(existing)) {
|
|
15277
|
-
return false;
|
|
15278
|
-
}
|
|
15279
|
-
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
15280
|
-
fs.writeFileSync(filePath, existing + separator + "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
|
|
15281
|
-
return true;
|
|
15282
|
-
}
|
|
15283
|
-
fs.writeFileSync(filePath, "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
|
|
15284
|
-
return true;
|
|
15285
|
-
}
|
|
15286
|
-
async function addCoverageMapEnv(projectRoot) {
|
|
15287
|
-
const filePath = path.join(projectRoot, ".env.local");
|
|
15288
|
-
if (!fs.existsSync(filePath)) {
|
|
15289
|
-
fs.writeFileSync(filePath, "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
|
|
15290
|
-
return true;
|
|
15291
|
-
}
|
|
15292
|
-
const existing = fs.readFileSync(filePath, "utf-8");
|
|
15293
|
-
const keyRegex = /^(\s*GLASSTRACE_COVERAGE_MAP\s*=\s*)(.*)$/m;
|
|
15294
|
-
const keyMatch = keyRegex.exec(existing);
|
|
15295
|
-
if (keyMatch) {
|
|
15296
|
-
const currentValue = keyMatch[2].trim();
|
|
15297
|
-
if (currentValue === "true") {
|
|
15298
|
-
return false;
|
|
15299
|
-
}
|
|
15300
|
-
const updated = existing.replace(keyRegex, `${keyMatch[1]}true`);
|
|
15301
|
-
fs.writeFileSync(filePath, updated, "utf-8");
|
|
15302
|
-
return true;
|
|
15303
|
-
}
|
|
15304
|
-
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
15305
|
-
fs.writeFileSync(filePath, existing + separator + "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
|
|
15306
|
-
return true;
|
|
15307
|
-
}
|
|
15308
|
-
async function scaffoldGitignore(projectRoot) {
|
|
15309
|
-
const filePath = path.join(projectRoot, ".gitignore");
|
|
15310
|
-
if (fs.existsSync(filePath)) {
|
|
15311
|
-
const existing = fs.readFileSync(filePath, "utf-8");
|
|
15312
|
-
const lines = existing.split("\n").map((l) => l.trim());
|
|
15313
|
-
if (lines.includes(".glasstrace/")) {
|
|
15314
|
-
return false;
|
|
15315
|
-
}
|
|
15316
|
-
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
15317
|
-
fs.writeFileSync(filePath, existing + separator + ".glasstrace/\n", "utf-8");
|
|
15318
|
-
return true;
|
|
15319
|
-
}
|
|
15320
|
-
fs.writeFileSync(filePath, ".glasstrace/\n", "utf-8");
|
|
15321
|
-
return true;
|
|
15322
|
-
}
|
|
15323
|
-
var fs, path, INSTRUMENTATION_FILENAMES;
|
|
15324
|
-
var init_scaffolder = __esm({
|
|
15325
|
-
"src/cli/scaffolder.ts"() {
|
|
15326
|
-
"use strict";
|
|
15327
|
-
fs = __toESM(require("node:fs"), 1);
|
|
15328
|
-
path = __toESM(require("node:path"), 1);
|
|
15329
|
-
init_constants();
|
|
15330
|
-
init_mcp_runtime();
|
|
15331
|
-
INSTRUMENTATION_FILENAMES = [
|
|
15332
|
-
"instrumentation.ts",
|
|
15333
|
-
"instrumentation.js",
|
|
15334
|
-
"instrumentation.mjs"
|
|
15335
|
-
];
|
|
15336
|
-
}
|
|
15337
|
-
});
|
|
15338
|
-
|
|
15339
|
-
// src/agent-detection/detect.ts
|
|
15340
|
-
async function pathExists(path10, mode = import_node_fs.constants.R_OK) {
|
|
15341
|
-
try {
|
|
15342
|
-
await (0, import_promises.access)(path10, mode);
|
|
15343
|
-
return true;
|
|
15344
|
-
} catch {
|
|
15345
|
-
return false;
|
|
15346
|
-
}
|
|
15347
|
-
}
|
|
15348
|
-
async function findGitRoot(startDir) {
|
|
15349
|
-
let current = (0, import_node_path.resolve)(startDir);
|
|
15350
|
-
while (true) {
|
|
15351
|
-
if (await pathExists((0, import_node_path.join)(current, ".git"), import_node_fs.constants.F_OK)) {
|
|
15352
|
-
return current;
|
|
15353
|
-
}
|
|
15354
|
-
const parent = (0, import_node_path.dirname)(current);
|
|
15355
|
-
if (parent === current) {
|
|
15356
|
-
break;
|
|
15357
|
-
}
|
|
15358
|
-
current = parent;
|
|
15359
|
-
}
|
|
15360
|
-
return (0, import_node_path.resolve)(startDir);
|
|
15361
|
-
}
|
|
15362
|
-
function isCliAvailable(binary) {
|
|
15363
|
-
return new Promise((resolve2) => {
|
|
15364
|
-
const command = process.platform === "win32" ? "where" : "which";
|
|
15365
|
-
(0, import_node_child_process.execFile)(command, [binary], (error48) => {
|
|
15366
|
-
resolve2(error48 === null);
|
|
15367
|
-
});
|
|
15368
|
-
});
|
|
15369
|
-
}
|
|
15370
|
-
async function detectAgents(projectRoot) {
|
|
15371
|
-
const resolvedRoot = (0, import_node_path.resolve)(projectRoot);
|
|
15372
|
-
let rootStat;
|
|
15373
|
-
try {
|
|
15374
|
-
rootStat = await (0, import_promises.stat)(resolvedRoot);
|
|
15375
|
-
} catch (err) {
|
|
15376
|
-
const code = err.code;
|
|
15377
|
-
throw new Error(
|
|
15378
|
-
`projectRoot does not exist: ${resolvedRoot}` + (code ? ` (${code})` : "")
|
|
15379
|
-
);
|
|
15380
|
-
}
|
|
15381
|
-
if (!rootStat.isDirectory()) {
|
|
15382
|
-
throw new Error(`projectRoot is not a directory: ${resolvedRoot}`);
|
|
14997
|
+
if (!rootStat.isDirectory()) {
|
|
14998
|
+
throw new Error(`projectRoot is not a directory: ${resolvedRoot}`);
|
|
15383
14999
|
}
|
|
15384
15000
|
const gitRoot = await findGitRoot(resolvedRoot);
|
|
15385
15001
|
const searchDirs = [];
|
|
@@ -16995,7 +16611,7 @@ var init_uninit = __esm({
|
|
|
16995
16611
|
os = __toESM(require("node:os"), 1);
|
|
16996
16612
|
path5 = __toESM(require("node:path"), 1);
|
|
16997
16613
|
init_constants();
|
|
16998
|
-
|
|
16614
|
+
init_mcp_runtime();
|
|
16999
16615
|
init_discovery_file();
|
|
17000
16616
|
MCP_CONFIG_FILES = [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json"];
|
|
17001
16617
|
AGENT_INFO_FILES = [
|
|
@@ -17120,7 +16736,8 @@ async function mcpAdd(options) {
|
|
|
17120
16736
|
}
|
|
17121
16737
|
const agents = await detectAgents(projectRoot);
|
|
17122
16738
|
const detectedNonGeneric = agents.filter((a) => a.name !== "generic");
|
|
17123
|
-
const
|
|
16739
|
+
const genericAgent = agents.find((a) => a.name === "generic");
|
|
16740
|
+
const targetAgents = genericAgent ? [...detectedNonGeneric, genericAgent] : detectedNonGeneric;
|
|
17124
16741
|
if (dryRun) {
|
|
17125
16742
|
messages.push("Dry run: would perform the following actions:", "");
|
|
17126
16743
|
for (const agent of targetAgents) {
|
|
@@ -17229,6 +16846,17 @@ async function mcpAdd(options) {
|
|
|
17229
16846
|
" No agents detected. Place agent marker files (e.g., CLAUDE.md, .cursor/) in your project."
|
|
17230
16847
|
);
|
|
17231
16848
|
}
|
|
16849
|
+
const detectedNonGenericResults = results.filter(
|
|
16850
|
+
(r) => detectedNonGeneric.some((a) => a.name === r.agent)
|
|
16851
|
+
);
|
|
16852
|
+
const allDetectedNonGenericFailed = detectedNonGeneric.length > 0 && !detectedNonGenericResults.some((r) => r.success);
|
|
16853
|
+
if (allDetectedNonGenericFailed) {
|
|
16854
|
+
messages.push(
|
|
16855
|
+
"",
|
|
16856
|
+
"All detected agent registrations failed. Check errors above."
|
|
16857
|
+
);
|
|
16858
|
+
return { exitCode: 1, results, messages };
|
|
16859
|
+
}
|
|
17232
16860
|
if (!anySuccess && results.length > 0) {
|
|
17233
16861
|
messages.push(
|
|
17234
16862
|
"",
|
|
@@ -17274,7 +16902,7 @@ function hasRegisterGlasstraceImport(content) {
|
|
|
17274
16902
|
if (!importMatch) return false;
|
|
17275
16903
|
return importMatch[1].split(",").map((s) => s.trim()).includes("registerGlasstrace");
|
|
17276
16904
|
}
|
|
17277
|
-
function runValidate(options) {
|
|
16905
|
+
async function runValidate(options) {
|
|
17278
16906
|
const { projectRoot } = options;
|
|
17279
16907
|
const issues = [];
|
|
17280
16908
|
const glasstraceDir = path7.join(projectRoot, ".glasstrace");
|
|
@@ -17319,6 +16947,22 @@ function runValidate(options) {
|
|
|
17319
16947
|
fix: "Run `npx glasstrace init` to re-register the marker, or `npx glasstrace uninit` to fully remove MCP configuration."
|
|
17320
16948
|
});
|
|
17321
16949
|
}
|
|
16950
|
+
if (markerExists) {
|
|
16951
|
+
try {
|
|
16952
|
+
const [markerState, resolved] = await Promise.all([
|
|
16953
|
+
readMcpMarker(projectRoot),
|
|
16954
|
+
resolveEffectiveMcpCredential(projectRoot)
|
|
16955
|
+
]);
|
|
16956
|
+
if (markerState.status === "valid" && resolved.effective !== null && markerState.credentialHash !== identityFingerprint(resolved.effective.key)) {
|
|
16957
|
+
issues.push({
|
|
16958
|
+
code: "mcp-helper-stale-credential",
|
|
16959
|
+
message: "Managed MCP configs were last refreshed with a different credential than the project is now using. MCP queries may return no traces while the dashboard sees them.",
|
|
16960
|
+
fix: "Run `npx glasstrace mcp add --force` to refresh managed MCP configs with the current credential."
|
|
16961
|
+
});
|
|
16962
|
+
}
|
|
16963
|
+
} catch {
|
|
16964
|
+
}
|
|
16965
|
+
}
|
|
17322
16966
|
const summary = [];
|
|
17323
16967
|
if (issues.length === 0) {
|
|
17324
16968
|
summary.push("Glasstrace install state is consistent.");
|
|
@@ -17354,11 +16998,13 @@ var init_validate = __esm({
|
|
|
17354
16998
|
"use strict";
|
|
17355
16999
|
fs7 = __toESM(require("node:fs"), 1);
|
|
17356
17000
|
path7 = __toESM(require("node:path"), 1);
|
|
17001
|
+
init_mcp_runtime();
|
|
17357
17002
|
MCP_CONFIG_CANDIDATES = [
|
|
17358
17003
|
".mcp.json",
|
|
17359
17004
|
".cursor/mcp.json",
|
|
17360
17005
|
".gemini/settings.json",
|
|
17361
|
-
".codex/config.toml"
|
|
17006
|
+
".codex/config.toml",
|
|
17007
|
+
".glasstrace/mcp.json"
|
|
17362
17008
|
];
|
|
17363
17009
|
}
|
|
17364
17010
|
});
|
|
@@ -17571,7 +17217,384 @@ module.exports = __toCommonJS(init_exports);
|
|
|
17571
17217
|
var fs9 = __toESM(require("node:fs"), 1);
|
|
17572
17218
|
var path9 = __toESM(require("node:path"), 1);
|
|
17573
17219
|
var readline = __toESM(require("node:readline"), 1);
|
|
17574
|
-
|
|
17220
|
+
|
|
17221
|
+
// src/cli/scaffolder.ts
|
|
17222
|
+
var fs = __toESM(require("node:fs"), 1);
|
|
17223
|
+
var path = __toESM(require("node:path"), 1);
|
|
17224
|
+
init_constants();
|
|
17225
|
+
function hasRegisterGlasstraceCall(content) {
|
|
17226
|
+
return content.split("\n").some((line) => {
|
|
17227
|
+
const uncommented = line.replace(/\/\/.*$/, "");
|
|
17228
|
+
return /\bregisterGlasstrace\s*\(/.test(uncommented);
|
|
17229
|
+
});
|
|
17230
|
+
}
|
|
17231
|
+
function injectRegisterGlasstrace(content) {
|
|
17232
|
+
if (hasRegisterGlasstraceCall(content)) {
|
|
17233
|
+
return { injected: false, content };
|
|
17234
|
+
}
|
|
17235
|
+
const registerFnRegex = /export\s+(?:async\s+)?function\s+register\s*\([^)]*\)\s*\{/;
|
|
17236
|
+
const match = registerFnRegex.exec(content);
|
|
17237
|
+
if (!match) {
|
|
17238
|
+
return { injected: false, content };
|
|
17239
|
+
}
|
|
17240
|
+
const afterBrace = content.slice(match.index + match[0].length);
|
|
17241
|
+
const indentMatch = /\n([ \t]+)/.exec(afterBrace);
|
|
17242
|
+
const indent = indentMatch ? indentMatch[1] : " ";
|
|
17243
|
+
const importLine = 'import { registerGlasstrace } from "@glasstrace/sdk";\n';
|
|
17244
|
+
const hasGlasstraceImport2 = content.includes("@glasstrace/sdk");
|
|
17245
|
+
const insertPoint = match.index + match[0].length;
|
|
17246
|
+
const callInjection = `
|
|
17247
|
+
${indent}// Glasstrace must be registered before other instrumentation
|
|
17248
|
+
${indent}registerGlasstrace();
|
|
17249
|
+
`;
|
|
17250
|
+
let modified;
|
|
17251
|
+
if (hasGlasstraceImport2) {
|
|
17252
|
+
const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
|
|
17253
|
+
const importMatch = importRegex.exec(content);
|
|
17254
|
+
if (importMatch) {
|
|
17255
|
+
const specifiers = importMatch[1];
|
|
17256
|
+
const alreadyImported = specifiers.split(",").some((s) => s.trim() === "registerGlasstrace");
|
|
17257
|
+
if (alreadyImported) {
|
|
17258
|
+
modified = content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
|
|
17259
|
+
} else {
|
|
17260
|
+
const existingImports = specifiers.trimEnd();
|
|
17261
|
+
const separator = existingImports.endsWith(",") ? " " : ", ";
|
|
17262
|
+
const updatedImport = `import { ${existingImports.trim()}${separator}registerGlasstrace } from "@glasstrace/sdk"`;
|
|
17263
|
+
modified = content.replace(importMatch[0], updatedImport);
|
|
17264
|
+
const newMatch = registerFnRegex.exec(modified);
|
|
17265
|
+
if (newMatch) {
|
|
17266
|
+
const newInsertPoint = newMatch.index + newMatch[0].length;
|
|
17267
|
+
modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
|
|
17268
|
+
}
|
|
17269
|
+
}
|
|
17270
|
+
} else {
|
|
17271
|
+
modified = importLine + content;
|
|
17272
|
+
const newMatch = registerFnRegex.exec(modified);
|
|
17273
|
+
if (newMatch) {
|
|
17274
|
+
const newInsertPoint = newMatch.index + newMatch[0].length;
|
|
17275
|
+
modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
|
|
17276
|
+
}
|
|
17277
|
+
}
|
|
17278
|
+
} else {
|
|
17279
|
+
modified = importLine + content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
|
|
17280
|
+
}
|
|
17281
|
+
return { injected: true, content: modified };
|
|
17282
|
+
}
|
|
17283
|
+
var INSTRUMENTATION_FILENAMES = [
|
|
17284
|
+
"instrumentation.ts",
|
|
17285
|
+
"instrumentation.js",
|
|
17286
|
+
"instrumentation.mjs"
|
|
17287
|
+
];
|
|
17288
|
+
function resolveInstrumentationTarget(projectRoot) {
|
|
17289
|
+
const rootExisting = [];
|
|
17290
|
+
const srcExisting = [];
|
|
17291
|
+
for (const name of INSTRUMENTATION_FILENAMES) {
|
|
17292
|
+
const rootPath = path.join(projectRoot, name);
|
|
17293
|
+
if (isRegularFile(rootPath)) {
|
|
17294
|
+
rootExisting.push(rootPath);
|
|
17295
|
+
}
|
|
17296
|
+
const srcPath = path.join(projectRoot, "src", name);
|
|
17297
|
+
if (isRegularFile(srcPath)) {
|
|
17298
|
+
srcExisting.push(srcPath);
|
|
17299
|
+
}
|
|
17300
|
+
}
|
|
17301
|
+
const existing = [...rootExisting, ...srcExisting];
|
|
17302
|
+
if (rootExisting.length > 0 && srcExisting.length > 0) {
|
|
17303
|
+
return {
|
|
17304
|
+
target: null,
|
|
17305
|
+
layout: null,
|
|
17306
|
+
existing,
|
|
17307
|
+
rootExisting,
|
|
17308
|
+
srcExisting,
|
|
17309
|
+
conflict: true
|
|
17310
|
+
};
|
|
17311
|
+
}
|
|
17312
|
+
if (srcExisting.length > 0) {
|
|
17313
|
+
return {
|
|
17314
|
+
target: srcExisting[0],
|
|
17315
|
+
layout: "src",
|
|
17316
|
+
existing,
|
|
17317
|
+
rootExisting,
|
|
17318
|
+
srcExisting,
|
|
17319
|
+
conflict: false
|
|
17320
|
+
};
|
|
17321
|
+
}
|
|
17322
|
+
if (rootExisting.length > 0) {
|
|
17323
|
+
return {
|
|
17324
|
+
target: rootExisting[0],
|
|
17325
|
+
layout: "root",
|
|
17326
|
+
existing,
|
|
17327
|
+
rootExisting,
|
|
17328
|
+
srcExisting,
|
|
17329
|
+
conflict: false
|
|
17330
|
+
};
|
|
17331
|
+
}
|
|
17332
|
+
const srcDir = path.join(projectRoot, "src");
|
|
17333
|
+
const layout = isDirectory(srcDir) ? "src" : "root";
|
|
17334
|
+
const target = layout === "src" ? path.join(projectRoot, "src", "instrumentation.ts") : path.join(projectRoot, "instrumentation.ts");
|
|
17335
|
+
return {
|
|
17336
|
+
target,
|
|
17337
|
+
layout,
|
|
17338
|
+
existing,
|
|
17339
|
+
rootExisting,
|
|
17340
|
+
srcExisting,
|
|
17341
|
+
conflict: false
|
|
17342
|
+
};
|
|
17343
|
+
}
|
|
17344
|
+
function isDirectory(p) {
|
|
17345
|
+
try {
|
|
17346
|
+
return fs.statSync(p).isDirectory();
|
|
17347
|
+
} catch {
|
|
17348
|
+
return false;
|
|
17349
|
+
}
|
|
17350
|
+
}
|
|
17351
|
+
function isRegularFile(p) {
|
|
17352
|
+
try {
|
|
17353
|
+
const stat2 = fs.lstatSync(p);
|
|
17354
|
+
if (stat2.isSymbolicLink()) {
|
|
17355
|
+
return fs.statSync(p).isFile();
|
|
17356
|
+
}
|
|
17357
|
+
return stat2.isFile();
|
|
17358
|
+
} catch {
|
|
17359
|
+
return false;
|
|
17360
|
+
}
|
|
17361
|
+
}
|
|
17362
|
+
function appendRegisterFunction(content) {
|
|
17363
|
+
const importLine = 'import { registerGlasstrace } from "@glasstrace/sdk";\n';
|
|
17364
|
+
const functionBlock = "\nexport async function register() {\n // Glasstrace must be registered before Prisma instrumentation\n // to ensure all ORM spans are captured correctly.\n // If you use @prisma/instrumentation, import it after this call.\n registerGlasstrace();\n}\n";
|
|
17365
|
+
let withImport = content;
|
|
17366
|
+
const hasGlasstraceImport2 = content.includes("@glasstrace/sdk");
|
|
17367
|
+
if (!hasGlasstraceImport2) {
|
|
17368
|
+
withImport = importLine + content;
|
|
17369
|
+
} else {
|
|
17370
|
+
const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
|
|
17371
|
+
const importMatch = importRegex.exec(content);
|
|
17372
|
+
if (importMatch) {
|
|
17373
|
+
const specifiers = importMatch[1];
|
|
17374
|
+
const alreadyImported = specifiers.split(",").some((s) => s.trim() === "registerGlasstrace");
|
|
17375
|
+
if (!alreadyImported) {
|
|
17376
|
+
const existingImports = specifiers.trimEnd();
|
|
17377
|
+
const separator = existingImports.endsWith(",") ? " " : ", ";
|
|
17378
|
+
const updatedImport = `import { ${existingImports.trim()}${separator}registerGlasstrace } from "@glasstrace/sdk"`;
|
|
17379
|
+
withImport = content.replace(importMatch[0], updatedImport);
|
|
17380
|
+
}
|
|
17381
|
+
} else {
|
|
17382
|
+
withImport = importLine + content;
|
|
17383
|
+
}
|
|
17384
|
+
}
|
|
17385
|
+
const trailingNewline = withImport.endsWith("\n") ? "" : "\n";
|
|
17386
|
+
return withImport + trailingNewline + functionBlock;
|
|
17387
|
+
}
|
|
17388
|
+
async function defaultInstrumentationPrompt(question, defaultValue) {
|
|
17389
|
+
if (!process.stdin.isTTY) return defaultValue;
|
|
17390
|
+
const readline2 = await import("node:readline");
|
|
17391
|
+
const rl = readline2.createInterface({
|
|
17392
|
+
input: process.stdin,
|
|
17393
|
+
output: process.stdout
|
|
17394
|
+
});
|
|
17395
|
+
return new Promise((resolve2) => {
|
|
17396
|
+
const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
|
|
17397
|
+
rl.question(question + suffix, (answer) => {
|
|
17398
|
+
rl.close();
|
|
17399
|
+
const trimmed = answer.trim().toLowerCase();
|
|
17400
|
+
if (trimmed === "") {
|
|
17401
|
+
resolve2(defaultValue);
|
|
17402
|
+
return;
|
|
17403
|
+
}
|
|
17404
|
+
resolve2(trimmed === "y" || trimmed === "yes");
|
|
17405
|
+
});
|
|
17406
|
+
});
|
|
17407
|
+
}
|
|
17408
|
+
async function scaffoldInstrumentation(projectRoot, options = {}) {
|
|
17409
|
+
const target = resolveInstrumentationTarget(projectRoot);
|
|
17410
|
+
if (target.conflict) {
|
|
17411
|
+
return {
|
|
17412
|
+
action: "conflict",
|
|
17413
|
+
// Point the user at the `src/` variant — modern Next.js apps with a
|
|
17414
|
+
// `src/` directory load from there, so that's the merge target. The
|
|
17415
|
+
// competing path is reported separately for the error message.
|
|
17416
|
+
filePath: target.srcExisting[0],
|
|
17417
|
+
conflictingPath: target.rootExisting[0]
|
|
17418
|
+
};
|
|
17419
|
+
}
|
|
17420
|
+
const filePath = target.target;
|
|
17421
|
+
const layout = target.layout;
|
|
17422
|
+
if (filePath === null || layout === null) {
|
|
17423
|
+
return { action: "unrecognized" };
|
|
17424
|
+
}
|
|
17425
|
+
const force = options.force === true;
|
|
17426
|
+
const prompt = options.prompt ?? defaultInstrumentationPrompt;
|
|
17427
|
+
if (!fs.existsSync(filePath)) {
|
|
17428
|
+
const content = `import { registerGlasstrace } from "@glasstrace/sdk";
|
|
17429
|
+
|
|
17430
|
+
export async function register() {
|
|
17431
|
+
// Glasstrace must be registered before Prisma instrumentation
|
|
17432
|
+
// to ensure all ORM spans are captured correctly.
|
|
17433
|
+
// If you use @prisma/instrumentation, import it after this call.
|
|
17434
|
+
registerGlasstrace();
|
|
17435
|
+
}
|
|
17436
|
+
`;
|
|
17437
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
17438
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
17439
|
+
return { action: "created", filePath, layout };
|
|
17440
|
+
}
|
|
17441
|
+
const existing = fs.readFileSync(filePath, "utf-8");
|
|
17442
|
+
if (hasRegisterGlasstraceCall(existing)) {
|
|
17443
|
+
return { action: "already-registered", filePath, layout };
|
|
17444
|
+
}
|
|
17445
|
+
if (!force) {
|
|
17446
|
+
const approved = await prompt(
|
|
17447
|
+
`Merge registerGlasstrace() into ${path.relative(projectRoot, filePath)}?`,
|
|
17448
|
+
false
|
|
17449
|
+
);
|
|
17450
|
+
if (!approved) {
|
|
17451
|
+
return { action: "skipped", filePath, layout };
|
|
17452
|
+
}
|
|
17453
|
+
}
|
|
17454
|
+
const injectResult = injectRegisterGlasstrace(existing);
|
|
17455
|
+
if (injectResult.injected) {
|
|
17456
|
+
fs.writeFileSync(filePath, injectResult.content, "utf-8");
|
|
17457
|
+
return { action: "injected", filePath, layout };
|
|
17458
|
+
}
|
|
17459
|
+
const appended = appendRegisterFunction(existing);
|
|
17460
|
+
fs.writeFileSync(filePath, appended, "utf-8");
|
|
17461
|
+
return { action: "appended", filePath, layout };
|
|
17462
|
+
}
|
|
17463
|
+
async function scaffoldNextConfig(projectRoot) {
|
|
17464
|
+
let configPath;
|
|
17465
|
+
let configName;
|
|
17466
|
+
for (const name of NEXT_CONFIG_NAMES) {
|
|
17467
|
+
const candidate = path.join(projectRoot, name);
|
|
17468
|
+
if (fs.existsSync(candidate)) {
|
|
17469
|
+
configPath = candidate;
|
|
17470
|
+
configName = name;
|
|
17471
|
+
break;
|
|
17472
|
+
}
|
|
17473
|
+
}
|
|
17474
|
+
if (configPath === void 0 || configName === void 0) {
|
|
17475
|
+
return null;
|
|
17476
|
+
}
|
|
17477
|
+
const existing = fs.readFileSync(configPath, "utf-8");
|
|
17478
|
+
if (existing.trim().length === 0) {
|
|
17479
|
+
return { modified: false, reason: "empty-file" };
|
|
17480
|
+
}
|
|
17481
|
+
if (existing.includes("withGlasstraceConfig")) {
|
|
17482
|
+
return { modified: false, reason: "already-wrapped" };
|
|
17483
|
+
}
|
|
17484
|
+
const isESM = configName.endsWith(".ts") || configName.endsWith(".mjs");
|
|
17485
|
+
if (isESM) {
|
|
17486
|
+
const importLine = 'import { withGlasstraceConfig } from "@glasstrace/sdk";\n';
|
|
17487
|
+
const wrapResult2 = wrapExport(existing);
|
|
17488
|
+
if (!wrapResult2.wrapped) {
|
|
17489
|
+
return { modified: false, reason: "no-export" };
|
|
17490
|
+
}
|
|
17491
|
+
const modified2 = importLine + "\n" + wrapResult2.content;
|
|
17492
|
+
fs.writeFileSync(configPath, modified2, "utf-8");
|
|
17493
|
+
return { modified: true };
|
|
17494
|
+
}
|
|
17495
|
+
const requireLine = 'const { withGlasstraceConfig } = require("@glasstrace/sdk");\n';
|
|
17496
|
+
const wrapResult = wrapCJSExport(existing);
|
|
17497
|
+
if (!wrapResult.wrapped) {
|
|
17498
|
+
return { modified: false, reason: "no-export" };
|
|
17499
|
+
}
|
|
17500
|
+
const modified = requireLine + "\n" + wrapResult.content;
|
|
17501
|
+
fs.writeFileSync(configPath, modified, "utf-8");
|
|
17502
|
+
return { modified: true };
|
|
17503
|
+
}
|
|
17504
|
+
function wrapExport(content) {
|
|
17505
|
+
const marker = "export default";
|
|
17506
|
+
const idx = content.lastIndexOf(marker);
|
|
17507
|
+
if (idx === -1) {
|
|
17508
|
+
return { content, wrapped: false };
|
|
17509
|
+
}
|
|
17510
|
+
const preamble = content.slice(0, idx);
|
|
17511
|
+
const exprRaw = content.slice(idx + marker.length);
|
|
17512
|
+
const expr = exprRaw.trim().replace(/;?\s*$/, "");
|
|
17513
|
+
if (expr.length === 0) {
|
|
17514
|
+
return { content, wrapped: false };
|
|
17515
|
+
}
|
|
17516
|
+
return {
|
|
17517
|
+
content: preamble + `export default withGlasstraceConfig(${expr});
|
|
17518
|
+
`,
|
|
17519
|
+
wrapped: true
|
|
17520
|
+
};
|
|
17521
|
+
}
|
|
17522
|
+
function wrapCJSExport(content) {
|
|
17523
|
+
const cjsMarker = "module.exports";
|
|
17524
|
+
const cjsIdx = content.lastIndexOf(cjsMarker);
|
|
17525
|
+
if (cjsIdx === -1) {
|
|
17526
|
+
return { content, wrapped: false };
|
|
17527
|
+
}
|
|
17528
|
+
const preamble = content.slice(0, cjsIdx);
|
|
17529
|
+
const afterMarker = content.slice(cjsIdx + cjsMarker.length);
|
|
17530
|
+
const eqMatch = /^\s*=\s*/.exec(afterMarker);
|
|
17531
|
+
if (!eqMatch) {
|
|
17532
|
+
return { content, wrapped: false };
|
|
17533
|
+
}
|
|
17534
|
+
const exprRaw = afterMarker.slice(eqMatch[0].length);
|
|
17535
|
+
const expr = exprRaw.trim().replace(/;?\s*$/, "");
|
|
17536
|
+
if (expr.length === 0) {
|
|
17537
|
+
return { content, wrapped: false };
|
|
17538
|
+
}
|
|
17539
|
+
return {
|
|
17540
|
+
content: preamble + `module.exports = withGlasstraceConfig(${expr});
|
|
17541
|
+
`,
|
|
17542
|
+
wrapped: true
|
|
17543
|
+
};
|
|
17544
|
+
}
|
|
17545
|
+
async function scaffoldEnvLocal(projectRoot) {
|
|
17546
|
+
const filePath = path.join(projectRoot, ".env.local");
|
|
17547
|
+
if (fs.existsSync(filePath)) {
|
|
17548
|
+
const existing = fs.readFileSync(filePath, "utf-8");
|
|
17549
|
+
if (/^\s*#?\s*GLASSTRACE_API_KEY\s*=/m.test(existing)) {
|
|
17550
|
+
return false;
|
|
17551
|
+
}
|
|
17552
|
+
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
17553
|
+
fs.writeFileSync(filePath, existing + separator + "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
|
|
17554
|
+
return true;
|
|
17555
|
+
}
|
|
17556
|
+
fs.writeFileSync(filePath, "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
|
|
17557
|
+
return true;
|
|
17558
|
+
}
|
|
17559
|
+
async function addCoverageMapEnv(projectRoot) {
|
|
17560
|
+
const filePath = path.join(projectRoot, ".env.local");
|
|
17561
|
+
if (!fs.existsSync(filePath)) {
|
|
17562
|
+
fs.writeFileSync(filePath, "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
|
|
17563
|
+
return true;
|
|
17564
|
+
}
|
|
17565
|
+
const existing = fs.readFileSync(filePath, "utf-8");
|
|
17566
|
+
const keyRegex = /^(\s*GLASSTRACE_COVERAGE_MAP\s*=\s*)(.*)$/m;
|
|
17567
|
+
const keyMatch = keyRegex.exec(existing);
|
|
17568
|
+
if (keyMatch) {
|
|
17569
|
+
const currentValue = keyMatch[2].trim();
|
|
17570
|
+
if (currentValue === "true") {
|
|
17571
|
+
return false;
|
|
17572
|
+
}
|
|
17573
|
+
const updated = existing.replace(keyRegex, `${keyMatch[1]}true`);
|
|
17574
|
+
fs.writeFileSync(filePath, updated, "utf-8");
|
|
17575
|
+
return true;
|
|
17576
|
+
}
|
|
17577
|
+
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
17578
|
+
fs.writeFileSync(filePath, existing + separator + "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
|
|
17579
|
+
return true;
|
|
17580
|
+
}
|
|
17581
|
+
async function scaffoldGitignore(projectRoot) {
|
|
17582
|
+
const filePath = path.join(projectRoot, ".gitignore");
|
|
17583
|
+
if (fs.existsSync(filePath)) {
|
|
17584
|
+
const existing = fs.readFileSync(filePath, "utf-8");
|
|
17585
|
+
const lines = existing.split("\n").map((l) => l.trim());
|
|
17586
|
+
if (lines.includes(".glasstrace/")) {
|
|
17587
|
+
return false;
|
|
17588
|
+
}
|
|
17589
|
+
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
17590
|
+
fs.writeFileSync(filePath, existing + separator + ".glasstrace/\n", "utf-8");
|
|
17591
|
+
return true;
|
|
17592
|
+
}
|
|
17593
|
+
fs.writeFileSync(filePath, ".glasstrace/\n", "utf-8");
|
|
17594
|
+
return true;
|
|
17595
|
+
}
|
|
17596
|
+
|
|
17597
|
+
// src/cli/init.ts
|
|
17575
17598
|
init_mcp_runtime();
|
|
17576
17599
|
|
|
17577
17600
|
// src/import-graph.ts
|
|
@@ -18664,7 +18687,7 @@ async function verifyAnonKeyRegistration(projectRoot) {
|
|
|
18664
18687
|
}
|
|
18665
18688
|
const baseConfig = resolveConfig({ apiKey: devKey });
|
|
18666
18689
|
const config2 = { ...baseConfig, apiKey: devKey };
|
|
18667
|
-
const sdkVersion = true ? "1.1.
|
|
18690
|
+
const sdkVersion = true ? "1.1.2" : "0.0.0-dev";
|
|
18668
18691
|
const result = await verifyInitReachable(config2, anonKey, sdkVersion);
|
|
18669
18692
|
if (result.ok) {
|
|
18670
18693
|
return { outcome: "verified" };
|