botversion-sdk 1.0.0 → 1.0.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/bin/init.js +201 -62
- package/cli/detector.js +473 -71
- package/cli/generator.js +298 -137
- package/cli/writer.js +270 -6
- package/client.js +4 -0
- package/index.js +12 -3
- package/interceptor.js +2 -2
- package/package.json +1 -1
- package/scanner.js +21 -22
package/bin/init.js
CHANGED
|
@@ -29,6 +29,9 @@ function log(msg) {
|
|
|
29
29
|
function info(msg) {
|
|
30
30
|
console.log(`${c.cyan} ℹ${c.reset} ${msg}`);
|
|
31
31
|
}
|
|
32
|
+
function info2(msg) {
|
|
33
|
+
console.log(`${c.cyan} ℹ${c.reset} ${msg}`);
|
|
34
|
+
}
|
|
32
35
|
function success(msg) {
|
|
33
36
|
console.log(`${c.green} ✔${c.reset} ${msg}`);
|
|
34
37
|
}
|
|
@@ -42,6 +45,22 @@ function step(msg) {
|
|
|
42
45
|
console.log(`\n${c.bold}${c.white} → ${msg}${c.reset}`);
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
// ─── FETCH PROJECT INFO ───────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
async function fetchProjectInfo(apiKey, platformUrl) {
|
|
51
|
+
const url = `${platformUrl}/api/sdk/project-info?workspaceKey=${encodeURIComponent(apiKey)}`;
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch(url);
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new Error("Invalid API key or project not found");
|
|
56
|
+
}
|
|
57
|
+
return await response.json();
|
|
58
|
+
// returns { projectId, publicKey, apiUrl, cdnUrl }
|
|
59
|
+
} catch (err) {
|
|
60
|
+
throw new Error(`Could not fetch project info: ${err.message}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
45
64
|
// ─── PARSE ARGS ───────────────────────────────────────────────────────────────
|
|
46
65
|
|
|
47
66
|
function parseArgs(argv) {
|
|
@@ -90,26 +109,33 @@ async function main() {
|
|
|
90
109
|
const cwd = args.cwd;
|
|
91
110
|
const changes = { modified: [], created: [], backups: [], manual: [] };
|
|
92
111
|
|
|
112
|
+
// ── Fetch project info from platform ──────────────────────────────────────
|
|
113
|
+
step("Fetching project info from platform...");
|
|
114
|
+
let projectInfo;
|
|
115
|
+
try {
|
|
116
|
+
projectInfo = await fetchProjectInfo(args.key, "http://localhost:3000");
|
|
117
|
+
success(`Project found — ID: ${projectInfo.projectId}`);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
error(err.message);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
93
123
|
// ── Detect environment ────────────────────────────────────────────────────
|
|
94
124
|
step("Scanning your project...");
|
|
95
125
|
|
|
96
126
|
// Handle monorepo
|
|
97
|
-
let workingDir = cwd;
|
|
98
127
|
const monorepoInfo = detector.detectMonorepo(cwd);
|
|
99
128
|
if (monorepoInfo.isMonorepo) {
|
|
100
|
-
warn(
|
|
101
|
-
|
|
102
|
-
monorepoInfo.packages,
|
|
103
|
-
cwd,
|
|
129
|
+
warn(
|
|
130
|
+
"Monorepo detected — will scan all packages for frontend and backend.",
|
|
104
131
|
);
|
|
105
|
-
info(`Using package: ${path.relative(cwd, workingDir) || "root"}`);
|
|
106
132
|
}
|
|
107
133
|
|
|
108
|
-
//
|
|
109
|
-
const
|
|
134
|
+
// Always detect from root so frontend/backend split works correctly
|
|
135
|
+
const detected = detector.detect(cwd);
|
|
110
136
|
|
|
111
137
|
// ── Check if already initialized ─────────────────────────────────────────
|
|
112
|
-
if (
|
|
138
|
+
if (detected.alreadyInitialized && !args.force) {
|
|
113
139
|
warn("BotVersion SDK is already initialized in this project.");
|
|
114
140
|
log(`\n To reinitialize, run with --force flag:\n`);
|
|
115
141
|
log(` npx botversion-sdk init --key ${args.key} --force\n`);
|
|
@@ -119,69 +145,112 @@ async function main() {
|
|
|
119
145
|
// ── Framework check ───────────────────────────────────────────────────────
|
|
120
146
|
step("Detecting framework...");
|
|
121
147
|
|
|
122
|
-
if (!
|
|
148
|
+
if (!detected.framework.name) {
|
|
123
149
|
error("Could not detect a supported framework.");
|
|
124
150
|
log(`\n Supported: Express.js, Next.js`);
|
|
125
151
|
log(` Make sure you have them listed in package.json\n`);
|
|
126
152
|
process.exit(1);
|
|
127
153
|
}
|
|
128
154
|
|
|
129
|
-
if (!
|
|
155
|
+
if (!detected.framework.supported) {
|
|
130
156
|
warn(
|
|
131
|
-
`Detected: ${
|
|
157
|
+
`Detected: ${detected.framework.name} (not yet supported for auto-setup)`,
|
|
132
158
|
);
|
|
133
159
|
log("");
|
|
134
|
-
log(
|
|
160
|
+
log(
|
|
161
|
+
generator.generateManualInstructions(detected.framework.name, args.key),
|
|
162
|
+
);
|
|
135
163
|
process.exit(0);
|
|
136
164
|
}
|
|
137
165
|
|
|
138
|
-
success(`Framework: ${
|
|
166
|
+
success(`Framework: ${detected.framework.name}`);
|
|
139
167
|
info(
|
|
140
|
-
`Module system: ${
|
|
168
|
+
`Module system: ${detected.moduleSystem === "esm" ? "ES Modules" : "CommonJS"}`,
|
|
141
169
|
);
|
|
142
|
-
info(`Language: ${
|
|
170
|
+
info(`Language: ${detected.isTypeScript ? "TypeScript" : "JavaScript"}`);
|
|
143
171
|
|
|
144
172
|
// ── Auth detection ────────────────────────────────────────────────────────
|
|
145
173
|
step("Detecting auth library...");
|
|
146
174
|
|
|
147
|
-
let auth =
|
|
175
|
+
let auth = detected.auth;
|
|
148
176
|
|
|
149
177
|
if (!auth.name) {
|
|
150
178
|
warn("No auth library detected automatically.");
|
|
151
179
|
auth = await prompts.promptAuthLibrary();
|
|
152
|
-
|
|
180
|
+
detected.auth = auth;
|
|
153
181
|
} else if (!auth.supported) {
|
|
154
182
|
warn(`Detected auth: ${auth.name} (not yet supported for auto-setup)`);
|
|
155
183
|
warn("Will set up without user context — you can add it manually later.");
|
|
156
184
|
const proceed = await prompts.confirm("Continue without auth?", true);
|
|
157
185
|
if (!proceed) process.exit(0);
|
|
158
186
|
auth = { name: auth.name, supported: false };
|
|
159
|
-
|
|
187
|
+
detected.auth = auth;
|
|
160
188
|
} else {
|
|
161
189
|
const versionLabel = auth.version ? ` (${auth.version})` : "";
|
|
162
190
|
success(`Auth: ${auth.name}${versionLabel}`);
|
|
163
191
|
}
|
|
164
192
|
|
|
165
193
|
// ── Package manager ───────────────────────────────────────────────────────
|
|
166
|
-
info(`Package manager: ${
|
|
194
|
+
info(`Package manager: ${detected.packageManager}`);
|
|
167
195
|
|
|
168
196
|
// ─────────────────────────────────────────────────────────────────────────
|
|
169
197
|
// FRAMEWORK: EXPRESS
|
|
170
198
|
// ─────────────────────────────────────────────────────────────────────────
|
|
171
|
-
if (
|
|
172
|
-
await setupExpress(
|
|
199
|
+
if (detected.framework.name === "express") {
|
|
200
|
+
await setupExpress(detected, args, changes, projectInfo);
|
|
201
|
+
|
|
202
|
+
// ── Inject script tag into frontend (Express only) ──────────────────
|
|
203
|
+
// Next.js handles its own script tag injection inside setupNextJs
|
|
204
|
+
if (detected.frontendMainFile && projectInfo) {
|
|
205
|
+
const scriptTag = generator.generateScriptTag(projectInfo);
|
|
206
|
+
const result = writer.injectScriptTag(
|
|
207
|
+
detected.frontendMainFile.file,
|
|
208
|
+
detected.frontendMainFile.type,
|
|
209
|
+
scriptTag,
|
|
210
|
+
args.force,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
if (result.success) {
|
|
214
|
+
success(
|
|
215
|
+
`Injected script tag into ${path.relative(detected.cwd, detected.frontendMainFile.file)}`,
|
|
216
|
+
);
|
|
217
|
+
changes.modified.push(
|
|
218
|
+
path.relative(detected.cwd, detected.frontendMainFile.file),
|
|
219
|
+
);
|
|
220
|
+
if (result.backup) changes.backups.push(result.backup);
|
|
221
|
+
} else if (result.reason === "already_exists") {
|
|
222
|
+
warn(
|
|
223
|
+
"BotVersion script tag already exists in frontend file — skipping.",
|
|
224
|
+
);
|
|
225
|
+
} else {
|
|
226
|
+
warn(
|
|
227
|
+
"Could not auto-inject script tag. Add this manually to your frontend HTML:",
|
|
228
|
+
);
|
|
229
|
+
console.log("\n" + scriptTag + "\n");
|
|
230
|
+
changes.manual.push(
|
|
231
|
+
`Add to your frontend HTML before </body>:\n\n${scriptTag}`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
} else if (!detected.frontendMainFile) {
|
|
235
|
+
warn("Could not find frontend main file automatically.");
|
|
236
|
+
const scriptTag = generator.generateScriptTag(projectInfo);
|
|
237
|
+
changes.manual.push(
|
|
238
|
+
`Add to your frontend HTML before </body>:\n\n${scriptTag}`,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
173
241
|
}
|
|
174
242
|
|
|
175
243
|
// ─────────────────────────────────────────────────────────────────────────
|
|
176
244
|
// FRAMEWORK: NEXT.JS
|
|
177
245
|
// ─────────────────────────────────────────────────────────────────────────
|
|
178
|
-
else if (
|
|
179
|
-
await setupNextJs(
|
|
246
|
+
else if (detected.framework.name === "next") {
|
|
247
|
+
await setupNextJs(detected, args, changes, projectInfo);
|
|
180
248
|
}
|
|
181
249
|
|
|
182
250
|
// ── Write API key to .env / .env.local ────────────────────────────────────
|
|
183
|
-
const envFileName =
|
|
184
|
-
|
|
251
|
+
const envFileName =
|
|
252
|
+
detected.framework.name === "next" ? ".env.local" : ".env";
|
|
253
|
+
const envPath = path.join(detected.cwd, envFileName);
|
|
185
254
|
const envLine = `BOTVERSION_API_KEY=${args.key}`;
|
|
186
255
|
const envContent = fs.existsSync(envPath)
|
|
187
256
|
? fs.readFileSync(envPath, "utf8")
|
|
@@ -216,16 +285,16 @@ async function main() {
|
|
|
216
285
|
|
|
217
286
|
// ─── EXPRESS SETUP ────────────────────────────────────────────────────────────
|
|
218
287
|
|
|
219
|
-
async function setupExpress(
|
|
288
|
+
async function setupExpress(detected, args, changes, projectInfo) {
|
|
220
289
|
step("Setting up Express...");
|
|
221
290
|
|
|
222
291
|
// Find entry point
|
|
223
|
-
let entryPoint =
|
|
292
|
+
let entryPoint = detected.entryPoint;
|
|
224
293
|
|
|
225
294
|
if (!entryPoint || !fs.existsSync(entryPoint)) {
|
|
226
295
|
warn("Could not find your server entry point automatically.");
|
|
227
296
|
const manualPath = await prompts.promptEntryPoint();
|
|
228
|
-
entryPoint = path.resolve(
|
|
297
|
+
entryPoint = path.resolve(detected.cwd, manualPath);
|
|
229
298
|
|
|
230
299
|
if (!fs.existsSync(entryPoint)) {
|
|
231
300
|
error(`File not found: ${entryPoint}`);
|
|
@@ -233,42 +302,73 @@ async function setupExpress(info, args, changes) {
|
|
|
233
302
|
}
|
|
234
303
|
}
|
|
235
304
|
|
|
236
|
-
success(`Entry point: ${path.relative(
|
|
305
|
+
success(`Entry point: ${path.relative(detected.cwd, entryPoint)}`);
|
|
237
306
|
|
|
238
307
|
// Generate the init code
|
|
239
|
-
const generated = generator.generateExpressInit(
|
|
308
|
+
const generated = generator.generateExpressInit(detected, args.key);
|
|
309
|
+
|
|
310
|
+
// PATTERN 2: Separate app file with module.exports = app
|
|
311
|
+
if (detected.appFile) {
|
|
312
|
+
info(`Found app file: ${path.relative(detected.cwd, detected.appFile)}`);
|
|
313
|
+
const generated2 = generator.generateExpressInit(detected, args.key);
|
|
314
|
+
const result = writer.injectBeforeExport(
|
|
315
|
+
detected.appFile,
|
|
316
|
+
generated2.initBlock,
|
|
317
|
+
detected.appVarName,
|
|
318
|
+
);
|
|
240
319
|
|
|
241
|
-
|
|
242
|
-
|
|
320
|
+
if (result.success) {
|
|
321
|
+
success(
|
|
322
|
+
`Injected BotVersion.init() into ${path.relative(detected.cwd, detected.appFile)}`,
|
|
323
|
+
);
|
|
324
|
+
changes.modified.push(path.relative(detected.cwd, detected.appFile));
|
|
325
|
+
} else if (result.reason === "already_exists") {
|
|
326
|
+
warn("BotVersion already found — skipping injection.");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
243
329
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
330
|
+
// PATTERN 1 & 3 & 4: app.listen() or server.listen() in entry file
|
|
331
|
+
else if (
|
|
332
|
+
detected.listenCall ||
|
|
333
|
+
detected.listenInsideCallback ||
|
|
334
|
+
detected.createServer
|
|
335
|
+
) {
|
|
336
|
+
const result = writer.injectBeforeListen(
|
|
337
|
+
entryPoint,
|
|
338
|
+
generated.initBlock,
|
|
339
|
+
detected.appVarName,
|
|
340
|
+
);
|
|
247
341
|
|
|
248
342
|
if (result.success) {
|
|
249
343
|
success(`Injected BotVersion.init() before app.listen()`);
|
|
250
|
-
changes.modified.push(path.relative(
|
|
344
|
+
changes.modified.push(path.relative(detected.cwd, entryPoint));
|
|
251
345
|
if (result.backup) changes.backups.push(result.backup);
|
|
252
346
|
} else if (result.reason === "already_exists") {
|
|
253
347
|
warn("BotVersion already found in entry point — skipping injection.");
|
|
254
348
|
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// LAST RESORT: ask the user
|
|
352
|
+
else {
|
|
353
|
+
warn("Could not find the right place to inject automatically.");
|
|
258
354
|
const response = await prompts.promptMissingListenCall(
|
|
259
|
-
path.relative(
|
|
355
|
+
path.relative(detected.cwd, entryPoint),
|
|
260
356
|
);
|
|
261
357
|
|
|
262
358
|
if (response.action === "append") {
|
|
263
359
|
const result = writer.appendToFile(entryPoint, generated.initBlock);
|
|
264
360
|
if (result.success) {
|
|
265
361
|
success("Appended BotVersion setup to end of file.");
|
|
266
|
-
changes.modified.push(path.relative(
|
|
362
|
+
changes.modified.push(path.relative(detected.cwd, entryPoint));
|
|
267
363
|
}
|
|
268
364
|
} else if (response.action === "manual_path") {
|
|
269
|
-
const altPath = path.resolve(
|
|
365
|
+
const altPath = path.resolve(detected.cwd, response.filePath);
|
|
270
366
|
if (fs.existsSync(altPath)) {
|
|
271
|
-
const result = writer.injectBeforeListen(
|
|
367
|
+
const result = writer.injectBeforeListen(
|
|
368
|
+
altPath,
|
|
369
|
+
generated.initBlock,
|
|
370
|
+
detected.appVarName,
|
|
371
|
+
);
|
|
272
372
|
if (result.success) {
|
|
273
373
|
success(`Injected into ${response.filePath}`);
|
|
274
374
|
changes.modified.push(response.filePath);
|
|
@@ -280,7 +380,6 @@ async function setupExpress(info, args, changes) {
|
|
|
280
380
|
);
|
|
281
381
|
}
|
|
282
382
|
} else {
|
|
283
|
-
// skip — print manual instructions
|
|
284
383
|
changes.manual.push(
|
|
285
384
|
`Add this to your server file before app.listen():\n\n${generated.initBlock}`,
|
|
286
385
|
);
|
|
@@ -291,10 +390,10 @@ async function setupExpress(info, args, changes) {
|
|
|
291
390
|
|
|
292
391
|
// ─── NEXT.JS SETUP ────────────────────────────────────────────────────────────
|
|
293
392
|
|
|
294
|
-
async function setupNextJs(
|
|
393
|
+
async function setupNextJs(detected, args, changes, projectInfo) {
|
|
295
394
|
step("Setting up Next.js...");
|
|
296
395
|
|
|
297
|
-
const nextInfo =
|
|
396
|
+
const nextInfo = detected.next;
|
|
298
397
|
const baseDir = nextInfo.baseDir;
|
|
299
398
|
|
|
300
399
|
info2(
|
|
@@ -302,20 +401,23 @@ async function setupNextJs(info, args, changes) {
|
|
|
302
401
|
);
|
|
303
402
|
|
|
304
403
|
// ── next-auth config location ─────────────────────────────────────────────
|
|
305
|
-
if (
|
|
404
|
+
if (detected.auth.name === "next-auth" && !detected.nextAuthConfig) {
|
|
306
405
|
warn("Could not find authOptions location automatically.");
|
|
307
406
|
const configPath = await prompts.promptNextAuthConfigPath();
|
|
308
|
-
|
|
309
|
-
path: path.resolve(
|
|
407
|
+
detected.nextAuthConfig = {
|
|
408
|
+
path: path.resolve(detected.cwd, configPath),
|
|
310
409
|
relativePath: configPath,
|
|
311
410
|
};
|
|
312
411
|
}
|
|
313
412
|
|
|
314
413
|
// ── 1. Create instrumentation.js ──────────────────────────────────────────
|
|
315
|
-
const instrExt =
|
|
316
|
-
const instrFile = path.join(
|
|
414
|
+
const instrExt = detected.generateTs ? ".ts" : ".js";
|
|
415
|
+
const instrFile = path.join(detected.cwd, `instrumentation${instrExt}`);
|
|
317
416
|
|
|
318
|
-
const instrContent = generator.generateInstrumentationFile(
|
|
417
|
+
const instrContent = generator.generateInstrumentationFile(
|
|
418
|
+
detected,
|
|
419
|
+
args.key,
|
|
420
|
+
);
|
|
319
421
|
const instrResult = writer.createFile(instrFile, instrContent, args.force);
|
|
320
422
|
|
|
321
423
|
if (instrResult.success) {
|
|
@@ -333,15 +435,18 @@ async function setupNextJs(info, args, changes) {
|
|
|
333
435
|
}
|
|
334
436
|
|
|
335
437
|
// ── 2. Patch next.config.js ───────────────────────────────────────────────
|
|
336
|
-
const configPatch = generator.generateNextConfigPatch(
|
|
438
|
+
const configPatch = generator.generateNextConfigPatch(
|
|
439
|
+
detected.cwd,
|
|
440
|
+
detected.nextVersion,
|
|
441
|
+
);
|
|
337
442
|
|
|
338
443
|
if (configPatch) {
|
|
339
444
|
if (configPatch.alreadyPatched) {
|
|
340
445
|
info("next.config.js already has instrumentationHook — skipping.");
|
|
341
446
|
} else {
|
|
342
447
|
fs.writeFileSync(configPatch.path, configPatch.content, "utf8");
|
|
343
|
-
success(`Updated ${path.relative(
|
|
344
|
-
changes.modified.push(path.relative(
|
|
448
|
+
success(`Updated ${path.relative(detected.cwd, configPatch.path)}`);
|
|
449
|
+
changes.modified.push(path.relative(detected.cwd, configPatch.path));
|
|
345
450
|
}
|
|
346
451
|
} else {
|
|
347
452
|
warn("Could not find next.config.js — please add this manually:");
|
|
@@ -356,7 +461,7 @@ async function setupNextJs(info, args, changes) {
|
|
|
356
461
|
const chatDir = path.join(pagesBase, "api", "botversion");
|
|
357
462
|
const chatFile = path.join(chatDir, `chat${instrExt}`);
|
|
358
463
|
|
|
359
|
-
const chatContent = generator.generateNextPagesChatRoute(
|
|
464
|
+
const chatContent = generator.generateNextPagesChatRoute(detected);
|
|
360
465
|
const chatResult = writer.createFile(chatFile, chatContent, args.force);
|
|
361
466
|
|
|
362
467
|
const relPath = `${nextInfo.srcDir ? "src/" : ""}pages/api/botversion/chat${instrExt}`;
|
|
@@ -382,7 +487,7 @@ async function setupNextJs(info, args, changes) {
|
|
|
382
487
|
const chatDir = path.join(appBase, "api", "botversion", "chat");
|
|
383
488
|
const chatFile = path.join(chatDir, `route${instrExt}`);
|
|
384
489
|
|
|
385
|
-
const chatContent = generator.generateNextAppChatRoute(
|
|
490
|
+
const chatContent = generator.generateNextAppChatRoute(detected);
|
|
386
491
|
const relPath = `${nextInfo.srcDir ? "src/" : ""}app/api/botversion/chat/route${instrExt}`;
|
|
387
492
|
|
|
388
493
|
const chatResult = writer.createFile(chatFile, chatContent, args.force);
|
|
@@ -401,11 +506,45 @@ async function setupNextJs(info, args, changes) {
|
|
|
401
506
|
}
|
|
402
507
|
}
|
|
403
508
|
}
|
|
404
|
-
}
|
|
405
509
|
|
|
406
|
-
//
|
|
407
|
-
|
|
408
|
-
|
|
510
|
+
// ── 5. Inject script tag into frontend ───────────────────────────────────
|
|
511
|
+
// For Next.js the frontend IS the same project
|
|
512
|
+
// frontendMainFile will be _app.js or layout.js
|
|
513
|
+
if (detected.frontendMainFile && projectInfo) {
|
|
514
|
+
const scriptTag = generator.generateScriptTag(projectInfo);
|
|
515
|
+
const result = writer.injectScriptTag(
|
|
516
|
+
detected.frontendMainFile.file,
|
|
517
|
+
detected.frontendMainFile.type,
|
|
518
|
+
scriptTag,
|
|
519
|
+
args.force,
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
if (result.success) {
|
|
523
|
+
success(
|
|
524
|
+
`Injected script tag into ${path.relative(detected.cwd, detected.frontendMainFile.file)}`,
|
|
525
|
+
);
|
|
526
|
+
changes.modified.push(
|
|
527
|
+
path.relative(detected.cwd, detected.frontendMainFile.file),
|
|
528
|
+
);
|
|
529
|
+
if (result.backup) changes.backups.push(result.backup);
|
|
530
|
+
} else if (result.reason === "already_exists") {
|
|
531
|
+
warn("BotVersion script tag already exists — skipping.");
|
|
532
|
+
} else {
|
|
533
|
+
warn(
|
|
534
|
+
"Could not auto-inject script tag. Add this manually to your frontend file:",
|
|
535
|
+
);
|
|
536
|
+
console.log("\n" + scriptTag + "\n");
|
|
537
|
+
changes.manual.push(
|
|
538
|
+
`Add to your frontend HTML before </body>:\n\n${scriptTag}`,
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
} else if (!detected.frontendMainFile) {
|
|
542
|
+
warn("Could not find frontend file automatically.");
|
|
543
|
+
const scriptTag = generator.generateScriptTag(projectInfo);
|
|
544
|
+
changes.manual.push(
|
|
545
|
+
`Add to your frontend HTML before </body>:\n\n${scriptTag}`,
|
|
546
|
+
);
|
|
547
|
+
}
|
|
409
548
|
}
|
|
410
549
|
|
|
411
550
|
// ─── RUN ──────────────────────────────────────────────────────────────────────
|