usetraceforge-cli 0.1.4 → 0.1.5

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.
@@ -2,9 +2,10 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import { spinner } from "@clack/prompts";
4
4
  import chalk from "chalk";
5
+ import { Project, SyntaxKind } from "ts-morph";
5
6
  export async function installNextJs(apiKey, endpoint) {
6
7
  const s = spinner();
7
- s.start("Configuring Next.js...");
8
+ s.start("Agent: Scanning and configuring Next.js...");
8
9
  try {
9
10
  // 1. Add API key and endpoint to .env.local
10
11
  const envPath = path.resolve(process.cwd(), ".env.local");
@@ -18,62 +19,10 @@ export async function installNextJs(apiKey, endpoint) {
18
19
  else {
19
20
  fs.writeFileSync(envPath, envVar);
20
21
  }
21
- // 2. Generate TraceForgeProvider.tsx
22
- const componentsDir = path.resolve(process.cwd(), "components");
23
- if (!fs.existsSync(componentsDir)) {
24
- fs.mkdirSync(componentsDir);
25
- }
26
- const providerCode = `"use client";
27
- import { useEffect } from "react";
28
- import TraceForge from "usetraceforge";
29
- import { TraceForgeErrorBoundary } from "usetraceforge/react";
30
-
31
- let initialized = false;
32
-
33
- export function TraceForgeProvider({ children }: { children: React.ReactNode }) {
34
- useEffect(() => {
35
- if (initialized) return;
36
- TraceForge.init({
37
- apiKey: process.env.NEXT_PUBLIC_TRACEFORGE_API_KEY!,
38
- endpoint: process.env.NEXT_PUBLIC_TRACEFORGE_INGEST_URL,
39
- autoCapture: true,
40
- });
41
- initialized = true;
42
- }, []);
43
-
44
- return <TraceForgeErrorBoundary>{children}</TraceForgeErrorBoundary>;
45
- }
46
- `;
47
- fs.writeFileSync(path.join(componentsDir, "TraceForgeProvider.tsx"), providerCode);
48
- // 3. Find layout.tsx
49
- const layoutPaths = [
50
- path.resolve(process.cwd(), "app/layout.tsx"),
51
- path.resolve(process.cwd(), "src/app/layout.tsx"),
52
- ];
53
- let targetLayout = null;
54
- for (const p of layoutPaths) {
55
- if (fs.existsSync(p)) {
56
- targetLayout = p;
57
- break;
58
- }
59
- }
60
- if (!targetLayout) {
61
- s.stop(chalk.yellow("Could not find app/layout.tsx. Please configure TraceForge manually."));
62
- return;
63
- }
64
- // 4. Inject code into layout.tsx
65
- let layoutCode = fs.readFileSync(targetLayout, "utf-8");
66
- if (layoutCode.includes("TraceForgeProvider")) {
67
- s.stop(chalk.green("TraceForge is already configured in layout.tsx!"));
68
- return;
69
- }
70
- const importStatement = `import { TraceForgeProvider } from "@/components/TraceForgeProvider";\n`;
71
- layoutCode = importStatement + layoutCode;
72
- layoutCode = layoutCode.replace(/(<body[^>]*>)/g, `$1\n <TraceForgeProvider>`);
73
- layoutCode = layoutCode.replace(/<\/body>/g, ` </TraceForgeProvider>\n </body>`);
74
- fs.writeFileSync(targetLayout, layoutCode);
75
- // 5. Update next.config.js / next.config.mjs
22
+ const project = new Project();
23
+ // 2. Next.js Config AST Injection
76
24
  const configPaths = [
25
+ path.resolve(process.cwd(), "next.config.ts"),
77
26
  path.resolve(process.cwd(), "next.config.mjs"),
78
27
  path.resolve(process.cwd(), "next.config.js"),
79
28
  ];
@@ -85,25 +34,78 @@ export function TraceForgeProvider({ children }: { children: React.ReactNode })
85
34
  }
86
35
  }
87
36
  if (targetConfig) {
88
- let configCode = fs.readFileSync(targetConfig, "utf-8");
89
- if (!configCode.includes("withTraceForgeConfig")) {
90
- const isMjs = targetConfig.endsWith(".mjs") || configCode.includes("export default");
91
- // Add import
92
- if (isMjs) {
93
- configCode = `import { withTraceForgeConfig } from "usetraceforge/next-plugin";\n` + configCode;
94
- configCode = configCode.replace(/export default (.+);/g, `export default withTraceForgeConfig($1);`);
37
+ const sourceFile = project.addSourceFileAtPath(targetConfig);
38
+ const text = sourceFile.getFullText();
39
+ if (!text.includes("withTraceForgeConfig")) {
40
+ const isEsm = targetConfig.endsWith(".ts") || targetConfig.endsWith(".mjs") || text.includes("export default");
41
+ if (isEsm) {
42
+ sourceFile.addImportDeclaration({
43
+ namedImports: ["withTraceForgeConfig"],
44
+ moduleSpecifier: "usetraceforge/next-plugin"
45
+ });
46
+ const defaultExport = sourceFile.getExportAssignment(d => !d.isExportEquals());
47
+ if (defaultExport) {
48
+ const expression = defaultExport.getExpression();
49
+ defaultExport.setExpression(`withTraceForgeConfig(${expression.getText()})`);
50
+ }
51
+ else {
52
+ console.log(chalk.yellow("\nAgent: Could not parse export default in next.config. Please wrap your config manually."));
53
+ }
95
54
  }
96
55
  else {
56
+ // Fallback to simpler regex for CJS files if AST binary expression is too complex
57
+ let configCode = fs.readFileSync(targetConfig, "utf-8");
97
58
  configCode = `const { withTraceForgeConfig } = require("usetraceforge/next-plugin");\n` + configCode;
98
59
  configCode = configCode.replace(/module\.exports = (.+);/g, `module.exports = withTraceForgeConfig($1);`);
60
+ fs.writeFileSync(targetConfig, configCode);
99
61
  }
100
- fs.writeFileSync(targetConfig, configCode);
62
+ sourceFile.saveSync();
101
63
  }
102
64
  }
103
- s.stop(chalk.green("Next.js configuration complete!"));
65
+ else {
66
+ console.log(chalk.yellow("\nAgent: Could not locate next.config.ts/mjs. Please configure Webpack loader manually."));
67
+ }
68
+ // 3. Layout AST Injection
69
+ const layoutPaths = [
70
+ path.resolve(process.cwd(), "app/layout.tsx"),
71
+ path.resolve(process.cwd(), "src/app/layout.tsx"),
72
+ ];
73
+ let targetLayout = null;
74
+ for (const p of layoutPaths) {
75
+ if (fs.existsSync(p)) {
76
+ targetLayout = p;
77
+ break;
78
+ }
79
+ }
80
+ if (targetLayout) {
81
+ const sourceFile = project.addSourceFileAtPath(targetLayout);
82
+ if (!sourceFile.getFullText().includes("TraceForgeProvider")) {
83
+ sourceFile.addImportDeclaration({
84
+ namedImports: ["TraceForgeProvider"],
85
+ moduleSpecifier: "usetraceforge/react"
86
+ });
87
+ // Find body tag
88
+ const jsxElements = sourceFile.getDescendantsOfKind(SyntaxKind.JsxElement);
89
+ let bodyTag = jsxElements.find(el => el.getOpeningElement().getTagNameNode().getText() === "body");
90
+ if (bodyTag) {
91
+ const childrenText = bodyTag.getJsxChildren().map(c => c.getText()).join("");
92
+ const opening = bodyTag.getOpeningElement().getText();
93
+ const closing = bodyTag.getClosingElement().getText();
94
+ bodyTag.replaceWithText(`${opening}\n <TraceForgeProvider>\n ${childrenText}\n </TraceForgeProvider>\n ${closing}`);
95
+ sourceFile.saveSync();
96
+ }
97
+ else {
98
+ console.log(chalk.yellow("\nAgent: Could not find <body> tag in layout.tsx. Please add <TraceForgeProvider> manually."));
99
+ }
100
+ }
101
+ }
102
+ else {
103
+ console.log(chalk.yellow("\nAgent: Could not find layout.tsx. Please add <TraceForgeProvider> manually."));
104
+ }
105
+ s.stop(chalk.green("Agent: Next.js configuration complete!"));
104
106
  }
105
107
  catch (error) {
106
- s.stop(chalk.red("Failed to configure Next.js automatically."));
108
+ s.stop(chalk.red("Agent: An error occurred while parsing files. Please configure manually."));
107
109
  console.error(error);
108
110
  }
109
111
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usetraceforge-cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "TraceForge CLI Wizard for 2-click installations",
5
5
  "bin": {
6
6
  "traceforge": "./dist/index.js"
@@ -18,11 +18,12 @@
18
18
  "dependencies": {
19
19
  "@clack/prompts": "^0.7.0",
20
20
  "chalk": "^5.3.0",
21
- "execa": "^9.3.0"
21
+ "execa": "^9.3.0",
22
+ "ts-morph": "^28.0.0"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@types/node": "^20.0.0",
25
- "typescript": "^5.5.4",
26
- "tsx": "^4.16.2"
26
+ "tsx": "^4.16.2",
27
+ "typescript": "^5.5.4"
27
28
  }
28
29
  }