@stackframe/init-stack 2.8.44 → 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/CHANGELOG.md +15 -0
- package/dist/index.js +862 -111
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
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
|
|
4
|
+
import * as child_process2 from "child_process";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import * as crypto from "crypto";
|
|
7
|
-
import * as
|
|
7
|
+
import * as fs3 from "fs";
|
|
8
8
|
import inquirer from "inquirer";
|
|
9
9
|
import open from "open";
|
|
10
|
-
import * as
|
|
11
|
-
import * as
|
|
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.
|
|
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
|
|
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:
|
|
207
|
-
arch:
|
|
597
|
+
platform: os2.platform(),
|
|
598
|
+
arch: os2.arch(),
|
|
208
599
|
nodeVersion: process.version
|
|
209
600
|
});
|
|
210
|
-
await new Promise((
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
385
|
-
if (!
|
|
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 =
|
|
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)
|
|
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: "
|
|
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
|
-
|
|
882
|
+
const packageName = mapping[type];
|
|
883
|
+
logVerbose("Steps.getStackPackageName resolved", { type, install, packageName });
|
|
884
|
+
return packageName;
|
|
435
885
|
},
|
|
436
886
|
async addStackPackage(type) {
|
|
437
|
-
|
|
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)
|
|
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 =
|
|
456
|
-
const srcPath =
|
|
457
|
-
const appPath =
|
|
458
|
-
if (!
|
|
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 =
|
|
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 (!
|
|
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
|
-
|
|
481
|
-
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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) => !
|
|
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 =
|
|
517
|
-
if (!
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
747
|
-
if (isServer)
|
|
748
|
-
|
|
749
|
-
|
|
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
|
-
|
|
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 (
|
|
759
|
-
{ name: "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 (
|
|
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 =
|
|
770
|
-
|
|
1286
|
+
const hasTsConfig = fs3.existsSync(
|
|
1287
|
+
path3.join(projectPath, "tsconfig.json")
|
|
771
1288
|
);
|
|
772
|
-
|
|
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 (
|
|
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 =
|
|
780
|
-
const hasSrcFolder =
|
|
781
|
-
|
|
1298
|
+
const potentialSrcPath = path3.join(projectPath, "src");
|
|
1299
|
+
const hasSrcFolder = fs3.existsSync(
|
|
1300
|
+
path3.join(projectPath, "src")
|
|
782
1301
|
);
|
|
783
|
-
|
|
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 = !
|
|
853
|
-
|
|
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 (
|
|
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 =
|
|
883
|
-
const pnpmLock =
|
|
884
|
-
const npmLock =
|
|
885
|
-
const bunLock =
|
|
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")
|
|
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 =
|
|
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((
|
|
1565
|
+
await new Promise((resolve3, reject) => {
|
|
935
1566
|
child.on("exit", (code) => {
|
|
936
1567
|
if (code === 0) {
|
|
937
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1625
|
+
logVerbose("writeFile invoked", { fullPath, length: content.length, isDryRun });
|
|
1626
|
+
let create = !fs3.existsSync(fullPath);
|
|
985
1627
|
if (!isDryRun) {
|
|
986
|
-
|
|
987
|
-
|
|
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 =
|
|
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 (!
|
|
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)
|
|
1117
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1991
|
+
const result = { expression: "stackServerApp.getConvexClientAuth({})", identifier: "stackServerApp" };
|
|
1992
|
+
logVerbose("determineAuthCallExpression using server reference", result);
|
|
1993
|
+
return result;
|
|
1259
1994
|
}
|
|
1260
|
-
|
|
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
|
-
|
|
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) =>
|
|
2018
|
+
const existing = candidateDirs.map((dir) => path3.join(projectPath, dir)).filter((dirPath) => {
|
|
1277
2019
|
try {
|
|
1278
|
-
return
|
|
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
|
|
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 =
|
|
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))
|
|
1322
|
-
|
|
1323
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|