usetraceforge-cli 0.1.9 → 0.1.12
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 +25 -17
- package/dist/installers/express.js +114 -0
- package/dist/installers/node.js +46 -0
- package/dist/installers/react.js +50 -0
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -3,56 +3,64 @@ import { intro, outro, text, select, spinner } from "@clack/prompts";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { execa } from "execa";
|
|
5
5
|
import { installNextJs } from "./installers/nextjs.js";
|
|
6
|
-
import {
|
|
6
|
+
import { installExpress } from "./installers/express.js";
|
|
7
|
+
import { installReact } from "./installers/react.js";
|
|
8
|
+
import { installNode } from "./installers/node.js";
|
|
7
9
|
async function main() {
|
|
8
10
|
console.log();
|
|
9
|
-
intro(chalk.bgBlue.white
|
|
11
|
+
intro(chalk.bgBlue(chalk.white(" Welcome to TraceForge ")));
|
|
10
12
|
const apiKey = await text({
|
|
11
13
|
message: "What is your TraceForge API Key?",
|
|
12
14
|
placeholder: "tf_...",
|
|
13
15
|
validate(value) {
|
|
14
16
|
if (value.length === 0)
|
|
15
|
-
return "API Key is required";
|
|
17
|
+
return "API Key is required!";
|
|
16
18
|
},
|
|
17
19
|
});
|
|
18
20
|
if (typeof apiKey !== "string") {
|
|
19
21
|
outro(chalk.red("Installation cancelled."));
|
|
20
22
|
process.exit(1);
|
|
21
23
|
}
|
|
24
|
+
const endpoint = await text({
|
|
25
|
+
message: "What is your TraceForge Ingest Endpoint URL?",
|
|
26
|
+
placeholder: "http://localhost:80/ingest",
|
|
27
|
+
defaultValue: "http://localhost:80/ingest",
|
|
28
|
+
});
|
|
22
29
|
const framework = await select({
|
|
23
30
|
message: "Which framework are you using?",
|
|
24
31
|
options: [
|
|
25
32
|
{ value: "nextjs", label: "Next.js (Prebuilt)" },
|
|
26
|
-
{ value: "express", label: "Express.js (
|
|
27
|
-
{ value: "
|
|
33
|
+
{ value: "express", label: "Express.js (Auto Setup)" },
|
|
34
|
+
{ value: "react", label: "React (Manual Setup)" },
|
|
35
|
+
{ value: "node", label: "Raw Node.js (Manual Setup)" },
|
|
28
36
|
],
|
|
29
37
|
});
|
|
30
38
|
if (typeof framework !== "string") {
|
|
31
39
|
outro(chalk.red("Installation cancelled."));
|
|
32
40
|
process.exit(1);
|
|
33
41
|
}
|
|
34
|
-
const endpoint = await text({
|
|
35
|
-
message: "What is your TraceForge Ingest Endpoint URL?",
|
|
36
|
-
placeholder: "http://localhost:80/ingest",
|
|
37
|
-
defaultValue: "http://localhost:80/ingest",
|
|
38
|
-
});
|
|
39
42
|
const s = spinner();
|
|
40
|
-
s.start("Installing usetraceforge
|
|
43
|
+
s.start("Installing usetraceforge...");
|
|
41
44
|
try {
|
|
42
|
-
await execa("npm", ["install", "usetraceforge
|
|
43
|
-
s.stop(chalk.green("
|
|
45
|
+
await execa("npm", ["install", "usetraceforge"]);
|
|
46
|
+
s.stop(chalk.green("Package usetraceforge installed successfully!"));
|
|
44
47
|
}
|
|
45
48
|
catch (error) {
|
|
46
|
-
s.stop(chalk.red("Failed to install
|
|
49
|
+
s.stop(chalk.red("Failed to install usetraceforge."));
|
|
50
|
+
console.error(error);
|
|
51
|
+
process.exit(1);
|
|
47
52
|
}
|
|
48
53
|
if (framework === "nextjs") {
|
|
49
54
|
await installNextJs(apiKey, endpoint);
|
|
50
55
|
}
|
|
51
56
|
else if (framework === "express") {
|
|
52
|
-
|
|
57
|
+
await installExpress(apiKey, endpoint);
|
|
58
|
+
}
|
|
59
|
+
else if (framework === "react") {
|
|
60
|
+
await installReact(apiKey, endpoint);
|
|
53
61
|
}
|
|
54
|
-
else if (framework === "
|
|
55
|
-
await
|
|
62
|
+
else if (framework === "node") {
|
|
63
|
+
await installNode(apiKey, endpoint);
|
|
56
64
|
}
|
|
57
65
|
outro(chalk.green("✨ You're all set! TraceForge is now protecting your application."));
|
|
58
66
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { spinner } from "@clack/prompts";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { Project, SyntaxKind } from "ts-morph";
|
|
6
|
+
function printManualInstructions() {
|
|
7
|
+
console.log(chalk.cyan(`\n=================================================`));
|
|
8
|
+
console.log(chalk.bold.white(`🚀 Express.js Manual Setup Instructions`));
|
|
9
|
+
console.log(chalk.cyan(`=================================================\n`));
|
|
10
|
+
console.log(`Add this at the ${chalk.bold('TOP')} of your main file (e.g. app.js or server.js):`);
|
|
11
|
+
console.log(chalk.yellow(`
|
|
12
|
+
import TraceForge from "usetraceforge";
|
|
13
|
+
import { expressErrorHandler } from "usetraceforge/express";
|
|
14
|
+
|
|
15
|
+
TraceForge.init({
|
|
16
|
+
apiKey: process.env.TRACEFORGE_API_KEY,
|
|
17
|
+
endpoint: process.env.TRACEFORGE_INGEST_URL
|
|
18
|
+
});
|
|
19
|
+
`));
|
|
20
|
+
console.log(`Then, add the middleware at the ${chalk.bold('VERY END')} of your routes:`);
|
|
21
|
+
console.log(chalk.yellow(`
|
|
22
|
+
// ... all your other app.use() and app.get() routes ...
|
|
23
|
+
|
|
24
|
+
app.use(expressErrorHandler());
|
|
25
|
+
|
|
26
|
+
// ... your custom error handlers (if any) ...
|
|
27
|
+
app.listen(3000);
|
|
28
|
+
`));
|
|
29
|
+
}
|
|
30
|
+
export async function installExpress(apiKey, endpoint) {
|
|
31
|
+
const s = spinner();
|
|
32
|
+
s.start("Agent: Configuring Express.js environment...");
|
|
33
|
+
try {
|
|
34
|
+
const envPath = path.resolve(process.cwd(), ".env");
|
|
35
|
+
const envVar = `\nTRACEFORGE_API_KEY="${apiKey}"\nTRACEFORGE_INGEST_URL="${endpoint}"\n`;
|
|
36
|
+
if (fs.existsSync(envPath)) {
|
|
37
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
38
|
+
if (!content.includes("TRACEFORGE_API_KEY")) {
|
|
39
|
+
fs.appendFileSync(envPath, envVar);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
fs.writeFileSync(envPath, envVar);
|
|
44
|
+
}
|
|
45
|
+
s.stop(chalk.green("Agent: .env configured successfully!"));
|
|
46
|
+
const project = new Project();
|
|
47
|
+
const possiblePaths = [
|
|
48
|
+
"app.ts", "app.js",
|
|
49
|
+
"server.ts", "server.js",
|
|
50
|
+
"index.ts", "index.js",
|
|
51
|
+
"src/app.ts", "src/app.js",
|
|
52
|
+
"src/server.ts", "src/server.js",
|
|
53
|
+
"src/index.ts", "src/index.js",
|
|
54
|
+
"bin/www", "bin/www.js"
|
|
55
|
+
];
|
|
56
|
+
let targetFile = null;
|
|
57
|
+
for (const p of possiblePaths) {
|
|
58
|
+
const fullPath = path.resolve(process.cwd(), p);
|
|
59
|
+
if (fs.existsSync(fullPath)) {
|
|
60
|
+
targetFile = fullPath;
|
|
61
|
+
break; // First match wins
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!targetFile) {
|
|
65
|
+
console.log(chalk.yellow("\nAgent: Could not find your Express entrypoint file automatically."));
|
|
66
|
+
printManualInstructions();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const sourceFile = project.addSourceFileAtPath(targetFile);
|
|
70
|
+
let astSuccess = false;
|
|
71
|
+
const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
72
|
+
let listenCall = null;
|
|
73
|
+
let appVariableName = null;
|
|
74
|
+
for (const callExpr of callExpressions) {
|
|
75
|
+
const expression = callExpr.getExpression();
|
|
76
|
+
if (expression.getKind() === SyntaxKind.PropertyAccessExpression) {
|
|
77
|
+
const propAccess = expression.asKindOrThrow(SyntaxKind.PropertyAccessExpression);
|
|
78
|
+
if (propAccess.getName() === "listen") {
|
|
79
|
+
listenCall = callExpr;
|
|
80
|
+
appVariableName = propAccess.getExpression().getText();
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (listenCall && appVariableName) {
|
|
86
|
+
const listenStatement = listenCall.getFirstAncestorByKind(SyntaxKind.ExpressionStatement);
|
|
87
|
+
if (listenStatement) {
|
|
88
|
+
if (!sourceFile.getFullText().includes("TraceForge.init")) {
|
|
89
|
+
// Inject imports and init at the top
|
|
90
|
+
sourceFile.insertStatements(0, `import TraceForge from "usetraceforge";\nimport { expressErrorHandler } from "usetraceforge/express";\n\nTraceForge.init({\n apiKey: process.env.TRACEFORGE_API_KEY as string,\n endpoint: process.env.TRACEFORGE_INGEST_URL\n});\n`);
|
|
91
|
+
// Inject middleware right before app.listen()
|
|
92
|
+
const listenIndex = listenStatement.getChildIndex();
|
|
93
|
+
sourceFile.insertStatements(listenIndex, `\n// TraceForge must be the last middleware before listen\n${appVariableName}.use(expressErrorHandler());\n`);
|
|
94
|
+
sourceFile.saveSync();
|
|
95
|
+
console.log(chalk.green(`\nAgent: Auto-injected TraceForge into ${targetFile.replace(process.cwd(), "")}!`));
|
|
96
|
+
astSuccess = true;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.log(chalk.blue(`\nAgent: TraceForge is already configured in ${targetFile.replace(process.cwd(), "")}`));
|
|
100
|
+
astSuccess = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!astSuccess) {
|
|
105
|
+
console.log(chalk.yellow(`\nAgent: Found ${targetFile.replace(process.cwd(), "")} but could not auto-inject safely.`));
|
|
106
|
+
printManualInstructions();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
s.stop(chalk.red("Agent: An error occurred during Express configuration."));
|
|
111
|
+
console.error(error);
|
|
112
|
+
printManualInstructions();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { spinner } from "@clack/prompts";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
export async function installNode(apiKey, endpoint) {
|
|
6
|
+
const s = spinner();
|
|
7
|
+
s.start("Agent: Configuring Node.js environment...");
|
|
8
|
+
try {
|
|
9
|
+
const envPath = path.resolve(process.cwd(), ".env");
|
|
10
|
+
const envVar = `\nTRACEFORGE_API_KEY="${apiKey}"\nTRACEFORGE_INGEST_URL="${endpoint}"\n`;
|
|
11
|
+
if (fs.existsSync(envPath)) {
|
|
12
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
13
|
+
if (!content.includes("TRACEFORGE_API_KEY")) {
|
|
14
|
+
fs.appendFileSync(envPath, envVar);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
fs.writeFileSync(envPath, envVar);
|
|
19
|
+
}
|
|
20
|
+
s.stop(chalk.green("Agent: .env configured successfully!"));
|
|
21
|
+
console.log(chalk.cyan(`\n=================================================`));
|
|
22
|
+
console.log(chalk.bold.white(`🚀 Node.js Manual Setup Instructions`));
|
|
23
|
+
console.log(chalk.cyan(`=================================================\n`));
|
|
24
|
+
console.log(`Add this to your entrypoint file:`);
|
|
25
|
+
console.log(chalk.yellow(`
|
|
26
|
+
import TraceForge from "usetraceforge";
|
|
27
|
+
|
|
28
|
+
TraceForge.init({
|
|
29
|
+
apiKey: process.env.TRACEFORGE_API_KEY,
|
|
30
|
+
endpoint: process.env.TRACEFORGE_INGEST_URL
|
|
31
|
+
});
|
|
32
|
+
`));
|
|
33
|
+
console.log(`Then, manually capture errors like this:`);
|
|
34
|
+
console.log(chalk.yellow(`
|
|
35
|
+
try {
|
|
36
|
+
// your code
|
|
37
|
+
} catch (error) {
|
|
38
|
+
TraceForge.captureException(error);
|
|
39
|
+
}
|
|
40
|
+
`));
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
s.stop(chalk.red("Agent: An error occurred."));
|
|
44
|
+
console.error(error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { spinner } from "@clack/prompts";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
export async function installReact(apiKey, endpoint) {
|
|
6
|
+
const s = spinner();
|
|
7
|
+
s.start("Agent: Configuring React environment...");
|
|
8
|
+
try {
|
|
9
|
+
// For React, usually Vite uses VITE_ prefix, CRA uses REACT_APP_
|
|
10
|
+
// Let's create a generic .env but also print the keys
|
|
11
|
+
const envPath = path.resolve(process.cwd(), ".env");
|
|
12
|
+
const envVar = `\nVITE_TRACEFORGE_API_KEY="${apiKey}"\nVITE_TRACEFORGE_INGEST_URL="${endpoint}"\nREACT_APP_TRACEFORGE_API_KEY="${apiKey}"\nREACT_APP_TRACEFORGE_INGEST_URL="${endpoint}"\n`;
|
|
13
|
+
if (fs.existsSync(envPath)) {
|
|
14
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
15
|
+
if (!content.includes("TRACEFORGE_API_KEY")) {
|
|
16
|
+
fs.appendFileSync(envPath, envVar);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
fs.writeFileSync(envPath, envVar);
|
|
21
|
+
}
|
|
22
|
+
s.stop(chalk.green("Agent: .env configured successfully!"));
|
|
23
|
+
console.log(chalk.cyan(`\n=================================================`));
|
|
24
|
+
console.log(chalk.bold.white(`🚀 React Manual Setup Instructions`));
|
|
25
|
+
console.log(chalk.cyan(`=================================================\n`));
|
|
26
|
+
console.log(`Open your main file (e.g. App.tsx or main.tsx) and wrap your app:`);
|
|
27
|
+
console.log(chalk.yellow(`
|
|
28
|
+
import TraceForge from "usetraceforge";
|
|
29
|
+
import { TraceForgeProvider } from "usetraceforge/react";
|
|
30
|
+
|
|
31
|
+
// For Vite use import.meta.env, for CRA use process.env
|
|
32
|
+
TraceForge.init({
|
|
33
|
+
apiKey: import.meta.env.VITE_TRACEFORGE_API_KEY,
|
|
34
|
+
endpoint: import.meta.env.VITE_TRACEFORGE_INGEST_URL
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export default function App() {
|
|
38
|
+
return (
|
|
39
|
+
<TraceForgeProvider>
|
|
40
|
+
<YourComponents />
|
|
41
|
+
</TraceForgeProvider>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
`));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
s.stop(chalk.red("Agent: An error occurred."));
|
|
48
|
+
console.error(error);
|
|
49
|
+
}
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usetraceforge-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "TraceForge CLI Wizard for 2-click installations",
|
|
5
5
|
"bin": {
|
|
6
6
|
"traceforge": "./dist/index.js"
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@clack/prompts": "^0.7.0",
|
|
20
|
-
"@google/generative-ai": "^0.24.1",
|
|
21
20
|
"chalk": "^5.3.0",
|
|
22
21
|
"execa": "^9.3.0",
|
|
23
22
|
"ts-morph": "^28.0.0"
|