@stackframe/init-stack 2.8.45 → 2.8.46

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 CHANGED
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import * as child_process from "child_process";
4
+ import * as child_process2 from "child_process";
5
5
  import { Command } from "commander";
6
6
  import * as crypto from "crypto";
7
- import * as fs from "fs";
7
+ import * as fs3 from "fs";
8
8
  import inquirer from "inquirer";
9
9
  import open from "open";
10
- import * as os from "os";
11
- import * as path from "path";
10
+ import * as os2 from "os";
11
+ import * as path3 from "path";
12
12
  import { PostHog } from "posthog-node";
13
13
 
14
14
  // package.json
15
15
  var package_default = {
16
16
  name: "@stackframe/init-stack",
17
- version: "2.8.45",
17
+ version: "2.8.46",
18
18
  description: "The setup wizard for Stack. https://stack-auth.com",
19
19
  main: "dist/index.js",
20
20
  type: "module",
@@ -32,16 +32,16 @@ var package_default = {
32
32
  "ensure-neon": `grep -q '"@neondatabase/serverless"' ./test-run-output/package.json && echo 'Initialized Neon successfully!'`,
33
33
  "test-run-neon": "pnpm run test-run-node --neon && pnpm run ensure-neon",
34
34
  "test-run-neon:manual": "pnpm run test-run-node:manual --neon && pnpm run ensure-neon",
35
- "test-run-no-browser": "rimraf test-run-output && mkdir test-run-output && cd test-run-output && npm init --init-author-name example-author --init-license UNLICENSED --init-author-url http://example.com --init-module test-run-output --init-version 1.0.0 -y && cd .. && pnpm run init-stack:local test-run-output --on-question error --js --server --npm --no-browser",
35
+ "test-run-no-browser": "rimraf test-run-output && mkdir test-run-output && cd test-run-output && npm init --init-author-name example-author --init-license UNLICENSED --init-author-url http://example.com --init-module test-run-output --init-version 1.0.0 -y && cd .. && pnpm run init-stack:local test-run-output --on-question error --no-warn-uncommitted-changes --js --server --npm --no-browser",
36
36
  "test-run-node:manual": "rimraf test-run-output && mkdir test-run-output && cd test-run-output && npm init && cd .. && pnpm run init-stack:local test-run-output",
37
- "test-run-node": "rimraf test-run-output && mkdir test-run-output && cd test-run-output && npm init --init-author-name example-author --init-license UNLICENSED --init-author-url http://example.com --init-module test-run-output --init-version 1.0.0 -y && cd .. && pnpm run init-stack:local test-run-output --on-question error --js --server --npm --no-browser",
37
+ "test-run-node": "rimraf test-run-output && mkdir test-run-output && cd test-run-output && npm init --init-author-name example-author --init-license UNLICENSED --init-author-url http://example.com --init-module test-run-output --init-version 1.0.0 -y && cd .. && pnpm run init-stack:local test-run-output --on-question error --no-warn-uncommitted-changes --js --server --npm --no-browser",
38
38
  "test-run-js:manual": "rimraf test-run-output && npx -y sv create test-run-output --no-install && pnpm run init-stack:local test-run-output",
39
- "test-run-js": "rimraf test-run-output && npx -y sv create test-run-output --template minimal --types ts --no-add-ons --no-install && pnpm run init-stack:local test-run-output --on-question error --js --client --npm --no-browser",
39
+ "test-run-js": "rimraf test-run-output && npx -y sv create test-run-output --template minimal --types ts --no-add-ons --no-install && pnpm run init-stack:local test-run-output --on-question error --no-warn-uncommitted-changes --js --client --npm --no-browser",
40
40
  "test-run-next:manual": "rimraf test-run-output && npx -y create-next-app@latest test-run-output && pnpm run init-stack:local test-run-output",
41
- "test-run-next": "rimraf test-run-output && npx -y create-next-app@latest test-run-output --app --ts --no-src-dir --tailwind --use-npm --eslint --import-alias '##@#/*' --turbopack && pnpm run init-stack:local test-run-output --on-question error --no-browser",
42
- "test-run-keys-next": "rimraf test-run-output && npx -y create-next-app@latest test-run-output --app --ts --no-src-dir --tailwind --use-npm --eslint --import-alias '##@#/*' --turbopack && pnpm run init-stack:local test-run-output --on-question error --project-id my-project-id --publishable-client-key my-publishable-client-key",
43
- "test-run-keys-js": "rimraf test-run-output && npx -y sv create test-run-output --template minimal --types ts --no-add-ons --no-install && pnpm run init-stack:local test-run-output --on-question error --js --client --npm --project-id my-project-id --publishable-client-key my-publishable-client-key",
44
- "test-run-react": "rimraf test-run-output && npx -y create-vite@latest test-run-output --template react-ts && pnpm run init-stack:local test-run-output --on-question error --no-browser --npm",
41
+ "test-run-next": "rimraf test-run-output && npx -y create-next-app@latest test-run-output --app --ts --no-src-dir --tailwind --use-npm --eslint --import-alias '##@#/*' --turbopack && pnpm run init-stack:local test-run-output --on-question error --no-warn-uncommitted-changes --no-browser",
42
+ "test-run-keys-next": "rimraf test-run-output && npx -y create-next-app@latest test-run-output --app --ts --no-src-dir --tailwind --use-npm --eslint --import-alias '##@#/*' --turbopack && pnpm run init-stack:local test-run-output --on-question error --no-warn-uncommitted-changes --project-id my-project-id --publishable-client-key my-publishable-client-key",
43
+ "test-run-keys-js": "rimraf test-run-output && npx -y sv create test-run-output --template minimal --types ts --no-add-ons --no-install && pnpm run init-stack:local test-run-output --on-question error --no-warn-uncommitted-changes --js --client --npm --project-id my-project-id --publishable-client-key my-publishable-client-key",
44
+ "test-run-react": "rimraf test-run-output && npx -y create-vite@latest test-run-output --template react-ts && pnpm run init-stack:local test-run-output --on-question error --no-warn-uncommitted-changes --no-browser --npm",
45
45
  "test-run-react:manual": "rimraf test-run-output && npx -y create-vite@latest test-run-output --template react-ts && pnpm run init-stack:local test-run-output --react"
46
46
  },
47
47
  files: [
@@ -70,6 +70,367 @@ var package_default = {
70
70
  }
71
71
  };
72
72
 
73
+ // src/mcp.ts
74
+ import * as fs2 from "fs";
75
+ import * as os from "os";
76
+ import * as path2 from "path";
77
+
78
+ // src/util.ts
79
+ import * as child_process from "child_process";
80
+ import * as fs from "fs";
81
+ import * as path from "path";
82
+ var verboseLevelState = 0;
83
+ var verboseFormatterState = null;
84
+ function configureVerboseLogging(options2) {
85
+ verboseLevelState = Math.max(0, options2.level ?? 0);
86
+ verboseFormatterState = options2.formatter ?? null;
87
+ }
88
+ function logVerbose(message, details) {
89
+ if (verboseLevelState <= 0) return;
90
+ const formattedMessage = verboseFormatterState ? verboseFormatterState(message) : `[verbose] ${message}`;
91
+ console.log(formattedMessage);
92
+ if (typeof details !== "undefined" && verboseLevelState >= 2) {
93
+ console.dir(details, { depth: null });
94
+ }
95
+ }
96
+ function templateIdentity(strings, ...values) {
97
+ if (strings.length === 0) return "";
98
+ if (values.length !== strings.length - 1) {
99
+ throw new Error("Invalid number of values; must be one less than strings");
100
+ }
101
+ return strings.slice(1).reduce(
102
+ (result, string, i) => `${result}${String(values[i])}${string}`,
103
+ strings[0]
104
+ );
105
+ }
106
+ function getCommandPath(command) {
107
+ if (!command) return null;
108
+ const checker = process.platform === "win32" ? "where" : "which";
109
+ const commands = [
110
+ [process.env.SHELL ?? "bash", ["-ic", `${checker} ${command}`]],
111
+ [checker, [command]]
112
+ ];
113
+ for (const spawnArgs of commands) {
114
+ const result = child_process.spawnSync(spawnArgs[0], spawnArgs[1], { stdio: "pipe" });
115
+ if (result.status === 0) {
116
+ return result.stdout.toString().trim().split("\n")[0];
117
+ }
118
+ }
119
+ return null;
120
+ }
121
+ function shouldWriteConfigFile(fullPath, options2 = {}) {
122
+ if (!fullPath) return false;
123
+ if (fs.existsSync(fullPath)) return true;
124
+ const dir = path.dirname(fullPath);
125
+ if (!options2.allowCreate) {
126
+ return fs.existsSync(dir);
127
+ }
128
+ return true;
129
+ }
130
+
131
+ // src/mcp.ts
132
+ var MCP_SERVER_NAME = "stack-auth";
133
+ var MCP_SERVER_URL = "https://mcp.stack-auth.com/";
134
+ var MCP_ACCEPT_HEADER = "application/json, text/event-stream";
135
+ var VS_CODE_PAYLOAD = JSON.stringify({ type: "http", name: MCP_SERVER_NAME, url: MCP_SERVER_URL });
136
+ var CLAUDE_BASE_ARGS = ["mcp", "add", "--transport", "http", MCP_SERVER_NAME, MCP_SERVER_URL];
137
+ function scheduleMcpConfiguration(ctx) {
138
+ const workspaceRoot = path2.resolve(ctx.projectPath);
139
+ const homeDir = os.homedir();
140
+ logMcpVerbose("scheduleMcpConfiguration invoked", { workspaceRoot, homeDir });
141
+ scheduleCursorConfigs(ctx, homeDir, workspaceRoot);
142
+ scheduleVsCodeConfigs(ctx, homeDir, workspaceRoot);
143
+ scheduleClaudeConfigs(ctx, workspaceRoot);
144
+ scheduleWindsurfConfigs(ctx, homeDir);
145
+ scheduleGeminiConfig(ctx, homeDir);
146
+ }
147
+ function scheduleCursorConfigs(ctx, homeDir, workspaceRoot) {
148
+ const updater = createServerUpdater("mcpServers", { url: MCP_SERVER_URL });
149
+ logMcpVerbose("Scheduling Cursor MCP configs", { homeDir, workspaceRoot });
150
+ const targets = [
151
+ { path: path2.join(homeDir, ".cursor", "mcp.json"), allowCreate: false, scope: "home" },
152
+ { path: path2.join(workspaceRoot, ".cursor", "mcp.json"), allowCreate: true, scope: "workspace" }
153
+ ];
154
+ for (const target of targets) {
155
+ logInvestigationStatus("Cursor", target.path, target.scope);
156
+ }
157
+ scheduleJsonTargets(
158
+ ctx,
159
+ targets.map(({ path: targetPath, allowCreate }) => ({ path: targetPath, allowCreate })),
160
+ updater
161
+ );
162
+ }
163
+ function scheduleVsCodeConfigs(ctx, homeDir, workspaceRoot) {
164
+ const updater = createServerUpdater("servers", { type: "http", url: MCP_SERVER_URL });
165
+ const paths = getVsCodeUserConfigPaths(homeDir);
166
+ logMcpVerbose("Scheduling VS Code MCP configs", { homeDir, paths });
167
+ paths.stable.forEach((configPath) => logInvestigationStatus("VS Code (stable)", configPath));
168
+ paths.insiders.forEach((configPath) => logInvestigationStatus("VS Code (insiders)", configPath));
169
+ const workspaceTarget = path2.join(workspaceRoot, ".vscode", "mcp.json");
170
+ logInvestigationStatus("VS Code (workspace)", workspaceTarget);
171
+ const stableChanged = scheduleJsonTargets(
172
+ ctx,
173
+ paths.stable.map((configPath) => ({ path: configPath })),
174
+ updater
175
+ );
176
+ const insidersChanged = scheduleJsonTargets(
177
+ ctx,
178
+ paths.insiders.map((configPath) => ({ path: configPath })),
179
+ updater
180
+ );
181
+ const workspaceChanged = scheduleJsonTargets(
182
+ ctx,
183
+ [{ path: path2.join(workspaceRoot, ".vscode", "mcp.json"), allowCreate: true }],
184
+ updater
185
+ );
186
+ scheduleCliIfAvailable(ctx, "code", ["--add-mcp", VS_CODE_PAYLOAD], true);
187
+ scheduleCliIfAvailable(ctx, "code-insiders", ["--add-mcp", VS_CODE_PAYLOAD], true);
188
+ }
189
+ function findClaudeExecutable() {
190
+ const homeDir = os.homedir();
191
+ const localBinPath = path2.join(homeDir, ".local", "bin", "claude");
192
+ const claudeLocalPath = path2.join(homeDir, ".claude", "local", "claude");
193
+ if (fs2.existsSync(localBinPath)) {
194
+ return localBinPath;
195
+ }
196
+ if (fs2.existsSync(claudeLocalPath)) {
197
+ return claudeLocalPath;
198
+ }
199
+ return "claude";
200
+ }
201
+ function scheduleClaudeConfigs(ctx, workspaceRoot) {
202
+ const updater = createServerUpdater("mcpServers", { type: "http", url: MCP_SERVER_URL });
203
+ logMcpVerbose("Scheduling Claude MCP configs", { workspaceRoot });
204
+ const targetPath = path2.join(workspaceRoot, ".mcp.json");
205
+ logInvestigationStatus("Claude (project)", targetPath);
206
+ const projectConfigChanged = scheduleJsonTargets(
207
+ ctx,
208
+ [{ path: targetPath, allowCreate: true }],
209
+ updater
210
+ );
211
+ const claudeExecutable = findClaudeExecutable();
212
+ scheduleCliIfAvailable(ctx, claudeExecutable, [...CLAUDE_BASE_ARGS, "--scope", "user"], projectConfigChanged);
213
+ scheduleCliIfAvailable(
214
+ ctx,
215
+ claudeExecutable,
216
+ [...CLAUDE_BASE_ARGS, "--scope", "project"],
217
+ projectConfigChanged,
218
+ { cwd: workspaceRoot }
219
+ );
220
+ }
221
+ function scheduleWindsurfConfigs(ctx, homeDir) {
222
+ const updater = createServerUpdater("mcpServers", { serverUrl: MCP_SERVER_URL });
223
+ logMcpVerbose("Scheduling Windsurf MCP configs", { homeDir });
224
+ const paths = getWindsurfConfigPaths(homeDir);
225
+ paths.forEach((configPath) => logInvestigationStatus("Windsurf", configPath));
226
+ scheduleJsonTargets(ctx, paths.map((configPath) => ({ path: configPath })), updater);
227
+ }
228
+ function scheduleGeminiConfig(ctx, homeDir) {
229
+ const updater = createServerUpdater("mcpServers", {
230
+ httpUrl: MCP_SERVER_URL,
231
+ headers: { Accept: MCP_ACCEPT_HEADER }
232
+ });
233
+ logMcpVerbose("Scheduling Gemini MCP configs", { homeDir });
234
+ const targetPath = path2.join(homeDir, ".gemini", "settings.json");
235
+ logInvestigationStatus("Gemini", targetPath);
236
+ scheduleJsonTargets(
237
+ ctx,
238
+ [{ path: targetPath }],
239
+ updater
240
+ );
241
+ }
242
+ function scheduleJsonFileUpdate(ctx, fullPath, update, options2 = {}) {
243
+ logMcpVerbose("scheduleJsonFileUpdate invoked", { fullPath, allowCreate: options2.allowCreate });
244
+ if (!fullPath) {
245
+ logMcpVerbose("scheduleJsonFileUpdate skipped: no path provided");
246
+ return false;
247
+ }
248
+ const allowCreate = options2.allowCreate ?? false;
249
+ if (!shouldWriteConfigFile(fullPath, { allowCreate })) {
250
+ logMcpVerbose("scheduleJsonFileUpdate skipped: shouldWriteConfigFile returned false", { fullPath, allowCreate });
251
+ return false;
252
+ }
253
+ const absolutePath = path2.resolve(fullPath);
254
+ const info = readJsonOrEmpty(absolutePath);
255
+ logMcpVerbose("scheduleJsonFileUpdate current file info", { absolutePath, existed: info.existed, parseError: info.parseError?.message });
256
+ const draft = cloneJsonRecord(info.data);
257
+ const updated = update(draft);
258
+ if (!updated) {
259
+ logMcpVerbose("scheduleJsonFileUpdate skipped: updater returned nullish", { absolutePath });
260
+ return false;
261
+ }
262
+ const nextContent = JSON.stringify(updated, null, 2) + "\n";
263
+ const currentContent = info.existed && !info.parseError ? JSON.stringify(info.data, null, 2) + "\n" : null;
264
+ if (!info.parseError && currentContent === nextContent) {
265
+ logMcpVerbose("scheduleJsonFileUpdate skipped: content unchanged", { absolutePath });
266
+ return false;
267
+ }
268
+ ctx.registerWriteHandler(async () => {
269
+ await writeJsonFile(ctx, absolutePath, update, { allowCreate });
270
+ });
271
+ logMcpVerbose("scheduleJsonFileUpdate scheduled write", { absolutePath });
272
+ return true;
273
+ }
274
+ async function writeJsonFile(ctx, absolutePath, update, options2) {
275
+ logMcpVerbose("writeJsonFile invoked", { absolutePath, allowCreate: options2.allowCreate });
276
+ const allowCreate = options2.allowCreate ?? false;
277
+ const info = readJsonOrEmpty(absolutePath);
278
+ if (!info.existed && !allowCreate) {
279
+ logMcpVerbose("writeJsonFile skipped: file missing and allowCreate false", { absolutePath });
280
+ return;
281
+ }
282
+ let current = info.data;
283
+ if (info.parseError) {
284
+ console.warn(
285
+ ctx.colorize.yellow`Warning: Unable to parse MCP config at ${absolutePath}. It will be replaced with a fresh configuration.`
286
+ );
287
+ current = {};
288
+ logMcpVerbose("writeJsonFile parse error encountered; falling back to empty object", { absolutePath, error: info.parseError.message });
289
+ }
290
+ const draft = cloneJsonRecord(current);
291
+ const updated = update(draft);
292
+ if (!updated) {
293
+ logMcpVerbose("writeJsonFile skipped: updater returned nullish", { absolutePath });
294
+ return;
295
+ }
296
+ const nextContent = JSON.stringify(updated, null, 2) + "\n";
297
+ const currentContent = info.existed && !info.parseError ? JSON.stringify(current, null, 2) + "\n" : null;
298
+ if (currentContent === nextContent && !info.parseError) {
299
+ logMcpVerbose("writeJsonFile skipped: current content matches desired content", { absolutePath });
300
+ return;
301
+ }
302
+ if (ctx.isDryRun) {
303
+ console.log(`[DRY-RUN] Would write ${absolutePath}`);
304
+ logMcpVerbose("writeJsonFile dry-run; write skipped", { absolutePath });
305
+ return;
306
+ }
307
+ fs2.mkdirSync(path2.dirname(absolutePath), { recursive: true });
308
+ if (info.existed) {
309
+ try {
310
+ fs2.copyFileSync(absolutePath, `${absolutePath}.bak`);
311
+ } catch {
312
+ }
313
+ }
314
+ fs2.writeFileSync(absolutePath, nextContent, "utf-8");
315
+ await ctx.recordFileChange(absolutePath, info.existed);
316
+ logMcpVerbose("writeJsonFile completed write", { absolutePath, existed: info.existed });
317
+ }
318
+ function scheduleJsonTargets(ctx, targets, update) {
319
+ let changed = false;
320
+ logMcpVerbose("scheduleJsonTargets invoked", { targetCount: targets.length });
321
+ for (const target of targets) {
322
+ if (!target.path) continue;
323
+ if (scheduleJsonFileUpdate(ctx, target.path, update, { allowCreate: target.allowCreate })) {
324
+ changed = true;
325
+ }
326
+ }
327
+ logMcpVerbose("scheduleJsonTargets completed", { changed });
328
+ return changed;
329
+ }
330
+ function scheduleCliIfAvailable(ctx, command, args, shouldRun, options2) {
331
+ logMcpVerbose("scheduleCliIfAvailable invoked", { command, args, shouldRun });
332
+ if (!shouldRun) {
333
+ logMcpVerbose("scheduleCliIfAvailable skipped: shouldRun false", { command });
334
+ return;
335
+ }
336
+ const commandPath = getCommandPath(command);
337
+ if (!commandPath) {
338
+ logMcpVerbose("scheduleCliIfAvailable skipped: command not available", { command });
339
+ return;
340
+ }
341
+ logMcpVerbose("scheduleCliIfAvailable scheduling CLI registration", { command });
342
+ scheduleCliRegistration(ctx, commandPath, args, options2);
343
+ }
344
+ function createServerUpdater(containerKey, entry) {
345
+ logMcpVerbose("createServerUpdater invoked", { containerKey });
346
+ return (current) => {
347
+ const next = { ...current };
348
+ const servers = { ...next[containerKey] ?? {} };
349
+ servers[MCP_SERVER_NAME] = cloneJsonRecord(entry);
350
+ next[containerKey] = servers;
351
+ return next;
352
+ };
353
+ }
354
+ function getVsCodeUserConfigPaths(homeDir) {
355
+ const stable = /* @__PURE__ */ new Set();
356
+ const insiders = /* @__PURE__ */ new Set();
357
+ const platform2 = process.platform;
358
+ logMcpVerbose("getVsCodeUserConfigPaths invoked", { homeDir, platform: platform2 });
359
+ if (platform2 === "darwin") {
360
+ stable.add(path2.join(homeDir, "Library", "Application Support", "Code", "User", "mcp.json"));
361
+ insiders.add(path2.join(homeDir, "Library", "Application Support", "Code - Insiders", "User", "mcp.json"));
362
+ } else if (platform2 === "win32") {
363
+ const appData = process.env.APPDATA;
364
+ if (appData) {
365
+ stable.add(path2.join(appData, "Code", "User", "mcp.json"));
366
+ insiders.add(path2.join(appData, "Code - Insiders", "User", "mcp.json"));
367
+ }
368
+ } else {
369
+ stable.add(path2.join(homeDir, ".config", "Code", "User", "mcp.json"));
370
+ insiders.add(path2.join(homeDir, ".config", "Code - Insiders", "User", "mcp.json"));
371
+ }
372
+ return {
373
+ stable: Array.from(stable),
374
+ insiders: Array.from(insiders)
375
+ };
376
+ }
377
+ function getWindsurfConfigPaths(homeDir) {
378
+ const paths = /* @__PURE__ */ new Set();
379
+ logMcpVerbose("getWindsurfConfigPaths invoked", { homeDir, platform: process.platform });
380
+ if (process.platform === "darwin") {
381
+ paths.add(path2.join(homeDir, ".codeium", "windsurf", "mcp_config.json"));
382
+ } else if (process.platform === "win32") {
383
+ const appData = process.env.APPDATA;
384
+ if (appData) {
385
+ paths.add(path2.join(appData, "Codeium", "Windsurf", "mcp_config.json"));
386
+ }
387
+ } else {
388
+ paths.add(path2.join(homeDir, ".config", "Codeium", "Windsurf", "mcp_config.json"));
389
+ paths.add(path2.join(homeDir, ".codeium", "windsurf", "mcp_config.json"));
390
+ paths.add(path2.join(homeDir, ".var", "app", "com.codeium.windsurf", "config", "Codeium", "Windsurf", "mcp_config.json"));
391
+ }
392
+ return Array.from(paths);
393
+ }
394
+ function readJsonOrEmpty(fullPath) {
395
+ logMcpVerbose("readJsonOrEmpty invoked", { fullPath });
396
+ if (!fs2.existsSync(fullPath)) {
397
+ logMcpVerbose("readJsonOrEmpty no file found", { fullPath });
398
+ return { existed: false, data: {}, parseError: null };
399
+ }
400
+ try {
401
+ const raw = fs2.readFileSync(fullPath, "utf-8");
402
+ const data = raw.trim() ? JSON.parse(raw) : {};
403
+ logMcpVerbose("readJsonOrEmpty parsed file", { fullPath, hasContent: Boolean(raw.trim()) });
404
+ return { existed: true, data, parseError: null };
405
+ } catch (error) {
406
+ const parseError = error instanceof Error ? error : new Error(String(error));
407
+ logMcpVerbose("readJsonOrEmpty parse error", { fullPath, error: parseError.message });
408
+ return { existed: true, data: {}, parseError };
409
+ }
410
+ }
411
+ function cloneJsonRecord(value) {
412
+ return JSON.parse(JSON.stringify(value));
413
+ }
414
+ function scheduleCliRegistration(ctx, command, args, options2 = {}) {
415
+ logMcpVerbose("scheduleCliRegistration invoked", { command, args, options: options2 });
416
+ ctx.registerCommandHandler(async () => {
417
+ try {
418
+ await ctx.runScheduledCommand(command, args, options2, {
419
+ recordInCommandsExecuted: false
420
+ });
421
+ } catch (error) {
422
+ logMcpVerbose("scheduleCliRegistration encountered error. Ignoring.", { command, args, options: options2, error: error instanceof Error ? { message: error.message, stack: error.stack } : error });
423
+ }
424
+ });
425
+ }
426
+ function logMcpVerbose(message, details) {
427
+ logVerbose(`[mcp] ${message}`, details);
428
+ }
429
+ function logInvestigationStatus(editorLabel, configPath, scope) {
430
+ const exists = fs2.existsSync(configPath);
431
+ logMcpVerbose(`Investigating ${editorLabel} config${scope ? ` (${scope})` : ""}`, { path: configPath, exists });
432
+ }
433
+
73
434
  // src/index.ts
74
435
  var jsLikeFileExtensions = [
75
436
  "mtsx",
@@ -124,12 +485,16 @@ function resolveOnQuestionMode(opt) {
124
485
  throw new UserError(`Invalid argument for --on-question: "${opt}". Valid modes are: "ask", "guess", "error", "default".`);
125
486
  }
126
487
  var program = new Command();
127
- program.name(package_default.name).description("Stack Auth Initialization Tool").version(package_default.version).argument("[project-path]", "Path to your project").usage(`[project-path] [options]`).option("--dry-run", "Run without making any changes").option("--neon", "Use Neon database").option("--js", "Initialize for JavaScript project").option("--next", "Initialize for Next.js project").option("--react", "Initialize for React project").option("--npm", "Use npm as package manager").option("--yarn", "Use yarn as package manager").option("--pnpm", "Use pnpm as package manager").option("--bun", "Use bun as package manager").option("--client", "Initialize client-side only").option("--server", "Initialize server-side only").option("--project-id <project-id>", "Project ID to use in setup").option("--publishable-client-key <publishable-client-key>", "Publishable client key to use in setup").option("--no-browser", "Don't open browser for environment variable setup").option("--on-question <mode>", "How to handle interactive questions: ask | guess | error | default", "default").addHelpText("after", `
488
+ program.name(package_default.name).description("Stack Auth Initialization Tool").version(package_default.version).argument("[project-path]", "Path to your project").usage(`[project-path] [options]`).option("--dry-run", "Run without making any changes").option("--neon", "Use Neon database").option("--js", "Initialize for JavaScript project").option("--next", "Initialize for Next.js project").option("--react", "Initialize for React project").option("--npm", "Use npm as package manager").option("--yarn", "Use yarn as package manager").option("--pnpm", "Use pnpm as package manager").option("--bun", "Use bun as package manager").option("--client", "Initialize client-side only").option("--server", "Initialize server-side only").option("--project-id <project-id>", "Project ID to use in setup").option("--publishable-client-key <publishable-client-key>", "Publishable client key to use in setup").option("--no-browser", "Don't open browser for environment variable setup").option("--on-question <mode>", "How to handle interactive questions: ask | guess | error | default", "default").option("--no-warn-uncommitted-changes", "Don't warn about uncommitted changes in the Git repository").addHelpText("after", `
128
489
  For more information, please visit https://docs.stack-auth.com/getting-started/setup`);
129
490
  program.parse();
130
491
  var options = program.opts();
131
492
  var savedProjectPath = program.args[0] || void 0;
132
- var isDryRun = options.dryRun || false;
493
+ var verboseEnvRaw = process.env.STACK_VERBOSE;
494
+ var parsedVerboseLevel = typeof verboseEnvRaw === "string" && verboseEnvRaw.trim().length > 0 ? Number.parseInt(verboseEnvRaw.trim(), 10) : 0;
495
+ var verboseLevel = Number.isFinite(parsedVerboseLevel) ? Math.max(0, parsedVerboseLevel) : 0;
496
+ var isVerbose = verboseLevel > 0;
497
+ var isDryRun = options.dryRun || isTruthyEnv("STACK_DRY_RUN") || false;
133
498
  var isNeon = options.neon || false;
134
499
  var typeFromArgs = options.js ? "js" : options.next ? "next" : options.react ? "react" : void 0;
135
500
  var packageManagerFromArgs = options.npm ? "npm" : options.yarn ? "yarn" : options.pnpm ? "pnpm" : options.bun ? "bun" : void 0;
@@ -138,6 +503,7 @@ var isServer = options.server || false;
138
503
  var projectIdFromArgs = options.projectId;
139
504
  var publishableClientKeyFromArgs = options.publishableClientKey;
140
505
  var onQuestionMode = resolveOnQuestionMode(options.onQuestion);
506
+ var warnUncommittedChanges = options.warnUncommittedChanges ?? true;
141
507
  var noBrowser = !options.browser;
142
508
  var ansis = {
143
509
  red: "\x1B[31m",
@@ -154,11 +520,16 @@ var colorize = {
154
520
  yellow: (strings, ...values) => ansis.yellow + templateIdentity(strings, ...values) + ansis.clear,
155
521
  bold: (strings, ...values) => ansis.bold + templateIdentity(strings, ...values) + ansis.clear
156
522
  };
523
+ configureVerboseLogging({
524
+ level: verboseLevel,
525
+ formatter: (message) => colorize.blue`[verbose] ${message}`
526
+ });
157
527
  var filesCreated = [];
158
528
  var filesModified = [];
159
529
  var commandsExecuted = [];
160
530
  var packagesToInstall = [];
161
531
  var writeFileHandlers = [];
532
+ var deferredCommandHandlers = [];
162
533
  var nextSteps = [
163
534
  `Create an account and Stack Auth API key for your project on https://app.stack-auth.com`
164
535
  ];
@@ -171,6 +542,7 @@ var ph_client = new PostHog(STACK_AUTH_PUBLIC_HOG_KEY, {
171
542
  });
172
543
  var distinctId = crypto.randomUUID();
173
544
  async function capture(event, properties) {
545
+ logVerbose("capture event", { event, properties });
174
546
  ph_client.capture({
175
547
  event: `${EVENT_PREFIX}${event}`,
176
548
  distinctId,
@@ -194,6 +566,25 @@ async function main() {
194
566
  \u2588\u2588\u2588\u2588\u2588\u2588
195
567
  `);
196
568
  console.log();
569
+ logVerbose("Initialization run metadata", {
570
+ version: package_default.version,
571
+ cwd: process.cwd(),
572
+ args: program.args,
573
+ options: {
574
+ isDryRun,
575
+ isVerbose,
576
+ isNeon,
577
+ typeFromArgs,
578
+ packageManagerFromArgs,
579
+ isClient,
580
+ isServer,
581
+ projectIdFromArgs: Boolean(projectIdFromArgs),
582
+ publishableClientKeyFromArgs: Boolean(publishableClientKeyFromArgs),
583
+ noBrowser,
584
+ onQuestionMode,
585
+ verboseLevel
586
+ }
587
+ });
197
588
  await capture("start", {
198
589
  version: package_default.version,
199
590
  isDryRun,
@@ -203,15 +594,38 @@ async function main() {
203
594
  isClient,
204
595
  isServer,
205
596
  noBrowser,
206
- platform: os.platform(),
207
- arch: os.arch(),
597
+ platform: os2.platform(),
598
+ arch: os2.arch(),
208
599
  nodeVersion: process.version
209
600
  });
210
- await new Promise((resolve) => resolve());
601
+ await new Promise((resolve3) => resolve3());
211
602
  await clearStdin();
212
603
  const projectPath = await getProjectPath();
604
+ await ensureGitWorkspaceIsReady(projectPath);
605
+ logVerbose("Project path prepared", { projectPath, isDryRun, isVerbose });
606
+ scheduleMcpConfiguration({
607
+ projectPath,
608
+ isDryRun,
609
+ colorize,
610
+ registerWriteHandler: (handler) => writeFileHandlers.push(handler),
611
+ registerCommandHandler: (handler) => deferredCommandHandlers.push(handler),
612
+ recordFileChange,
613
+ runScheduledCommand
614
+ });
615
+ nextSteps.push("Restart your MCP-enabled editors so they pick up the Stack Auth MCP.");
616
+ logVerbose("MCP configuration scheduled", {
617
+ writeHandlers: writeFileHandlers.length,
618
+ deferredCommands: deferredCommandHandlers.length
619
+ });
213
620
  const { packageJson: projectPackageJson } = await Steps.getProject();
214
621
  const type = await Steps.getProjectType({ packageJson: projectPackageJson });
622
+ logVerbose("Project inspection complete", {
623
+ detectedType: type,
624
+ dependencies: {
625
+ hasReact: Boolean(projectPackageJson.dependencies?.["react"]),
626
+ hasNext: Boolean(projectPackageJson.dependencies?.["next"])
627
+ }
628
+ });
215
629
  await capture("project-type-selected", {
216
630
  type,
217
631
  wasSpecifiedInArgs: !!typeFromArgs
@@ -222,6 +636,7 @@ async function main() {
222
636
  const convexIntegration = await Steps.maybeInstallConvexIntegration({ packageJson: projectPackageJson, type });
223
637
  if (convexIntegration) {
224
638
  nextSteps.push(...convexIntegration.instructions);
639
+ logVerbose("Convex integration detected", convexIntegration);
225
640
  }
226
641
  if (type === "next") {
227
642
  const projectInfo = await Steps.getNextProjectInfo({ packageJson: projectPackageJson });
@@ -263,7 +678,9 @@ async function main() {
263
678
  `Follow the instructions on how to use Stack Auth's vanilla SDK at http://docs.stack-auth.com/others/js-client`
264
679
  );
265
680
  }
681
+ logVerbose("Primary integration steps completed", { type, nextStepsCount: nextSteps.length });
266
682
  const { packageManager } = await Steps.getPackageManager();
683
+ logVerbose("Package manager determined", { packageManager });
267
684
  await capture(`package-manager-selected`, {
268
685
  packageManager,
269
686
  wasSpecifiedInArgs: !!packageManagerFromArgs
@@ -283,6 +700,10 @@ async function main() {
283
700
  shell: true,
284
701
  cwd: projectPath
285
702
  });
703
+ logVerbose("Dependency installation finished", {
704
+ packageManager,
705
+ packages: packagesToInstall
706
+ });
286
707
  await capture(`dependencies-installed`, {
287
708
  packageManager,
288
709
  packages: packagesToInstall
@@ -290,10 +711,13 @@ async function main() {
290
711
  console.log();
291
712
  console.log(colorize.bold`Writing files...`);
292
713
  console.log();
293
- for (const writeFileHandler of writeFileHandlers) {
714
+ for (let i = 0; i < writeFileHandlers.length; i++) {
715
+ const writeFileHandler = writeFileHandlers[i];
716
+ logVerbose("Executing write handler", { index: i });
294
717
  await writeFileHandler();
295
718
  }
296
719
  console.log(`${colorize.green`√`} Done writing files`);
720
+ await runDeferredCommands();
297
721
  console.log("\n\n\n");
298
722
  console.log(colorize.bold`${colorize.green`Installation succeeded!`}`);
299
723
  console.log();
@@ -302,6 +726,9 @@ async function main() {
302
726
  console.log(` ${colorize.blue`${command}`}`);
303
727
  }
304
728
  console.log();
729
+ console.log("MCP servers installed:");
730
+ console.log(` ${colorize.green`https://mcp.stack-auth.com`}`);
731
+ console.log();
305
732
  console.log("Files written:");
306
733
  for (const file of filesModified) {
307
734
  console.log(` ${colorize.yellow`${file}`}`);
@@ -378,34 +805,53 @@ main().catch(async (err) => {
378
805
  var Steps = {
379
806
  async getProject() {
380
807
  let projectPath = await getProjectPath();
381
- if (!fs.existsSync(projectPath)) {
808
+ logVerbose("Steps.getProject invoked", { projectPath });
809
+ if (!fs3.existsSync(projectPath)) {
382
810
  throw new UserError(`The project path ${projectPath} does not exist`);
383
811
  }
384
- const packageJsonPath = path.join(projectPath, "package.json");
385
- if (!fs.existsSync(packageJsonPath)) {
812
+ const packageJsonPath = path3.join(projectPath, "package.json");
813
+ if (!fs3.existsSync(packageJsonPath)) {
386
814
  throw new UserError(
387
815
  `The package.json file does not exist in the project path ${projectPath}. You must initialize a new project first before installing Stack.`
388
816
  );
389
817
  }
390
- const packageJsonText = fs.readFileSync(packageJsonPath, "utf-8");
818
+ const packageJsonText = fs3.readFileSync(packageJsonPath, "utf-8");
391
819
  let packageJson;
392
820
  try {
393
821
  packageJson = JSON.parse(packageJsonText);
394
822
  } catch (e) {
395
823
  throw new UserError(`package.json file is not valid JSON: ${e}`);
396
824
  }
825
+ logVerbose("Steps.getProject completed", {
826
+ packageJsonPath,
827
+ dependencyCounts: {
828
+ dependencies: Object.keys(packageJson.dependencies ?? {}).length,
829
+ devDependencies: Object.keys(packageJson.devDependencies ?? {}).length
830
+ }
831
+ });
397
832
  return { packageJson };
398
833
  },
399
834
  async getProjectType({ packageJson }) {
400
- if (typeFromArgs) return typeFromArgs;
835
+ if (typeFromArgs) {
836
+ logVerbose("Steps.getProjectType using CLI override", { typeFromArgs });
837
+ return typeFromArgs;
838
+ }
839
+ logVerbose("Steps.getProjectType attempting autodetect", {
840
+ hasNext: Boolean(packageJson.dependencies?.["next"] || packageJson.devDependencies?.["next"]),
841
+ hasReact: Boolean(packageJson.dependencies?.["react"] || packageJson.dependencies?.["react-dom"]),
842
+ onQuestionMode
843
+ });
401
844
  const maybeNextProject = await Steps.maybeGetNextProjectInfo({ packageJson });
402
845
  if (!("error" in maybeNextProject)) {
846
+ logVerbose("Steps.getProjectType resolved to Next.js project");
403
847
  return "next";
404
848
  }
405
849
  if (packageJson.dependencies?.["react"] || packageJson.dependencies?.["react-dom"]) {
850
+ logVerbose("Steps.getProjectType resolved to React project");
406
851
  return "react";
407
852
  }
408
853
  if (onQuestionMode === "guess") {
854
+ logVerbose("Steps.getProjectType defaulting to JS due to --on-question=guess");
409
855
  return "js";
410
856
  }
411
857
  if (onQuestionMode === "error") {
@@ -417,12 +863,14 @@ var Steps = {
417
863
  name: "type",
418
864
  message: "Which integration would you like to install?",
419
865
  choices: [
420
- { name: "None (vanilla JS, Node.js, etc)", value: "js" },
866
+ { name: "Vanilla JS (other/no framework)", value: "js" },
867
+ { name: "Node.js", value: "js" },
421
868
  { name: "React", value: "react" },
422
869
  { name: "Next.js", value: "next" }
423
870
  ]
424
871
  }
425
872
  ]);
873
+ logVerbose("Steps.getProjectType received user selection", { type });
426
874
  return type;
427
875
  },
428
876
  async getStackPackageName(type, install = false) {
@@ -431,42 +879,62 @@ var Steps = {
431
879
  next: install && process.env.STACK_NEXT_INSTALL_PACKAGE_NAME_OVERRIDE || "@stackframe/stack",
432
880
  react: install && process.env.STACK_REACT_INSTALL_PACKAGE_NAME_OVERRIDE || "@stackframe/react"
433
881
  };
434
- return mapping[type];
882
+ const packageName = mapping[type];
883
+ logVerbose("Steps.getStackPackageName resolved", { type, install, packageName });
884
+ return packageName;
435
885
  },
436
886
  async addStackPackage(type) {
437
- packagesToInstall.push(await Steps.getStackPackageName(type, true));
887
+ const pkgName = await Steps.getStackPackageName(type, true);
888
+ logVerbose("Steps.addStackPackage scheduling install", { pkgName });
889
+ packagesToInstall.push(pkgName);
438
890
  },
439
891
  async getNextProjectInfo({ packageJson }) {
892
+ logVerbose("Steps.getNextProjectInfo invoked");
440
893
  const maybe = await Steps.maybeGetNextProjectInfo({ packageJson });
441
- if ("error" in maybe) throw new UserError(maybe.error);
894
+ if ("error" in maybe) {
895
+ logVerbose("Steps.getNextProjectInfo failed validation", maybe);
896
+ throw new UserError(maybe.error);
897
+ }
898
+ logVerbose("Steps.getNextProjectInfo resolved", maybe);
442
899
  return maybe;
443
900
  },
444
901
  async maybeGetNextProjectInfo({ packageJson }) {
445
902
  const projectPath = await getProjectPath();
903
+ logVerbose("Steps.maybeGetNextProjectInfo evaluating Next.js eligibility", { projectPath });
446
904
  const nextVersionInPackageJson = packageJson.dependencies?.["next"] ?? packageJson.devDependencies?.["next"];
447
905
  if (!nextVersionInPackageJson) {
906
+ logVerbose("Steps.maybeGetNextProjectInfo missing Next dependency");
448
907
  return { error: `The project at ${projectPath} does not appear to be a Next.js project, or does not have 'next' installed as a dependency.` };
449
908
  }
450
909
  if (!nextVersionInPackageJson.includes("14") && !nextVersionInPackageJson.includes("15") && nextVersionInPackageJson !== "latest") {
910
+ logVerbose("Steps.maybeGetNextProjectInfo found unsupported Next version", { version: nextVersionInPackageJson });
451
911
  return { error: `The project at ${projectPath} is using an unsupported version of Next.js (found ${nextVersionInPackageJson}).
452
912
 
453
913
  Only Next.js 14 & 15 projects are currently supported. See Next's upgrade guide: https://nextjs.org/docs/app/building-your-application/upgrading/version-14` };
454
914
  }
455
- const hasSrcAppFolder = fs.existsSync(path.join(projectPath, "src/app"));
456
- const srcPath = path.join(projectPath, hasSrcAppFolder ? "src" : "");
457
- const appPath = path.join(srcPath, "app");
458
- if (!fs.existsSync(appPath)) {
915
+ const hasSrcAppFolder = fs3.existsSync(path3.join(projectPath, "src/app"));
916
+ const srcPath = path3.join(projectPath, hasSrcAppFolder ? "src" : "");
917
+ const appPath = path3.join(srcPath, "app");
918
+ if (!fs3.existsSync(appPath)) {
919
+ logVerbose("Steps.maybeGetNextProjectInfo missing Next app directory", { appPath });
459
920
  return { error: `The app path ${appPath} does not exist. Only the Next.js App router is supported \u2014 are you maybe on the Pages router instead?` };
460
921
  }
461
- const nextConfigPathWithoutExtension = path.join(projectPath, "next.config");
922
+ const nextConfigPathWithoutExtension = path3.join(projectPath, "next.config");
462
923
  const nextConfigFileExtension = await findJsExtension(
463
924
  nextConfigPathWithoutExtension
464
925
  );
465
926
  const nextConfigPath = nextConfigPathWithoutExtension + "." + (nextConfigFileExtension ?? "js");
466
- if (!fs.existsSync(nextConfigPath)) {
927
+ if (!fs3.existsSync(nextConfigPath)) {
928
+ logVerbose("Steps.maybeGetNextProjectInfo missing next.config file", { nextConfigPath });
467
929
  return { error: `Expected file at ${nextConfigPath} for Next.js projects.` };
468
930
  }
469
931
  const dryUpdateNextLayoutFileResult = await Steps.dryUpdateNextLayoutFile({ appPath, defaultExtension: "jsx" });
932
+ logVerbose("Steps.maybeGetNextProjectInfo success", {
933
+ projectPath,
934
+ srcPath,
935
+ appPath,
936
+ detectedExtension: dryUpdateNextLayoutFileResult.fileExtension
937
+ });
470
938
  return {
471
939
  type: "next",
472
940
  srcPath,
@@ -477,17 +945,21 @@ Only Next.js 14 & 15 projects are currently supported. See Next's upgrade guide:
477
945
  },
478
946
  async writeEnvVars(type) {
479
947
  const projectPath = await getProjectPath();
480
- if (type !== "next") return false;
481
- const envLocalPath = path.join(projectPath, ".env.local");
948
+ logVerbose("Steps.writeEnvVars invoked", { type, projectPath });
949
+ if (type !== "next") {
950
+ logVerbose("Steps.writeEnvVars skipped", { reason: "non-next-project" });
951
+ return false;
952
+ }
953
+ const envLocalPath = path3.join(projectPath, ".env.local");
482
954
  const potentialEnvLocations = [
483
- path.join(projectPath, ".env"),
484
- path.join(projectPath, ".env.development"),
485
- path.join(projectPath, ".env.default"),
486
- path.join(projectPath, ".env.defaults"),
487
- path.join(projectPath, ".env.example"),
955
+ path3.join(projectPath, ".env"),
956
+ path3.join(projectPath, ".env.development"),
957
+ path3.join(projectPath, ".env.default"),
958
+ path3.join(projectPath, ".env.defaults"),
959
+ path3.join(projectPath, ".env.example"),
488
960
  envLocalPath
489
961
  ];
490
- if (potentialEnvLocations.every((p) => !fs.existsSync(p))) {
962
+ if (potentialEnvLocations.every((p) => !fs3.existsSync(p))) {
491
963
  const envContent = noBrowser ? `# Stack Auth keys
492
964
  # To get these variables:
493
965
  # 1. Go to https://app.stack-auth.com
@@ -503,43 +975,54 @@ NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY="${publishableClientKeyFromArgs ?? ""}"
503
975
  STACK_SECRET_SERVER_KEY=
504
976
  `;
505
977
  laterWriteFile(envLocalPath, envContent);
978
+ logVerbose("Steps.writeEnvVars scheduled .env.local creation", { envLocalPath });
506
979
  return true;
507
980
  }
981
+ logVerbose("Steps.writeEnvVars found existing env files", { potentialEnvLocations });
508
982
  return false;
509
983
  },
510
984
  async maybeInstallConvexIntegration({ packageJson, type }) {
511
985
  const hasConvexDependency = Boolean(packageJson.dependencies?.["convex"] || packageJson.devDependencies?.["convex"]);
512
986
  if (!hasConvexDependency) {
987
+ logVerbose("Steps.maybeInstallConvexIntegration skipped", { reason: "no-convex-dependency" });
513
988
  return null;
514
989
  }
515
990
  const projectPath = await getProjectPath();
516
- const convexDir = path.join(projectPath, "convex");
517
- if (!fs.existsSync(convexDir)) {
991
+ const convexDir = path3.join(projectPath, "convex");
992
+ if (!fs3.existsSync(convexDir)) {
993
+ logVerbose("Steps.maybeInstallConvexIntegration skipped", { reason: "missing-convex-dir", convexDir });
518
994
  return null;
519
995
  }
520
996
  const stackPackageName = await Steps.getStackPackageName(type);
521
997
  const instructions = [];
522
- const authConfigPath = path.join(convexDir, "auth.config.ts");
998
+ logVerbose("Steps.maybeInstallConvexIntegration configuring", { convexDir, stackPackageName });
999
+ const authConfigPath = path3.join(convexDir, "auth.config.ts");
523
1000
  const desiredAuthConfig = createConvexAuthConfigContent({ stackPackageName, type });
524
1001
  const existingAuthConfig = await readFile(authConfigPath);
525
1002
  if (!existingAuthConfig || !existingAuthConfig.includes("getConvexProvidersConfig") && !existingAuthConfig.includes("@stackframe/")) {
526
1003
  laterWriteFile(authConfigPath, desiredAuthConfig);
1004
+ logVerbose("Steps.maybeInstallConvexIntegration scheduled auth.config.ts update", { authConfigPath });
527
1005
  }
528
- const convexConfigPath = path.join(convexDir, "convex.config.ts");
1006
+ const convexConfigPath = path3.join(convexDir, "convex.config.ts");
529
1007
  const existingConvexConfig = await readFile(convexConfigPath);
530
1008
  const desiredConvexConfig = createConvexIntegrationConvexConfigContent(stackPackageName);
531
1009
  let needsManualConvexConfig = false;
532
1010
  if (!existingConvexConfig) {
533
1011
  laterWriteFile(convexConfigPath, desiredConvexConfig);
1012
+ logVerbose("Steps.maybeInstallConvexIntegration writing convex.config.ts from template", { convexConfigPath });
534
1013
  } else if (existingConvexConfig.includes("app.use(stackAuthComponent") && existingConvexConfig.includes("/convex.config") && existingConvexConfig.includes("stackframe")) {
1014
+ logVerbose("Steps.maybeInstallConvexIntegration detected existing convex.config.ts integration", { convexConfigPath });
535
1015
  } else {
536
1016
  const integratedContent = integrateConvexConfig(existingConvexConfig, stackPackageName);
537
1017
  if (integratedContent) {
538
1018
  laterWriteFile(convexConfigPath, integratedContent);
1019
+ logVerbose("Steps.maybeInstallConvexIntegration merged convex.config.ts content", { convexConfigPath });
539
1020
  } else if (isSimpleConvexConfig(existingConvexConfig)) {
540
1021
  laterWriteFile(convexConfigPath, desiredConvexConfig);
1022
+ logVerbose("Steps.maybeInstallConvexIntegration replaced simple convex.config.ts", { convexConfigPath });
541
1023
  } else {
542
1024
  needsManualConvexConfig = true;
1025
+ logVerbose("Steps.maybeInstallConvexIntegration requiring manual convex.config.ts review", { convexConfigPath });
543
1026
  }
544
1027
  }
545
1028
  if (needsManualConvexConfig) {
@@ -549,14 +1032,16 @@ STACK_SECRET_SERVER_KEY=
549
1032
  if (convexClientUpdateResult.skippedFiles.length > 0) {
550
1033
  instructions.push("Review your Convex client setup and call stackClientApp.getConvexClientAuth({}) or stackServerApp.getConvexClientAuth({}) manually where needed.");
551
1034
  }
1035
+ logVerbose("Steps.maybeInstallConvexIntegration client update summary", convexClientUpdateResult);
552
1036
  instructions.push(
553
1037
  "Set the Stack Auth environment variables in Convex (Deployment \u2192 Settings \u2192 Environment Variables).",
554
1038
  "Verify your Convex clients call stackClientApp.getConvexClientAuth({}) or stackServerApp.getConvexClientAuth({}) so they share authentication with Stack Auth."
555
1039
  );
1040
+ logVerbose("Steps.maybeInstallConvexIntegration completed", { instructions });
556
1041
  return { instructions };
557
1042
  },
558
1043
  async dryUpdateNextLayoutFile({ appPath, defaultExtension }) {
559
- const layoutPathWithoutExtension = path.join(appPath, "layout");
1044
+ const layoutPathWithoutExtension = path3.join(appPath, "layout");
560
1045
  const layoutFileExtension = await findJsExtension(layoutPathWithoutExtension) ?? defaultExtension;
561
1046
  const layoutPath = layoutPathWithoutExtension + "." + layoutFileExtension;
562
1047
  const layoutContent = await readFile(layoutPath) ?? throwErr(
@@ -574,22 +1059,26 @@ STACK_SECRET_SERVER_KEY=
574
1059
  };
575
1060
  },
576
1061
  async updateNextLayoutFile(projectInfo) {
1062
+ logVerbose("Steps.updateNextLayoutFile invoked", projectInfo);
577
1063
  const res = await Steps.dryUpdateNextLayoutFile(projectInfo);
578
1064
  laterWriteFile(res.path, res.updatedContent);
1065
+ logVerbose("Steps.updateNextLayoutFile scheduled write", { path: res.path });
579
1066
  return res;
580
1067
  },
581
1068
  async writeStackAppFile({ type, srcPath, defaultExtension, indentation }, clientOrServer, alsoHasClient) {
1069
+ logVerbose("Steps.writeStackAppFile invoked", { type, srcPath, clientOrServer, alsoHasClient });
582
1070
  const packageName = await Steps.getStackPackageName(type);
583
1071
  const clientOrServerCap = {
584
1072
  client: "Client",
585
1073
  server: "Server"
586
1074
  }[clientOrServer];
587
1075
  const relativeStackAppPath = `stack/${clientOrServer}`;
588
- const stackAppPathWithoutExtension = path.join(srcPath, relativeStackAppPath);
1076
+ const stackAppPathWithoutExtension = path3.join(srcPath, relativeStackAppPath);
589
1077
  const stackAppFileExtension = await findJsExtension(stackAppPathWithoutExtension) ?? defaultExtension;
590
1078
  const stackAppPath = stackAppPathWithoutExtension + "." + stackAppFileExtension;
591
1079
  const stackAppContent = await readFile(stackAppPath);
592
1080
  if (stackAppContent) {
1081
+ logVerbose("Steps.writeStackAppFile found existing file", { stackAppPath });
593
1082
  if (!stackAppContent.includes("@stackframe/")) {
594
1083
  throw new UserError(
595
1084
  `A file at the path ${stackAppPath} already exists. Stack uses the stack/${clientOrServer}.ts file to initialize the Stack SDK. Please remove the existing file and try again.`
@@ -604,7 +1093,7 @@ STACK_SECRET_SERVER_KEY=
604
1093
  const jsOptions = type === "js" ? [
605
1094
  `
606
1095
 
607
- ${indentation}// get your Stack Auth API keys from https://app.stack-auth.com${clientOrServer === "client" ? ` and store them in a safe place (eg. environment variables)` : ""}`,
1096
+ ${indentation}// get your Stack Auth API keys from https://app.stack-auth.com${clientOrServer === "client" ? ` and store them in a safe place (e.g. environment variables)` : ""}`,
608
1097
  `${projectIdFromArgs ? `${indentation}projectId: ${JSON.stringify(projectIdFromArgs)},` : ""}`,
609
1098
  `${indentation}publishableClientKey: ${publishableClientKeyWrite},`,
610
1099
  `${clientOrServer === "server" ? `${indentation}secretServerKey: process.env.STACK_SECRET_SERVER_KEY,` : ""}`
@@ -631,16 +1120,19 @@ ${shouldInheritFromClient ? `${indentation}inheritsFrom: stackClientApp,` : `${i
631
1120
  });
632
1121
  `.trim() + "\n"
633
1122
  );
1123
+ logVerbose("Steps.writeStackAppFile scheduled creation", { stackAppPath, inheritsFromClient: shouldInheritFromClient });
634
1124
  return { fileName: stackAppPath };
635
1125
  },
636
1126
  async writeReactClientFile({ srcPath, defaultExtension, indentation, hasReactRouterDom }) {
1127
+ logVerbose("Steps.writeReactClientFile invoked", { srcPath, hasReactRouterDom });
637
1128
  const packageName = await Steps.getStackPackageName("react");
638
1129
  const relativeStackAppPath = `stack/client`;
639
- const stackAppPathWithoutExtension = path.join(srcPath, relativeStackAppPath);
1130
+ const stackAppPathWithoutExtension = path3.join(srcPath, relativeStackAppPath);
640
1131
  const stackAppFileExtension = await findJsExtension(stackAppPathWithoutExtension) ?? defaultExtension;
641
1132
  const stackAppPath = stackAppPathWithoutExtension + "." + stackAppFileExtension;
642
1133
  const stackAppContent = await readFile(stackAppPath);
643
1134
  if (stackAppContent) {
1135
+ logVerbose("Steps.writeReactClientFile found existing file", { stackAppPath });
644
1136
  if (!stackAppContent.includes("@stackframe/")) {
645
1137
  throw new UserError(`A file at the path ${stackAppPath} already exists. Stack uses the stack/client.ts file to initialize the Stack SDK. Please remove the existing file and try again.`);
646
1138
  }
@@ -667,10 +1159,12 @@ ${indentation}publishableClientKey: ${publishableClientKeyWrite}${redirectMethod
667
1159
  });
668
1160
  `
669
1161
  );
1162
+ logVerbose("Steps.writeReactClientFile scheduled creation", { stackAppPath });
670
1163
  return { fileName: stackAppPath };
671
1164
  },
672
1165
  async writeNextHandlerFile(projectInfo) {
673
- const handlerPathWithoutExtension = path.join(
1166
+ logVerbose("Steps.writeNextHandlerFile invoked", projectInfo);
1167
+ const handlerPathWithoutExtension = path3.join(
674
1168
  projectInfo.appPath,
675
1169
  "handler/[...stack]/page"
676
1170
  );
@@ -678,6 +1172,7 @@ ${indentation}publishableClientKey: ${publishableClientKeyWrite}${redirectMethod
678
1172
  const handlerPath = handlerPathWithoutExtension + "." + handlerFileExtension;
679
1173
  const handlerContent = await readFile(handlerPath);
680
1174
  if (handlerContent && !handlerContent.includes("@stackframe/")) {
1175
+ logVerbose("Steps.writeNextHandlerFile found conflicting file", { handlerPath });
681
1176
  throw new UserError(
682
1177
  `A file at the path ${handlerPath} already exists.Stack uses the / handler path to handle incoming requests.Please remove the existing file and try again.`
683
1178
  );
@@ -694,7 +1189,8 @@ ${projectInfo.indentation} return <StackHandler fullPage app = { stackServerApp
694
1189
  );
695
1190
  },
696
1191
  async writeNextLoadingFile(projectInfo) {
697
- let loadingPathWithoutExtension = path.join(projectInfo.appPath, "loading");
1192
+ logVerbose("Steps.writeNextLoadingFile invoked", projectInfo);
1193
+ let loadingPathWithoutExtension = path3.join(projectInfo.appPath, "loading");
698
1194
  const loadingFileExtension = await findJsExtension(loadingPathWithoutExtension) ?? projectInfo.defaultExtension;
699
1195
  const loadingPath = loadingPathWithoutExtension + "." + loadingFileExtension;
700
1196
  laterWriteFileIfNotExists(
@@ -709,9 +1205,13 @@ ${projectInfo.indentation}return <></>;
709
1205
  );
710
1206
  },
711
1207
  async getPackageManager() {
712
- if (packageManagerFromArgs) return { packageManager: packageManagerFromArgs };
1208
+ if (packageManagerFromArgs) {
1209
+ logVerbose("Steps.getPackageManager using CLI override", { packageManager: packageManagerFromArgs });
1210
+ return { packageManager: packageManagerFromArgs };
1211
+ }
713
1212
  const packageManager = await promptPackageManager();
714
1213
  const versionCommand = `${packageManager} --version`;
1214
+ logVerbose("Steps.getPackageManager checking binary availability", { packageManager });
715
1215
  try {
716
1216
  await shellNicelyFormatted(versionCommand, { shell: true, quiet: true });
717
1217
  } catch (err) {
@@ -720,6 +1220,7 @@ ${projectInfo.indentation}return <></>;
720
1220
  `Could not run the package manager command '${versionCommand}'. Please make sure ${packageManager} is installed on your system.`
721
1221
  );
722
1222
  }
1223
+ logVerbose("Steps.getPackageManager resolved", { packageManager });
723
1224
  return { packageManager };
724
1225
  },
725
1226
  async ensureReady(type) {
@@ -741,49 +1242,70 @@ ${projectInfo.indentation}return <></>;
741
1242
  if (!isReady) {
742
1243
  throw new UserError("Installation aborted.");
743
1244
  }
1245
+ logVerbose("Steps.ensureReady confirmed", { type, projectPath, isReady });
744
1246
  },
745
1247
  async getServerOrClientOrBoth() {
746
- if (isClient && isServer) return ["server", "client"];
747
- if (isServer) return ["server"];
748
- if (isClient) return ["client"];
749
- if (onQuestionMode === "guess") return ["server", "client"];
1248
+ logVerbose("Steps.getServerOrClientOrBoth invoked", { isClientFlag: isClient, isServerFlag: isServer, onQuestionMode });
1249
+ if (isClient && isServer) {
1250
+ logVerbose("Steps.getServerOrClientOrBoth using CLI flags", { selection: ["server", "client"] });
1251
+ return ["server", "client"];
1252
+ }
1253
+ if (isServer) {
1254
+ logVerbose("Steps.getServerOrClientOrBoth using server flag");
1255
+ return ["server"];
1256
+ }
1257
+ if (isClient) {
1258
+ logVerbose("Steps.getServerOrClientOrBoth using client flag");
1259
+ return ["client"];
1260
+ }
1261
+ if (onQuestionMode === "guess") {
1262
+ logVerbose("Steps.getServerOrClientOrBoth defaulting to both");
1263
+ return ["server", "client"];
1264
+ }
750
1265
  if (onQuestionMode === "error") {
751
1266
  throw new UnansweredQuestionError("Ambiguous installation type. Re-run with --server, --client, or both.");
752
1267
  }
753
- return (await inquirer.prompt([{
1268
+ const selection = (await inquirer.prompt([{
754
1269
  type: "list",
755
1270
  name: "type",
756
1271
  message: "Do you want to use Stack Auth on the server, or on the client?",
757
1272
  choices: [
758
- { name: "Client (eg. Vite, HTML)", value: ["client"] },
759
- { name: "Server (eg. Node.js)", value: ["server"] },
760
- { name: "Both", value: ["server", "client"] }
1273
+ { name: "Client (e.g. Vite, HTML)", value: ["client"] },
1274
+ { name: "Server (e.g. Node.js)", value: ["server"] },
1275
+ { name: "Both (e.g. Next.js)", value: ["server", "client"] }
761
1276
  ]
762
1277
  }])).type;
1278
+ logVerbose("Steps.getServerOrClientOrBoth received user selection", { selection });
1279
+ return selection;
763
1280
  },
764
1281
  /**
765
- * note: this is a heuristic, specific frameworks may have better heuristics (eg. the Next.js code uses the extension of the global layout file)
766
- */
1282
+ * note: this is a heuristic, specific frameworks may have better heuristics (e.g. the Next.js code uses the extension of the global layout file)
1283
+ */
767
1284
  async guessDefaultFileExtension() {
768
1285
  const projectPath = await getProjectPath();
769
- const hasTsConfig = fs.existsSync(
770
- path.join(projectPath, "tsconfig.json")
1286
+ const hasTsConfig = fs3.existsSync(
1287
+ path3.join(projectPath, "tsconfig.json")
771
1288
  );
772
- return hasTsConfig ? "ts" : "js";
1289
+ const extension = hasTsConfig ? "ts" : "js";
1290
+ logVerbose("Steps.guessDefaultFileExtension result", { projectPath, hasTsConfig, extension });
1291
+ return extension;
773
1292
  },
774
1293
  /**
775
- * note: this is a heuristic, specific frameworks may have better heuristics (eg. the Next.js code uses the location of the app folder)
1294
+ * note: this is a heuristic, specific frameworks may have better heuristics (e.g. the Next.js code uses the location of the app folder)
776
1295
  */
777
1296
  async guessSrcPath() {
778
1297
  const projectPath = await getProjectPath();
779
- const potentialSrcPath = path.join(projectPath, "src");
780
- const hasSrcFolder = fs.existsSync(
781
- path.join(projectPath, "src")
1298
+ const potentialSrcPath = path3.join(projectPath, "src");
1299
+ const hasSrcFolder = fs3.existsSync(
1300
+ path3.join(projectPath, "src")
782
1301
  );
783
- return hasSrcFolder ? potentialSrcPath : projectPath;
1302
+ const resolvedPath = hasSrcFolder ? potentialSrcPath : projectPath;
1303
+ logVerbose("Steps.guessSrcPath result", { hasSrcFolder, resolvedPath });
1304
+ return resolvedPath;
784
1305
  }
785
1306
  };
786
1307
  async function getUpdatedLayout(originalLayout) {
1308
+ logVerbose("getUpdatedLayout invoked", { length: originalLayout.length });
787
1309
  let layout = originalLayout;
788
1310
  const indentation = guessIndentation(originalLayout);
789
1311
  const firstImportLocationM1 = /\simport\s/.exec(layout)?.index;
@@ -797,11 +1319,13 @@ import { stackClientApp } from "../stack/client";
797
1319
  const bodyOpenTag = /<\s*body[^>]*>/.exec(layout);
798
1320
  const bodyCloseTag = /<\s*\/\s*body[^>]*>/.exec(layout);
799
1321
  if (!bodyOpenTag || !bodyCloseTag) {
1322
+ logVerbose("getUpdatedLayout missing body tag");
800
1323
  return void 0;
801
1324
  }
802
1325
  const bodyOpenEndIndex = bodyOpenTag.index + bodyOpenTag[0].length;
803
1326
  const bodyCloseStartIndex = bodyCloseTag.index;
804
1327
  if (bodyCloseStartIndex <= bodyOpenEndIndex) {
1328
+ logVerbose("getUpdatedLayout invalid body indices", { bodyOpenEndIndex, bodyCloseStartIndex });
805
1329
  return void 0;
806
1330
  }
807
1331
  const lines = layout.split("\n");
@@ -817,6 +1341,7 @@ import { stackClientApp } from "../stack/client";
817
1341
  const insertClose = "</StackTheme></StackProvider>";
818
1342
  layout = layout.slice(0, bodyCloseStartIndex) + insertClose + layout.slice(bodyCloseStartIndex);
819
1343
  layout = layout.slice(0, bodyOpenEndIndex) + insertOpen + layout.slice(bodyOpenEndIndex);
1344
+ logVerbose("getUpdatedLayout success", { updatedLength: layout.length });
820
1345
  return {
821
1346
  content: `${layout}`,
822
1347
  indentation
@@ -847,12 +1372,14 @@ function getLineIndex(lines, stringIndex) {
847
1372
  );
848
1373
  }
849
1374
  async function getProjectPath() {
1375
+ logVerbose("getProjectPath invoked", { savedProjectPath });
850
1376
  if (savedProjectPath === void 0) {
851
1377
  savedProjectPath = process.cwd();
852
- const askForPathModification = !fs.existsSync(
853
- path.join(savedProjectPath, "package.json")
1378
+ const askForPathModification = !fs3.existsSync(
1379
+ path3.join(savedProjectPath, "package.json")
854
1380
  );
855
1381
  if (askForPathModification) {
1382
+ logVerbose("getProjectPath did not find package.json in cwd", { cwd: savedProjectPath });
856
1383
  if (onQuestionMode === "guess" || onQuestionMode === "error") {
857
1384
  throw new UserError(`No package.json file found in ${savedProjectPath}. Re-run providing the project path argument (e.g. 'init-stack <project-path>').`);
858
1385
  }
@@ -864,35 +1391,136 @@ async function getProjectPath() {
864
1391
  default: "."
865
1392
  }
866
1393
  ])).newPath;
1394
+ logVerbose("getProjectPath received manual input", { savedProjectPath });
867
1395
  }
868
1396
  }
1397
+ logVerbose("getProjectPath resolved", { savedProjectPath });
869
1398
  return savedProjectPath;
870
1399
  }
1400
+ async function ensureGitWorkspaceIsReady(projectPath) {
1401
+ if (!warnUncommittedChanges) {
1402
+ logVerbose("ensureGitWorkspaceIsReady skipped as requested by user");
1403
+ return;
1404
+ }
1405
+ logVerbose("ensureGitWorkspaceIsReady invoked", { projectPath });
1406
+ let isGitRepo = false;
1407
+ try {
1408
+ const gitRepoResult = child_process2.spawnSync(
1409
+ "git",
1410
+ ["rev-parse", "--is-inside-work-tree"],
1411
+ {
1412
+ shell: true,
1413
+ cwd: projectPath,
1414
+ encoding: "utf8",
1415
+ stdio: ["ignore", "pipe", "ignore"]
1416
+ }
1417
+ );
1418
+ isGitRepo = gitRepoResult.status === 0 && gitRepoResult.stdout.trim() === "true";
1419
+ } catch (e) {
1420
+ logVerbose("ensureGitWorkspaceIsReady failed to detect git repository", { error: e });
1421
+ return;
1422
+ }
1423
+ if (!isGitRepo) {
1424
+ logVerbose("ensureGitWorkspaceIsReady skipping", { reason: "not-a-git-repo" });
1425
+ return;
1426
+ }
1427
+ const statusResult = child_process2.spawnSync(
1428
+ "git",
1429
+ ["status", "--porcelain"],
1430
+ {
1431
+ shell: true,
1432
+ cwd: projectPath,
1433
+ encoding: "utf8",
1434
+ stdio: ["ignore", "pipe", "pipe"]
1435
+ }
1436
+ );
1437
+ if (statusResult.error || statusResult.status !== 0) {
1438
+ logVerbose("ensureGitWorkspaceIsReady git status failed", { status: statusResult.status, error: statusResult.error });
1439
+ return;
1440
+ }
1441
+ const lines = statusResult.stdout.split("\n").map((line) => line.replace(/\r$/, "")).filter((line) => line.length > 0);
1442
+ const unstagedLines = lines.filter((line) => {
1443
+ if (line.startsWith("!!")) return false;
1444
+ if (line.startsWith("??")) return true;
1445
+ if (line.length < 2) return false;
1446
+ const workingTreeStatus = line[1];
1447
+ return Boolean(workingTreeStatus && workingTreeStatus !== " ");
1448
+ });
1449
+ if (unstagedLines.length === 0) {
1450
+ logVerbose("ensureGitWorkspaceIsReady clean working tree");
1451
+ return;
1452
+ }
1453
+ const changedFiles = unstagedLines.map((line) => {
1454
+ const filePath = line.slice(3).trim();
1455
+ return filePath.length > 0 ? filePath : line;
1456
+ });
1457
+ console.log();
1458
+ console.log(colorize.yellow`Detected unstaged/uncommitted changes in your Git repository:`);
1459
+ const filesToShow = changedFiles.slice(0, 10);
1460
+ for (const file of filesToShow) {
1461
+ console.log(` - ${file}`);
1462
+ }
1463
+ if (changedFiles.length > filesToShow.length) {
1464
+ console.log(` - ...and ${changedFiles.length - filesToShow.length} more`);
1465
+ }
1466
+ console.log(colorize.yellow`You may want to stage and commit these changes before installing Stack Auth, so you can review the changes afterwards.`);
1467
+ console.log();
1468
+ if (onQuestionMode === "guess") {
1469
+ console.log(colorize.yellow`Continuing because --on-question=guess.`);
1470
+ return;
1471
+ }
1472
+ if (onQuestionMode === "error") {
1473
+ throw new UnansweredQuestionError("Unstaged changes detected in the project directory");
1474
+ }
1475
+ const { proceed } = await inquirer.prompt([
1476
+ {
1477
+ type: "confirm",
1478
+ name: "proceed",
1479
+ message: "Continue with Stack initialization anyway?",
1480
+ default: false
1481
+ }
1482
+ ]);
1483
+ if (!proceed) {
1484
+ throw new UserError("Aborting Stack initialization to avoid overwriting unstaged changes.");
1485
+ }
1486
+ logVerbose("ensureGitWorkspaceIsReady user confirmed proceed despite unstaged changes");
1487
+ }
871
1488
  async function findJsExtension(fullPathWithoutExtension) {
1489
+ logVerbose("findJsExtension invoked", { fullPathWithoutExtension });
872
1490
  for (const ext of jsLikeFileExtensions) {
873
1491
  const fullPath = fullPathWithoutExtension + "." + ext;
874
- if (fs.existsSync(fullPath)) {
1492
+ if (fs3.existsSync(fullPath)) {
1493
+ logVerbose("findJsExtension found file", { fullPath, ext });
875
1494
  return ext;
876
1495
  }
877
1496
  }
1497
+ logVerbose("findJsExtension no matching file", { fullPathWithoutExtension });
878
1498
  return null;
879
1499
  }
880
1500
  async function promptPackageManager() {
881
1501
  const projectPath = await getProjectPath();
882
- const yarnLock = fs.existsSync(path.join(projectPath, "yarn.lock"));
883
- const pnpmLock = fs.existsSync(path.join(projectPath, "pnpm-lock.yaml"));
884
- const npmLock = fs.existsSync(path.join(projectPath, "package-lock.json"));
885
- const bunLock = fs.existsSync(path.join(projectPath, "bun.lockb")) || fs.existsSync(path.join(projectPath, "bun.lock"));
1502
+ const yarnLock = fs3.existsSync(path3.join(projectPath, "yarn.lock"));
1503
+ const pnpmLock = fs3.existsSync(path3.join(projectPath, "pnpm-lock.yaml"));
1504
+ const npmLock = fs3.existsSync(path3.join(projectPath, "package-lock.json"));
1505
+ const bunLock = fs3.existsSync(path3.join(projectPath, "bun.lockb")) || fs3.existsSync(path3.join(projectPath, "bun.lock"));
1506
+ logVerbose("promptPackageManager inspecting lockfiles", { yarnLock, pnpmLock, npmLock, bunLock });
886
1507
  if (yarnLock && !pnpmLock && !npmLock && !bunLock) {
1508
+ logVerbose("promptPackageManager auto-selected yarn");
887
1509
  return "yarn";
888
1510
  } else if (!yarnLock && pnpmLock && !npmLock && !bunLock) {
1511
+ logVerbose("promptPackageManager auto-selected pnpm");
889
1512
  return "pnpm";
890
1513
  } else if (!yarnLock && !pnpmLock && npmLock && !bunLock) {
1514
+ logVerbose("promptPackageManager auto-selected npm");
891
1515
  return "npm";
892
1516
  } else if (!yarnLock && !pnpmLock && !npmLock && bunLock) {
1517
+ logVerbose("promptPackageManager auto-selected bun");
893
1518
  return "bun";
894
1519
  }
895
- if (onQuestionMode === "guess") return "npm";
1520
+ if (onQuestionMode === "guess") {
1521
+ logVerbose("promptPackageManager defaulting to npm due to guess mode");
1522
+ return "npm";
1523
+ }
896
1524
  if (onQuestionMode === "error") {
897
1525
  throw new UnansweredQuestionError("Unable to determine the package manager. Re-run with one of: --npm, --yarn, --pnpm, or --bun.");
898
1526
  }
@@ -904,9 +1532,11 @@ async function promptPackageManager() {
904
1532
  choices: ["npm", "yarn", "pnpm", "bun"]
905
1533
  }
906
1534
  ]);
1535
+ logVerbose("promptPackageManager user selected", { packageManager: answers.packageManager });
907
1536
  return answers.packageManager;
908
1537
  }
909
1538
  async function shellNicelyFormatted(command, { quiet, ...options2 }) {
1539
+ logVerbose("shellNicelyFormatted invoked", { command, options: { ...options2, quiet } });
910
1540
  let ui;
911
1541
  let interval;
912
1542
  if (!quiet) {
@@ -926,22 +1556,25 @@ async function shellNicelyFormatted(command, { quiet, ...options2 }) {
926
1556
  }
927
1557
  try {
928
1558
  if (!isDryRun) {
929
- const child = child_process.spawn(command, options2);
1559
+ const child = child_process2.spawn(command, options2);
1560
+ logVerbose("shellNicelyFormatted spawned process", { pid: child.pid, command });
930
1561
  if (!quiet) {
931
1562
  child.stdout.pipe(ui.log);
932
1563
  child.stderr.pipe(ui.log);
933
1564
  }
934
- await new Promise((resolve, reject) => {
1565
+ await new Promise((resolve3, reject) => {
935
1566
  child.on("exit", (code) => {
936
1567
  if (code === 0) {
937
- resolve();
1568
+ resolve3();
938
1569
  } else {
1570
+ logVerbose("shellNicelyFormatted command failed", { code });
939
1571
  reject(new Error(`Command ${command} failed with code ${code}`));
940
1572
  }
941
1573
  });
942
1574
  });
943
1575
  } else {
944
1576
  console.log(`[DRY-RUN] Would have run: ${command}`);
1577
+ logVerbose("shellNicelyFormatted skipped due to dry run", { command });
945
1578
  }
946
1579
  if (!quiet) {
947
1580
  commandsExecuted.push(command);
@@ -950,7 +1583,9 @@ async function shellNicelyFormatted(command, { quiet, ...options2 }) {
950
1583
  `
951
1584
  );
952
1585
  }
1586
+ logVerbose("shellNicelyFormatted completed", { command });
953
1587
  } catch (e) {
1588
+ logVerbose("shellNicelyFormatted encountered error", { command, error: e instanceof Error ? { message: e.message, stack: e.stack } : e });
954
1589
  if (!quiet) {
955
1590
  ui.updateBottomBar(
956
1591
  `${colorize.red`X`} Command ${command} failed
@@ -968,48 +1603,116 @@ async function shellNicelyFormatted(command, { quiet, ...options2 }) {
968
1603
  }
969
1604
  }
970
1605
  async function readFile(fullPath) {
1606
+ logVerbose("readFile invoked", { fullPath, isDryRun });
971
1607
  try {
972
1608
  if (!isDryRun) {
973
- return fs.readFileSync(fullPath, "utf-8");
1609
+ const content = fs3.readFileSync(fullPath, "utf-8");
1610
+ logVerbose("readFile succeeded", { fullPath, length: content.length });
1611
+ return content;
974
1612
  }
1613
+ logVerbose("readFile skipped due to dry run", { fullPath });
975
1614
  return null;
976
1615
  } catch (err) {
977
1616
  if (err.code === "ENOENT") {
1617
+ logVerbose("readFile file missing", { fullPath });
978
1618
  return null;
979
1619
  }
1620
+ logVerbose("readFile errored", { fullPath, error: err instanceof Error ? { message: err.message, stack: err.stack } : err });
980
1621
  throw err;
981
1622
  }
982
1623
  }
983
1624
  async function writeFile(fullPath, content) {
984
- let create = !fs.existsSync(fullPath);
1625
+ logVerbose("writeFile invoked", { fullPath, length: content.length, isDryRun });
1626
+ let create = !fs3.existsSync(fullPath);
985
1627
  if (!isDryRun) {
986
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
987
- fs.writeFileSync(fullPath, content);
1628
+ fs3.mkdirSync(path3.dirname(fullPath), { recursive: true });
1629
+ fs3.writeFileSync(fullPath, content);
1630
+ logVerbose("writeFile wrote to disk", { fullPath, created: create });
988
1631
  } else {
989
1632
  console.log(`[DRY-RUN] Would have written to ${fullPath}`);
1633
+ logVerbose("writeFile skipped due to dry run", { fullPath });
990
1634
  }
991
- const relativeToProjectPath = path.relative(await getProjectPath(), fullPath);
1635
+ const relativeToProjectPath = path3.relative(await getProjectPath(), fullPath);
992
1636
  if (!create) {
993
1637
  filesModified.push(relativeToProjectPath);
994
1638
  } else {
995
1639
  filesCreated.push(relativeToProjectPath);
996
1640
  }
1641
+ logVerbose("writeFile recorded change", { relativeToProjectPath, created: create });
997
1642
  }
998
1643
  function laterWriteFile(fullPath, content) {
1644
+ logVerbose("laterWriteFile scheduled", { fullPath, length: content.length });
999
1645
  writeFileHandlers.push(async () => {
1000
1646
  await writeFile(fullPath, content);
1001
1647
  });
1002
1648
  }
1003
1649
  async function writeFileIfNotExists(fullPath, content) {
1004
- if (!fs.existsSync(fullPath)) {
1650
+ if (!fs3.existsSync(fullPath)) {
1651
+ logVerbose("writeFileIfNotExists writing new file", { fullPath });
1005
1652
  await writeFile(fullPath, content);
1653
+ } else {
1654
+ logVerbose("writeFileIfNotExists skipped", { fullPath });
1006
1655
  }
1007
1656
  }
1008
1657
  function laterWriteFileIfNotExists(fullPath, content) {
1658
+ logVerbose("laterWriteFileIfNotExists scheduled", { fullPath });
1009
1659
  writeFileHandlers.push(async () => {
1010
1660
  await writeFileIfNotExists(fullPath, content);
1011
1661
  });
1012
1662
  }
1663
+ async function runDeferredCommands() {
1664
+ if (!deferredCommandHandlers.length) {
1665
+ logVerbose("runDeferredCommands skipped", { reason: "no-handlers" });
1666
+ return;
1667
+ }
1668
+ logVerbose("runDeferredCommands executing handlers", { count: deferredCommandHandlers.length });
1669
+ for (let index = 0; index < deferredCommandHandlers.length; index++) {
1670
+ logVerbose("runDeferredCommands executing handler", { index });
1671
+ const handler = deferredCommandHandlers[index];
1672
+ await handler();
1673
+ }
1674
+ logVerbose("runDeferredCommands completed");
1675
+ }
1676
+ async function runScheduledCommand(command, args, options2 = {}, metadata = {}) {
1677
+ logVerbose("runScheduledCommand invoked", { command, args, options: options2, isDryRun });
1678
+ const display = [command, ...args].join(" ");
1679
+ if (isDryRun) {
1680
+ console.log(`[DRY-RUN] Would run: ${display}`);
1681
+ logVerbose("runScheduledCommand skipped due to dry run", { display });
1682
+ return;
1683
+ }
1684
+ const result = child_process2.spawnSync(command, args, {
1685
+ stdio: "pipe",
1686
+ ...options2
1687
+ });
1688
+ const recordInCommandsExecuted = metadata.recordInCommandsExecuted;
1689
+ if (recordInCommandsExecuted && !commandsExecuted.includes(display)) {
1690
+ commandsExecuted.push(display);
1691
+ }
1692
+ if (result.status === 0) {
1693
+ console.log(`${colorize.green`√`} ${display}`);
1694
+ logVerbose("runScheduledCommand succeeded", { display });
1695
+ } else {
1696
+ logVerbose("runScheduledCommand failed", { display, result, stderr: result.stderr.toString(), stdout: result.stdout.toString() });
1697
+ throw new Error(`Command ${display} failed with status ${result.status}: ${result.stderr.toString()}`);
1698
+ }
1699
+ }
1700
+ async function recordFileChange(fullPath, existed) {
1701
+ logVerbose("recordFileChange invoked", { fullPath, existed });
1702
+ const projectRoot = path3.resolve(await getProjectPath());
1703
+ const relative2 = path3.relative(projectRoot, fullPath);
1704
+ const insideProject = relative2 && !relative2.startsWith("..") && !path3.isAbsolute(relative2);
1705
+ const entry = insideProject ? relative2 : fullPath;
1706
+ if (existed) {
1707
+ if (!filesModified.includes(entry)) {
1708
+ filesModified.push(entry);
1709
+ }
1710
+ logVerbose("recordFileChange marked modified", { entry });
1711
+ } else if (!filesCreated.includes(entry)) {
1712
+ filesCreated.push(entry);
1713
+ logVerbose("recordFileChange marked created", { entry });
1714
+ }
1715
+ }
1013
1716
  function createConvexAuthConfigContent(options2) {
1014
1717
  const envVarName = getPublicProjectEnvVarName(options2.type);
1015
1718
  return `import { getConvexProvidersConfig } from ${JSON.stringify(options2.stackPackageName)};
@@ -1109,13 +1812,22 @@ function getPublicProjectEnvVarName(type) {
1109
1812
  }
1110
1813
  async function updateConvexClients({ projectPath, type }) {
1111
1814
  const files = collectConvexClientCandidateFiles(projectPath);
1815
+ logVerbose("updateConvexClients collected files", { projectPath, count: files.length });
1112
1816
  const updatedFiles = [];
1113
1817
  const skippedFiles = [];
1114
1818
  for (const filePath of files) {
1819
+ logVerbose("updateConvexClients inspecting file", { filePath });
1115
1820
  const fileContent = await readFile(filePath);
1116
- if (!fileContent) continue;
1117
- if (!/new\s+Convex(?:React|Http)?Client\b/.test(fileContent)) continue;
1821
+ if (!fileContent) {
1822
+ logVerbose("updateConvexClients skipped file (no content)", { filePath });
1823
+ continue;
1824
+ }
1825
+ if (!/new\s+Convex(?:React|Http)?Client\b/.test(fileContent)) {
1826
+ logVerbose("updateConvexClients skipped file (no Convex client)", { filePath });
1827
+ continue;
1828
+ }
1118
1829
  const addResult = addSetAuthToConvexClients(fileContent, type);
1830
+ logVerbose("updateConvexClients processed file", { filePath, addResult });
1119
1831
  if (!addResult.changed) {
1120
1832
  if (addResult.instantiationCount > 0 && addResult.skippedHttpCount > 0) {
1121
1833
  skippedFiles.push(filePath);
@@ -1124,28 +1836,34 @@ async function updateConvexClients({ projectPath, type }) {
1124
1836
  }
1125
1837
  let finalContent = addResult.updatedContent;
1126
1838
  if (addResult.usedClientApp) {
1839
+ logVerbose("updateConvexClients ensuring client import", { filePath });
1127
1840
  finalContent = await ensureStackAppImport(finalContent, filePath, "client");
1128
1841
  }
1129
1842
  if (addResult.usedServerApp) {
1843
+ logVerbose("updateConvexClients ensuring server import", { filePath });
1130
1844
  finalContent = await ensureStackAppImport(finalContent, filePath, "server");
1131
1845
  }
1132
1846
  if (finalContent !== fileContent) {
1133
1847
  laterWriteFile(filePath, finalContent);
1134
1848
  updatedFiles.push(filePath);
1849
+ logVerbose("updateConvexClients scheduled update", { filePath });
1135
1850
  }
1136
1851
  }
1852
+ logVerbose("updateConvexClients finished", { updatedFiles, skippedFiles });
1137
1853
  return {
1138
1854
  updatedFiles,
1139
1855
  skippedFiles
1140
1856
  };
1141
1857
  }
1142
1858
  async function ensureStackAppImport(content, filePath, kind) {
1859
+ logVerbose("ensureStackAppImport invoked", { filePath, kind });
1143
1860
  const identifier = kind === "client" ? "stackClientApp" : "stackServerApp";
1144
1861
  if (new RegExp(`import\\s+[^;]*\\b${identifier}\\b`).test(content)) {
1862
+ logVerbose("ensureStackAppImport found existing import", { filePath, identifier });
1145
1863
  return content;
1146
1864
  }
1147
1865
  const stackBasePath = await getStackAppBasePath(kind);
1148
- const relativeImportPath = convertToModuleSpecifier(path.relative(path.dirname(filePath), stackBasePath));
1866
+ const relativeImportPath = convertToModuleSpecifier(path3.relative(path3.dirname(filePath), stackBasePath));
1149
1867
  const newline = content.includes("\r\n") ? "\r\n" : "\n";
1150
1868
  const lines = content.split(/\r?\n/);
1151
1869
  const importLine = `import { ${identifier} } from "${relativeImportPath}";`;
@@ -1171,6 +1889,7 @@ async function ensureStackAppImport(content, filePath, kind) {
1171
1889
  if (nextLine && nextLine.trim() !== "" && !/^\s*import\b/.test(nextLine)) {
1172
1890
  lines.splice(insertIndex + 1, 0, "");
1173
1891
  }
1892
+ logVerbose("ensureStackAppImport added import", { filePath, importLine });
1174
1893
  return lines.join(newline);
1175
1894
  }
1176
1895
  function convertToModuleSpecifier(relativePath) {
@@ -1182,9 +1901,12 @@ function convertToModuleSpecifier(relativePath) {
1182
1901
  }
1183
1902
  async function getStackAppBasePath(kind) {
1184
1903
  const srcPath = await Steps.guessSrcPath();
1185
- return path.join(srcPath, "stack", kind);
1904
+ const basePath = path3.join(srcPath, "stack", kind);
1905
+ logVerbose("getStackAppBasePath resolved", { kind, basePath });
1906
+ return basePath;
1186
1907
  }
1187
1908
  function addSetAuthToConvexClients(content, type) {
1909
+ logVerbose("addSetAuthToConvexClients invoked", { type, length: content.length });
1188
1910
  const newline = content.includes("\r\n") ? "\r\n" : "\n";
1189
1911
  const instantiationRegex = /^[ \t]*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*new\s+(Convex(?:React|Http)?Client)\b([\s\S]*?);/gm;
1190
1912
  const replacements = [];
@@ -1200,11 +1922,13 @@ function addSetAuthToConvexClients(content, type) {
1200
1922
  const className = match[2];
1201
1923
  if (className === "ConvexHttpClient") {
1202
1924
  skippedHttpCount += 1;
1925
+ logVerbose("addSetAuthToConvexClients skipping ConvexHttpClient", { variableName, fileLength: content.length });
1203
1926
  continue;
1204
1927
  }
1205
1928
  const remainder = content.slice(match.index + fullMatch.length);
1206
1929
  const setAuthRegex = new RegExp(`\\b${escapeRegExp(variableName)}\\s*\\.setAuth\\s*\\(`);
1207
1930
  if (setAuthRegex.test(remainder)) {
1931
+ logVerbose("addSetAuthToConvexClients found existing setAuth", { variableName });
1208
1932
  continue;
1209
1933
  }
1210
1934
  const indentation = fullMatch.match(/^[\t ]*/)?.[0] ?? "";
@@ -1220,8 +1944,10 @@ function addSetAuthToConvexClients(content, type) {
1220
1944
  end: match.index + fullMatch.length,
1221
1945
  text: replacementText
1222
1946
  });
1947
+ logVerbose("addSetAuthToConvexClients queued replacement", { variableName, authCall });
1223
1948
  }
1224
1949
  if (replacements.length === 0) {
1950
+ logVerbose("addSetAuthToConvexClients no replacements", { instantiationCount, skippedHttpCount });
1225
1951
  return {
1226
1952
  updatedContent: content,
1227
1953
  changed: false,
@@ -1236,6 +1962,8 @@ function addSetAuthToConvexClients(content, type) {
1236
1962
  const replacement = replacements[i];
1237
1963
  updatedContent = `${updatedContent.slice(0, replacement.start)}${replacement.text}${updatedContent.slice(replacement.end)}`;
1238
1964
  }
1965
+ logVerbose("addSetAuthToConvexClients completed replacements", { replacements: replacements.length });
1966
+ logVerbose("addSetAuthToConvexClients result", { changed: true, instantiationCount, skippedHttpCount, usedClientApp, usedServerApp });
1239
1967
  return {
1240
1968
  updatedContent,
1241
1969
  changed: true,
@@ -1248,41 +1976,57 @@ function addSetAuthToConvexClients(content, type) {
1248
1976
  function determineAuthCallExpression({ type, className, content }) {
1249
1977
  const hasClientAppReference = /\bstackClientApp\b/.test(content);
1250
1978
  const hasServerAppReference = /\bstackServerApp\b/.test(content);
1979
+ logVerbose("determineAuthCallExpression context", { type, className, hasClientAppReference, hasServerAppReference });
1251
1980
  if (type === "js") {
1252
- return { expression: "stackServerApp.getConvexClientAuth({})", identifier: "stackServerApp" };
1981
+ const result = { expression: "stackServerApp.getConvexClientAuth({})", identifier: "stackServerApp" };
1982
+ logVerbose("determineAuthCallExpression returning for JS", result);
1983
+ return result;
1253
1984
  }
1254
1985
  if (hasClientAppReference) {
1255
- return { expression: getClientAuthCall(type), identifier: "stackClientApp" };
1986
+ const result = { expression: getClientAuthCall(type), identifier: "stackClientApp" };
1987
+ logVerbose("determineAuthCallExpression using client reference", result);
1988
+ return result;
1256
1989
  }
1257
1990
  if (hasServerAppReference && className !== "ConvexReactClient") {
1258
- return { expression: "stackServerApp.getConvexClientAuth({})", identifier: "stackServerApp" };
1991
+ const result = { expression: "stackServerApp.getConvexClientAuth({})", identifier: "stackServerApp" };
1992
+ logVerbose("determineAuthCallExpression using server reference", result);
1993
+ return result;
1259
1994
  }
1260
- return { expression: getClientAuthCall(type), identifier: "stackClientApp" };
1995
+ const fallback = { expression: getClientAuthCall(type), identifier: "stackClientApp" };
1996
+ logVerbose("determineAuthCallExpression fallback", fallback);
1997
+ return fallback;
1261
1998
  }
1262
1999
  function getClientAuthCall(type) {
2000
+ logVerbose("getClientAuthCall invoked", { type });
1263
2001
  return "stackClientApp.getConvexClientAuth({})";
1264
2002
  }
1265
2003
  function collectConvexClientCandidateFiles(projectPath) {
2004
+ logVerbose("collectConvexClientCandidateFiles invoked", { projectPath });
1266
2005
  const roots = getConvexSearchRoots(projectPath);
2006
+ logVerbose("collectConvexClientCandidateFiles roots", { roots });
1267
2007
  const files = /* @__PURE__ */ new Set();
1268
2008
  const visited = /* @__PURE__ */ new Set();
1269
2009
  for (const root of roots) {
1270
2010
  walkDirectory(root, files, visited);
1271
2011
  }
1272
- return Array.from(files);
2012
+ const result = Array.from(files);
2013
+ logVerbose("collectConvexClientCandidateFiles result", { count: result.length });
2014
+ return result;
1273
2015
  }
1274
2016
  function getConvexSearchRoots(projectPath) {
1275
2017
  const candidateDirs = ["convex", "src", "app", "components"];
1276
- const existing = candidateDirs.map((dir) => path.join(projectPath, dir)).filter((dirPath) => {
2018
+ const existing = candidateDirs.map((dir) => path3.join(projectPath, dir)).filter((dirPath) => {
1277
2019
  try {
1278
- return fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory();
2020
+ return fs3.existsSync(dirPath) && fs3.statSync(dirPath).isDirectory();
1279
2021
  } catch {
1280
2022
  return false;
1281
2023
  }
1282
2024
  });
1283
2025
  if (existing.length > 0) {
2026
+ logVerbose("getConvexSearchRoots using existing directories", { existing });
1284
2027
  return existing;
1285
2028
  }
2029
+ logVerbose("getConvexSearchRoots defaulting to project root", { projectPath });
1286
2030
  return [projectPath];
1287
2031
  }
1288
2032
  var directorySkipList = /* @__PURE__ */ new Set([
@@ -1302,31 +2046,40 @@ var directorySkipList = /* @__PURE__ */ new Set([
1302
2046
  function walkDirectory(currentDir, files, visited) {
1303
2047
  const realPath = (() => {
1304
2048
  try {
1305
- return fs.realpathSync(currentDir);
2049
+ return fs3.realpathSync(currentDir);
1306
2050
  } catch {
1307
2051
  return currentDir;
1308
2052
  }
1309
2053
  })();
1310
2054
  if (visited.has(realPath)) return;
1311
2055
  visited.add(realPath);
2056
+ logVerbose("walkDirectory scanning", { currentDir: realPath });
1312
2057
  let dirEntries;
1313
2058
  try {
1314
- dirEntries = fs.readdirSync(realPath, { withFileTypes: true });
2059
+ dirEntries = fs3.readdirSync(realPath, { withFileTypes: true });
1315
2060
  } catch {
1316
2061
  return;
1317
2062
  }
1318
2063
  for (const entry of dirEntries) {
1319
2064
  const entryName = entry.name;
1320
2065
  if (entry.isDirectory()) {
1321
- if (directorySkipList.has(entryName)) continue;
1322
- if (entryName.startsWith(".") || entryName.startsWith("_")) continue;
1323
- walkDirectory(path.join(realPath, entryName), files, visited);
2066
+ if (directorySkipList.has(entryName)) {
2067
+ logVerbose("walkDirectory skipping directory in skip list", { directory: entryName, parent: realPath });
2068
+ continue;
2069
+ }
2070
+ if (entryName.startsWith(".") || entryName.startsWith("_")) {
2071
+ logVerbose("walkDirectory skipping hidden directory", { directory: entryName, parent: realPath });
2072
+ continue;
2073
+ }
2074
+ walkDirectory(path3.join(realPath, entryName), files, visited);
1324
2075
  continue;
1325
2076
  }
1326
2077
  if (!entry.isFile()) continue;
1327
2078
  if (entryName.endsWith(".d.ts")) continue;
1328
2079
  if (!hasJsLikeExtension(entryName)) continue;
1329
- files.add(path.join(realPath, entryName));
2080
+ const filePath = path3.join(realPath, entryName);
2081
+ files.add(filePath);
2082
+ logVerbose("walkDirectory added file", { filePath });
1330
2083
  }
1331
2084
  }
1332
2085
  function hasJsLikeExtension(fileName) {
@@ -1338,13 +2091,9 @@ function escapeRegExp(str) {
1338
2091
  function throwErr(message) {
1339
2092
  throw new Error(message);
1340
2093
  }
1341
- function templateIdentity(strings, ...values) {
1342
- if (strings.length === 0) return "";
1343
- if (values.length !== strings.length - 1) throw new Error("Invalid number of values; must be one less than strings");
1344
- return strings.slice(1).reduce((result, string, i) => `${result}${values[i] ?? "n/a"}${string}`, strings[0]);
1345
- }
1346
2094
  async function clearStdin() {
1347
- await new Promise((resolve) => {
2095
+ logVerbose("clearStdin invoked");
2096
+ await new Promise((resolve3) => {
1348
2097
  if (process.stdin.isTTY) {
1349
2098
  process.stdin.setRawMode(true);
1350
2099
  }
@@ -1356,10 +2105,12 @@ async function clearStdin() {
1356
2105
  if (process.stdin.isTTY) {
1357
2106
  process.stdin.setRawMode(false);
1358
2107
  }
1359
- resolve();
2108
+ logVerbose("clearStdin flushed");
2109
+ resolve3();
1360
2110
  };
1361
2111
  setTimeout(flush, 10);
1362
2112
  });
2113
+ logVerbose("clearStdin completed");
1363
2114
  }
1364
2115
  export {
1365
2116
  templateIdentity