@01.software/init 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-docs.js +1 -1
- package/dist/{chunk-SRLZ5OIV.js → chunk-3MXIG3ZL.js} +5 -5
- package/dist/chunk-3MXIG3ZL.js.map +1 -0
- package/dist/chunk-3RQE6YVO.js +821 -0
- package/dist/chunk-3RQE6YVO.js.map +1 -0
- package/dist/chunk-TBGKXE3Q.js +183 -0
- package/dist/chunk-TBGKXE3Q.js.map +1 -0
- package/dist/chunk-UA7WNT2F.js +118 -0
- package/dist/chunk-UA7WNT2F.js.map +1 -0
- package/dist/file-ops.js +13 -5
- package/dist/index.js +57 -842
- package/dist/index.js.map +1 -1
- package/dist/init.js +11 -0
- package/dist/init.js.map +1 -0
- package/dist/templates.js +5 -1
- package/package.json +4 -2
- package/dist/chunk-OEAQV63E.js +0 -120
- package/dist/chunk-OEAQV63E.js.map +0 -1
- package/dist/chunk-SRLZ5OIV.js.map +0 -1
- package/dist/chunk-VOEXMD2S.js +0 -61
- package/dist/chunk-VOEXMD2S.js.map +0 -1
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
fetchTenantContext,
|
|
4
|
+
generateClaudeMd,
|
|
5
|
+
getSkillFiles
|
|
6
|
+
} from "./chunk-3MXIG3ZL.js";
|
|
7
|
+
import {
|
|
8
|
+
chmodSecretFile,
|
|
9
|
+
readEnvValue,
|
|
10
|
+
replaceTomlMcpSection,
|
|
11
|
+
setEnvValue,
|
|
12
|
+
writeEnvFile,
|
|
13
|
+
writeSecretGlobalConfig
|
|
14
|
+
} from "./chunk-UA7WNT2F.js";
|
|
15
|
+
import {
|
|
16
|
+
CODEX_MCP_SECTION_MARKER,
|
|
17
|
+
getAnalyticsTemplate,
|
|
18
|
+
getClientTemplate,
|
|
19
|
+
getCodexMcpTomlSection,
|
|
20
|
+
getEnvContent,
|
|
21
|
+
getMcpConfigTemplate,
|
|
22
|
+
getMcpRootKey,
|
|
23
|
+
getMcpServerEntry,
|
|
24
|
+
getQueryProviderTemplate,
|
|
25
|
+
getServerTemplate
|
|
26
|
+
} from "./chunk-TBGKXE3Q.js";
|
|
27
|
+
|
|
28
|
+
// src/init.ts
|
|
29
|
+
import fs2 from "fs";
|
|
30
|
+
import path2 from "path";
|
|
31
|
+
import os from "os";
|
|
32
|
+
import { execSync } from "child_process";
|
|
33
|
+
import pc2 from "picocolors";
|
|
34
|
+
import prompts from "prompts";
|
|
35
|
+
|
|
36
|
+
// src/detect.ts
|
|
37
|
+
import fs from "fs";
|
|
38
|
+
import path from "path";
|
|
39
|
+
function detectProject(cwd) {
|
|
40
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
41
|
+
const hasPackageJson = fs.existsSync(pkgPath);
|
|
42
|
+
let env = "node";
|
|
43
|
+
let hasSdk = false;
|
|
44
|
+
let hasReactQuery = false;
|
|
45
|
+
let parseError = false;
|
|
46
|
+
if (hasPackageJson) {
|
|
47
|
+
let pkg;
|
|
48
|
+
try {
|
|
49
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
50
|
+
} catch {
|
|
51
|
+
return {
|
|
52
|
+
hasPackageJson: true,
|
|
53
|
+
parseError: true,
|
|
54
|
+
env: "node",
|
|
55
|
+
packageManager: null,
|
|
56
|
+
hasSdk: false,
|
|
57
|
+
hasReactQuery: false,
|
|
58
|
+
srcDir: false
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const deps = {
|
|
62
|
+
...pkg.dependencies || {},
|
|
63
|
+
...pkg.devDependencies || {}
|
|
64
|
+
};
|
|
65
|
+
hasSdk = "@01.software/sdk" in deps;
|
|
66
|
+
hasReactQuery = "@tanstack/react-query" in deps;
|
|
67
|
+
if ("next" in deps) {
|
|
68
|
+
env = "nextjs";
|
|
69
|
+
} else if ("astro" in deps || "@astrojs/node" in deps) {
|
|
70
|
+
env = "other";
|
|
71
|
+
} else if ("@remix-run/node" in deps || "@remix-run/react" in deps) {
|
|
72
|
+
env = "other";
|
|
73
|
+
} else if ("@sveltejs/kit" in deps) {
|
|
74
|
+
env = "other";
|
|
75
|
+
} else if ("react" in deps) {
|
|
76
|
+
if ("vite" in deps) {
|
|
77
|
+
env = "react-vite";
|
|
78
|
+
} else if ("react-scripts" in deps) {
|
|
79
|
+
env = "react-cra";
|
|
80
|
+
} else {
|
|
81
|
+
env = "node";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
let packageManager = null;
|
|
86
|
+
if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
|
|
87
|
+
packageManager = "pnpm";
|
|
88
|
+
} else if (fs.existsSync(path.join(cwd, "yarn.lock"))) {
|
|
89
|
+
packageManager = "yarn";
|
|
90
|
+
} else if (fs.existsSync(path.join(cwd, "bun.lockb")) || fs.existsSync(path.join(cwd, "bun.lock"))) {
|
|
91
|
+
packageManager = "bun";
|
|
92
|
+
} else if (fs.existsSync(path.join(cwd, "package-lock.json"))) {
|
|
93
|
+
packageManager = "npm";
|
|
94
|
+
}
|
|
95
|
+
const srcDir = env === "nextjs" ? fs.existsSync(path.join(cwd, "src", "app")) : fs.existsSync(path.join(cwd, "src"));
|
|
96
|
+
return { hasPackageJson, parseError, env, packageManager, hasSdk, hasReactQuery, srcDir };
|
|
97
|
+
}
|
|
98
|
+
function needsClient(env) {
|
|
99
|
+
return env === "nextjs" || env === "react-vite" || env === "react-cra" || env === "vanilla";
|
|
100
|
+
}
|
|
101
|
+
function needsServer(env) {
|
|
102
|
+
return env === "nextjs" || env === "node" || env === "edge";
|
|
103
|
+
}
|
|
104
|
+
function needsReactQuery(env) {
|
|
105
|
+
return env === "nextjs" || env === "react-vite" || env === "react-cra";
|
|
106
|
+
}
|
|
107
|
+
function getPublishableKeyEnvVar(env) {
|
|
108
|
+
switch (env) {
|
|
109
|
+
case "nextjs":
|
|
110
|
+
return "NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY";
|
|
111
|
+
case "react-vite":
|
|
112
|
+
return "VITE_SOFTWARE_PUBLISHABLE_KEY";
|
|
113
|
+
case "react-cra":
|
|
114
|
+
return "REACT_APP_SOFTWARE_PUBLISHABLE_KEY";
|
|
115
|
+
default:
|
|
116
|
+
return "SOFTWARE_PUBLISHABLE_KEY";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/browser-auth.ts
|
|
121
|
+
import { randomBytes } from "crypto";
|
|
122
|
+
import { createServer } from "http";
|
|
123
|
+
import { execFile, exec } from "child_process";
|
|
124
|
+
import { platform } from "os";
|
|
125
|
+
import { URL } from "url";
|
|
126
|
+
import pc from "picocolors";
|
|
127
|
+
var DEFAULT_WEB_URL = process.env.SOFTWARE_WEB_URL || "https://01.software";
|
|
128
|
+
var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
129
|
+
function escapeHtml(s) {
|
|
130
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
131
|
+
}
|
|
132
|
+
function openBrowser(url) {
|
|
133
|
+
const os2 = platform();
|
|
134
|
+
const onError = () => {
|
|
135
|
+
console.log(
|
|
136
|
+
pc.yellow(
|
|
137
|
+
`Could not open browser automatically. Open this URL manually:
|
|
138
|
+
${url}`
|
|
139
|
+
)
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
if (os2 === "win32") {
|
|
143
|
+
exec(`start "" "${url}"`, (err) => {
|
|
144
|
+
if (err) onError();
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
const cmd = os2 === "darwin" ? "open" : "xdg-open";
|
|
148
|
+
execFile(cmd, [url], (err) => {
|
|
149
|
+
if (err) onError();
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
var PAGE_STYLE = `*{margin:0;box-sizing:border-box}
|
|
154
|
+
body{font-family:system-ui,-apple-system,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#fff;color:#252525}
|
|
155
|
+
@media(prefers-color-scheme:dark){body{background:#252525;color:#f5f5f5}}
|
|
156
|
+
.card{text-align:center;padding:2rem 2.5rem;border-radius:10px;max-width:380px;width:100%}
|
|
157
|
+
.icon{width:40px;height:40px;margin:0 auto 1rem;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.25rem}
|
|
158
|
+
.icon.ok{background:rgba(0,0,0,.05);color:#252525}
|
|
159
|
+
.icon.err{background:rgba(220,38,38,.08);color:#dc2626}
|
|
160
|
+
@media(prefers-color-scheme:dark){.icon.ok{background:rgba(255,255,255,.08);color:#f5f5f5}}
|
|
161
|
+
h1{font-size:.875rem;font-weight:600;margin-bottom:.375rem}
|
|
162
|
+
p{font-size:.75rem;color:#737373;line-height:1.5}`;
|
|
163
|
+
var SUCCESS_HTML = `<!DOCTYPE html>
|
|
164
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Login</title>
|
|
165
|
+
<style>${PAGE_STYLE}</style>
|
|
166
|
+
</head><body><div class="card"><div class="icon ok">\u2713</div><h1>Authenticated</h1><p>You can close this tab and return to the terminal.</p></div></body></html>`;
|
|
167
|
+
var ERROR_HTML = (msg) => `<!DOCTYPE html>
|
|
168
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Login Error</title>
|
|
169
|
+
<style>${PAGE_STYLE}</style>
|
|
170
|
+
</head><body><div class="card"><div class="icon err">!</div><h1>Authentication failed</h1><p>${escapeHtml(msg)}</p></div></body></html>`;
|
|
171
|
+
async function exchangeCode(webUrl, code) {
|
|
172
|
+
const url = `${webUrl}/api/cli/exchange`;
|
|
173
|
+
try {
|
|
174
|
+
const res = await fetch(url, {
|
|
175
|
+
method: "POST",
|
|
176
|
+
headers: { "Content-Type": "application/json" },
|
|
177
|
+
body: JSON.stringify({ code })
|
|
178
|
+
});
|
|
179
|
+
if (!res.ok) {
|
|
180
|
+
const body = await res.text().catch(() => "");
|
|
181
|
+
console.error(
|
|
182
|
+
pc.red(
|
|
183
|
+
`Exchange failed: HTTP ${res.status} from ${url}${body ? ` \u2014 ${body.slice(0, 200)}` : ""}`
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const data = await res.json();
|
|
189
|
+
if (typeof data.publishableKey !== "string" || typeof data.secretKey !== "string" || typeof data.tenantName !== "string" || typeof data.tenantId !== "string") {
|
|
190
|
+
console.error(pc.red(`Exchange failed: malformed response from ${url}`));
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
publishableKey: data.publishableKey,
|
|
195
|
+
secretKey: data.secretKey,
|
|
196
|
+
tenantName: data.tenantName,
|
|
197
|
+
tenantId: data.tenantId
|
|
198
|
+
};
|
|
199
|
+
} catch (err) {
|
|
200
|
+
console.error(
|
|
201
|
+
pc.red(
|
|
202
|
+
`Exchange request to ${url} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
203
|
+
)
|
|
204
|
+
);
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function startBrowserAuth(options) {
|
|
209
|
+
const state = randomBytes(32).toString("hex");
|
|
210
|
+
const webUrl = options?.webUrl ?? DEFAULT_WEB_URL;
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
const server = createServer((req, res) => {
|
|
213
|
+
if (!req.url) {
|
|
214
|
+
res.writeHead(400).end();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const url = new URL(req.url, `http://localhost`);
|
|
218
|
+
if (url.pathname !== "/callback" || req.method !== "GET") {
|
|
219
|
+
res.writeHead(404).end();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const error = url.searchParams.get("error");
|
|
223
|
+
if (error) {
|
|
224
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML(error));
|
|
225
|
+
console.error(pc.red(`Login failed: ${error}`));
|
|
226
|
+
cleanup(new Error(`Login failed: ${error}`));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const code = url.searchParams.get("code");
|
|
230
|
+
const receivedState = url.searchParams.get("state");
|
|
231
|
+
if (!code || !receivedState) {
|
|
232
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("Missing code or state."));
|
|
233
|
+
cleanup(new Error("Login failed: missing code or state."));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (receivedState !== state) {
|
|
237
|
+
res.writeHead(403, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("State mismatch."));
|
|
238
|
+
console.error(pc.red("Login failed: state mismatch."));
|
|
239
|
+
cleanup(new Error("Login failed: state mismatch."));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
exchangeCode(webUrl, code).then((creds) => {
|
|
243
|
+
if (!creds) {
|
|
244
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("Invalid or expired code."));
|
|
245
|
+
cleanup(new Error("Login failed: code exchange failed."));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(SUCCESS_HTML);
|
|
249
|
+
console.log(pc.green(`
|
|
250
|
+
Logged in successfully!`));
|
|
251
|
+
console.log(pc.dim(`Tenant: ${creds.tenantName}`));
|
|
252
|
+
cleanup(null, creds);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
let timeout;
|
|
256
|
+
let completed = false;
|
|
257
|
+
function cleanup(err, result) {
|
|
258
|
+
if (completed) return;
|
|
259
|
+
completed = true;
|
|
260
|
+
clearTimeout(timeout);
|
|
261
|
+
server.closeAllConnections?.();
|
|
262
|
+
server.close(() => {
|
|
263
|
+
if (err) {
|
|
264
|
+
reject(err);
|
|
265
|
+
} else {
|
|
266
|
+
resolve(result);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
server.listen(0, "127.0.0.1", () => {
|
|
271
|
+
const addr = server.address();
|
|
272
|
+
if (!addr || typeof addr === "string") {
|
|
273
|
+
reject(new Error("Failed to start local server."));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const port = addr.port;
|
|
277
|
+
timeout = setTimeout(() => {
|
|
278
|
+
console.error(pc.red("\nLogin timed out (5 minutes). Please try again."));
|
|
279
|
+
cleanup(new Error("Login timed out"));
|
|
280
|
+
}, TIMEOUT_MS);
|
|
281
|
+
const params = new URLSearchParams({ port: String(port), state });
|
|
282
|
+
if (options?.tenantId) {
|
|
283
|
+
params.set("tenantId", options.tenantId);
|
|
284
|
+
}
|
|
285
|
+
const loginUrl = `${webUrl}/cli-auth?${params.toString()}`;
|
|
286
|
+
console.log(pc.dim("Opening browser for login..."));
|
|
287
|
+
console.log(pc.dim(`If the browser does not open, visit:
|
|
288
|
+
${loginUrl}`));
|
|
289
|
+
openBrowser(loginUrl);
|
|
290
|
+
});
|
|
291
|
+
server.on("error", (err) => {
|
|
292
|
+
reject(err);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/init.ts
|
|
298
|
+
var SECRET_KEY_ENV_VAR = "SOFTWARE_SECRET_KEY";
|
|
299
|
+
async function init(cwd, info, answers, deps = {}) {
|
|
300
|
+
const { packageManager, srcDir } = info;
|
|
301
|
+
const env = answers.env;
|
|
302
|
+
const baseDir = srcDir ? path2.join(cwd, "src") : cwd;
|
|
303
|
+
const browserAuth = deps.startBrowserAuth ?? startBrowserAuth;
|
|
304
|
+
const publishableKeyEnvVar = getPublishableKeyEnvVar(env);
|
|
305
|
+
const wantsClient = needsClient(env);
|
|
306
|
+
const wantsServer = needsServer(env);
|
|
307
|
+
const wantsReactQuery = needsReactQuery(env);
|
|
308
|
+
const plan = await planConflictsAndEnv(cwd, baseDir, env, answers);
|
|
309
|
+
const installResult = deps.skipInstall ? {
|
|
310
|
+
installFailed: false,
|
|
311
|
+
installSkipped: true,
|
|
312
|
+
installCmd: buildAddCmd(packageManager, hasPnpmWorkspace(cwd), [
|
|
313
|
+
"@01.software/sdk"
|
|
314
|
+
])
|
|
315
|
+
} : installDeps(
|
|
316
|
+
cwd,
|
|
317
|
+
packageManager,
|
|
318
|
+
info.hasSdk,
|
|
319
|
+
info.hasReactQuery,
|
|
320
|
+
wantsReactQuery
|
|
321
|
+
);
|
|
322
|
+
if (wantsClient || wantsReactQuery || wantsServer) {
|
|
323
|
+
fs2.mkdirSync(path2.join(baseDir, "lib", "software"), { recursive: true });
|
|
324
|
+
}
|
|
325
|
+
const libDir = path2.join(baseDir, "lib", "software");
|
|
326
|
+
if (wantsClient) {
|
|
327
|
+
await writeFileWithPolicy(
|
|
328
|
+
cwd,
|
|
329
|
+
path2.join(libDir, "client.ts"),
|
|
330
|
+
getClientTemplate(env, publishableKeyEnvVar),
|
|
331
|
+
plan.policy
|
|
332
|
+
);
|
|
333
|
+
await writeFileWithPolicy(
|
|
334
|
+
cwd,
|
|
335
|
+
path2.join(libDir, "analytics.ts"),
|
|
336
|
+
getAnalyticsTemplate(env, publishableKeyEnvVar),
|
|
337
|
+
plan.policy
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (wantsReactQuery) {
|
|
341
|
+
await writeFileWithPolicy(
|
|
342
|
+
cwd,
|
|
343
|
+
path2.join(libDir, "query-provider.tsx"),
|
|
344
|
+
getQueryProviderTemplate(env),
|
|
345
|
+
plan.policy
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
if (wantsServer) {
|
|
349
|
+
await writeFileWithPolicy(
|
|
350
|
+
cwd,
|
|
351
|
+
path2.join(libDir, "server.ts"),
|
|
352
|
+
getServerTemplate(env, publishableKeyEnvVar, SECRET_KEY_ENV_VAR),
|
|
353
|
+
plan.policy
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
if (plan.envFile && answers.authMethod !== "browser") {
|
|
357
|
+
await writeEnv(
|
|
358
|
+
cwd,
|
|
359
|
+
plan.envFile,
|
|
360
|
+
answers.publishableKey || "",
|
|
361
|
+
answers.secretKey || "",
|
|
362
|
+
publishableKeyEnvVar,
|
|
363
|
+
wantsServer ? SECRET_KEY_ENV_VAR : null,
|
|
364
|
+
plan.policy
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
let publishableKey = answers.publishableKey;
|
|
368
|
+
let secretKey = answers.secretKey;
|
|
369
|
+
let tenantName = "";
|
|
370
|
+
if (answers.authMethod === "browser" && answers.aiTools.length > 0) {
|
|
371
|
+
try {
|
|
372
|
+
console.log();
|
|
373
|
+
const creds = await browserAuth();
|
|
374
|
+
publishableKey = creds.publishableKey;
|
|
375
|
+
secretKey = creds.secretKey;
|
|
376
|
+
tenantName = creds.tenantName;
|
|
377
|
+
if (plan.envFile && publishableKey) {
|
|
378
|
+
await writeEnv(
|
|
379
|
+
cwd,
|
|
380
|
+
plan.envFile,
|
|
381
|
+
publishableKey,
|
|
382
|
+
secretKey,
|
|
383
|
+
publishableKeyEnvVar,
|
|
384
|
+
wantsServer ? SECRET_KEY_ENV_VAR : null,
|
|
385
|
+
"overwrite",
|
|
386
|
+
true
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
} catch (err) {
|
|
390
|
+
console.log(
|
|
391
|
+
pc2.yellow(" Browser auth skipped:"),
|
|
392
|
+
err instanceof Error ? err.message : String(err)
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (answers.aiTools.length > 0) {
|
|
397
|
+
for (const tool of answers.aiTools) {
|
|
398
|
+
await writeMcpConfig(tool, cwd);
|
|
399
|
+
}
|
|
400
|
+
addToGitignore(cwd, answers.aiTools);
|
|
401
|
+
if (answers.aiTools.includes("claude")) {
|
|
402
|
+
await writeClaudeDocs(cwd, publishableKey, secretKey, tenantName, plan.policy);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
...installResult,
|
|
407
|
+
publishableKey: publishableKey || void 0,
|
|
408
|
+
secretKey: secretKey || void 0,
|
|
409
|
+
tenantName: tenantName || void 0
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function installDeps(cwd, pm, hasSdk, hasReactQuery, wantsReactQuery) {
|
|
413
|
+
const allDeps = [
|
|
414
|
+
{ name: "@01.software/sdk", installed: hasSdk, needed: true },
|
|
415
|
+
{ name: "@tanstack/react-query", installed: hasReactQuery, needed: wantsReactQuery }
|
|
416
|
+
];
|
|
417
|
+
const fullList = allDeps.filter((d) => d.needed).map((d) => d.name);
|
|
418
|
+
const toInstall = allDeps.filter((d) => d.needed && !d.installed).map((d) => d.name);
|
|
419
|
+
const fullCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), fullList);
|
|
420
|
+
if (toInstall.length === 0) {
|
|
421
|
+
console.log(pc2.dim(` Dependencies already installed: ${fullList.join(", ")}`));
|
|
422
|
+
return { installFailed: false, installSkipped: true, installCmd: fullCmd };
|
|
423
|
+
}
|
|
424
|
+
const addCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), toInstall);
|
|
425
|
+
console.log(pc2.dim(` Installing ${toInstall.join(" and ")}...`));
|
|
426
|
+
const wsPatched = pm === "pnpm" && patchPnpmWorkspace(cwd);
|
|
427
|
+
let installFailed = false;
|
|
428
|
+
try {
|
|
429
|
+
execSync(addCmd, { cwd, stdio: "pipe" });
|
|
430
|
+
console.log(pc2.green(" Installed"), toInstall.join(", "));
|
|
431
|
+
} catch (error) {
|
|
432
|
+
installFailed = true;
|
|
433
|
+
const err = error;
|
|
434
|
+
const msg = String(err.stderr || "").trim() || String(err.stdout || "").trim() || String(error);
|
|
435
|
+
console.log(pc2.yellow(" Install failed \u2014 continuing with scaffolding"));
|
|
436
|
+
const firstLines = msg.split("\n").slice(0, 3).map((l) => ` ${l}`).join("\n");
|
|
437
|
+
if (firstLines) console.log(pc2.dim(firstLines));
|
|
438
|
+
console.log(pc2.dim(` Run manually: ${addCmd}`));
|
|
439
|
+
} finally {
|
|
440
|
+
if (wsPatched) restorePnpmWorkspace(cwd);
|
|
441
|
+
}
|
|
442
|
+
return { installFailed, installSkipped: false, installCmd: addCmd };
|
|
443
|
+
}
|
|
444
|
+
function buildAddCmd(pm, hasPnpmWs, deps) {
|
|
445
|
+
const pkgs = deps.join(" ");
|
|
446
|
+
switch (pm) {
|
|
447
|
+
case "pnpm":
|
|
448
|
+
return hasPnpmWs ? `pnpm add -w ${pkgs}` : `pnpm add ${pkgs}`;
|
|
449
|
+
case "yarn":
|
|
450
|
+
return `yarn add ${pkgs}`;
|
|
451
|
+
case "bun":
|
|
452
|
+
return `bun add ${pkgs}`;
|
|
453
|
+
default:
|
|
454
|
+
return `npm install ${pkgs}`;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
async function planConflictsAndEnv(cwd, baseDir, env, answers) {
|
|
458
|
+
const candidates = [];
|
|
459
|
+
const libDir = path2.join(baseDir, "lib", "software");
|
|
460
|
+
if (needsClient(env)) candidates.push(path2.join(libDir, "client.ts"));
|
|
461
|
+
if (needsReactQuery(env)) candidates.push(path2.join(libDir, "query-provider.tsx"));
|
|
462
|
+
if (needsServer(env)) candidates.push(path2.join(libDir, "server.ts"));
|
|
463
|
+
if (answers.aiTools.includes("claude")) {
|
|
464
|
+
for (const { dirName } of getSkillFiles()) {
|
|
465
|
+
candidates.push(path2.join(cwd, ".claude", "skills", dirName, "SKILL.md"));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const conflicts = candidates.filter((p) => fs2.existsSync(p));
|
|
469
|
+
let policy = "skip";
|
|
470
|
+
if (conflicts.length > 0) {
|
|
471
|
+
console.log(pc2.yellow(` ${conflicts.length} file(s) already exist:`));
|
|
472
|
+
for (const c of conflicts) console.log(pc2.dim(` ${path2.relative(cwd, c)}`));
|
|
473
|
+
const { selected } = await prompts({
|
|
474
|
+
type: "select",
|
|
475
|
+
name: "selected",
|
|
476
|
+
message: "How should I handle existing files?",
|
|
477
|
+
choices: [
|
|
478
|
+
{ title: "Keep existing (skip)", value: "skip" },
|
|
479
|
+
{ title: "Overwrite all", value: "overwrite" },
|
|
480
|
+
{ title: "Ask for each", value: "ask" }
|
|
481
|
+
],
|
|
482
|
+
initial: 0
|
|
483
|
+
});
|
|
484
|
+
policy = selected ?? "skip";
|
|
485
|
+
}
|
|
486
|
+
const envFile = env === "vanilla" || env === "edge" ? "" : await pickEnvFile(cwd, env);
|
|
487
|
+
return { policy, envFile };
|
|
488
|
+
}
|
|
489
|
+
async function pickEnvFile(cwd, env) {
|
|
490
|
+
const candidates = [".env.local", ".env", ".env.development"];
|
|
491
|
+
const existing = candidates.filter((f) => fs2.existsSync(path2.join(cwd, f)));
|
|
492
|
+
const preferred = env === "nextjs" ? ".env.local" : ".env";
|
|
493
|
+
if (existing.length === 0) return preferred;
|
|
494
|
+
if (existing.length === 1 && existing[0] === preferred) return existing[0];
|
|
495
|
+
const options = Array.from(/* @__PURE__ */ new Set([...existing, preferred]));
|
|
496
|
+
const choices = options.map((f) => ({
|
|
497
|
+
title: f,
|
|
498
|
+
description: existing.includes(f) ? "exists" : "create",
|
|
499
|
+
value: f
|
|
500
|
+
}));
|
|
501
|
+
const initial = Math.max(
|
|
502
|
+
0,
|
|
503
|
+
choices.findIndex((c) => c.value === preferred)
|
|
504
|
+
);
|
|
505
|
+
const { file } = await prompts({
|
|
506
|
+
type: "select",
|
|
507
|
+
name: "file",
|
|
508
|
+
message: "Which env file should I write SDK credentials to?",
|
|
509
|
+
choices,
|
|
510
|
+
initial
|
|
511
|
+
});
|
|
512
|
+
return file ?? preferred;
|
|
513
|
+
}
|
|
514
|
+
async function writeFileWithPolicy(cwd, filePath, content, policy) {
|
|
515
|
+
const rel = path2.relative(cwd, filePath);
|
|
516
|
+
if (!fs2.existsSync(filePath)) {
|
|
517
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
518
|
+
fs2.writeFileSync(filePath, content);
|
|
519
|
+
console.log(pc2.green(" Created"), rel);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const existing = fs2.readFileSync(filePath, "utf-8");
|
|
523
|
+
if (existing === content) {
|
|
524
|
+
console.log(pc2.dim(" Unchanged"), rel);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
let shouldWrite = false;
|
|
528
|
+
if (policy === "overwrite") {
|
|
529
|
+
shouldWrite = true;
|
|
530
|
+
} else if (policy === "ask") {
|
|
531
|
+
const { confirm } = await prompts({
|
|
532
|
+
type: "confirm",
|
|
533
|
+
name: "confirm",
|
|
534
|
+
message: `Overwrite ${rel}?`,
|
|
535
|
+
initial: false
|
|
536
|
+
});
|
|
537
|
+
shouldWrite = !!confirm;
|
|
538
|
+
}
|
|
539
|
+
if (shouldWrite) {
|
|
540
|
+
fs2.writeFileSync(filePath, content);
|
|
541
|
+
console.log(pc2.green(" Overwrote"), rel);
|
|
542
|
+
} else {
|
|
543
|
+
console.log(pc2.yellow(" Skipped"), rel, pc2.dim("(already exists)"));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async function writeEnv(cwd, envFile, publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar, policy, fromBrowserAuth = false) {
|
|
547
|
+
const envPath = path2.join(cwd, envFile);
|
|
548
|
+
const targets = [
|
|
549
|
+
{ name: publishableKeyEnvVar, value: publishableKey }
|
|
550
|
+
];
|
|
551
|
+
if (secretKeyEnvVar) {
|
|
552
|
+
targets.push({ name: secretKeyEnvVar, value: secretKey });
|
|
553
|
+
}
|
|
554
|
+
if (!fs2.existsSync(envPath)) {
|
|
555
|
+
const initial = getEnvContent(publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar);
|
|
556
|
+
writeEnvFile(envPath, initial.trimStart());
|
|
557
|
+
console.log(pc2.green(" Created"), envFile);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
let content = fs2.readFileSync(envPath, "utf-8");
|
|
561
|
+
let modified = false;
|
|
562
|
+
let appendedHeader = false;
|
|
563
|
+
const headerAlreadyPresent = targets.some(
|
|
564
|
+
(t) => readEnvValue(content, t.name) !== null
|
|
565
|
+
);
|
|
566
|
+
for (const { name, value } of targets) {
|
|
567
|
+
const existing = readEnvValue(content, name);
|
|
568
|
+
if (existing === null) {
|
|
569
|
+
if (!headerAlreadyPresent && !appendedHeader) {
|
|
570
|
+
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
571
|
+
content += "\n# 01.software\n";
|
|
572
|
+
appendedHeader = true;
|
|
573
|
+
}
|
|
574
|
+
content = setEnvValue(content, name, value);
|
|
575
|
+
modified = true;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
if (existing === value) continue;
|
|
579
|
+
if (!value) continue;
|
|
580
|
+
let shouldOverwrite = false;
|
|
581
|
+
if (policy === "overwrite") {
|
|
582
|
+
shouldOverwrite = true;
|
|
583
|
+
} else if (policy === "ask") {
|
|
584
|
+
const { confirm } = await prompts({
|
|
585
|
+
type: "confirm",
|
|
586
|
+
name: "confirm",
|
|
587
|
+
message: `${name} already set in ${envFile}. Overwrite?`,
|
|
588
|
+
initial: fromBrowserAuth
|
|
589
|
+
});
|
|
590
|
+
shouldOverwrite = !!confirm;
|
|
591
|
+
}
|
|
592
|
+
if (shouldOverwrite) {
|
|
593
|
+
content = setEnvValue(content, name, value);
|
|
594
|
+
modified = true;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (modified) {
|
|
598
|
+
writeEnvFile(envPath, content);
|
|
599
|
+
console.log(
|
|
600
|
+
pc2.green(" Updated"),
|
|
601
|
+
envFile,
|
|
602
|
+
fromBrowserAuth ? pc2.dim("(SDK credentials)") : ""
|
|
603
|
+
);
|
|
604
|
+
} else {
|
|
605
|
+
chmodSecretFile(envPath);
|
|
606
|
+
console.log(pc2.dim(" Unchanged"), envFile);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function resolveMcpLocation(tool, cwd) {
|
|
610
|
+
const home = os.homedir();
|
|
611
|
+
switch (tool) {
|
|
612
|
+
case "claude":
|
|
613
|
+
return {
|
|
614
|
+
kind: "json",
|
|
615
|
+
absolutePath: path2.join(cwd, ".mcp.json"),
|
|
616
|
+
displayPath: ".mcp.json",
|
|
617
|
+
gitignoreEntry: ".mcp.json"
|
|
618
|
+
};
|
|
619
|
+
case "cursor":
|
|
620
|
+
return {
|
|
621
|
+
kind: "json",
|
|
622
|
+
absolutePath: path2.join(cwd, ".cursor", "mcp.json"),
|
|
623
|
+
displayPath: ".cursor/mcp.json",
|
|
624
|
+
gitignoreEntry: ".cursor/mcp.json"
|
|
625
|
+
};
|
|
626
|
+
case "vscode":
|
|
627
|
+
return {
|
|
628
|
+
kind: "json",
|
|
629
|
+
absolutePath: path2.join(cwd, ".vscode", "mcp.json"),
|
|
630
|
+
jsonClient: "vscode",
|
|
631
|
+
displayPath: ".vscode/mcp.json",
|
|
632
|
+
gitignoreEntry: ".vscode/mcp.json"
|
|
633
|
+
};
|
|
634
|
+
case "windsurf": {
|
|
635
|
+
if (!home) return null;
|
|
636
|
+
const p = path2.join(home, ".codeium", "windsurf", "mcp_config.json");
|
|
637
|
+
return {
|
|
638
|
+
kind: "json",
|
|
639
|
+
absolutePath: p,
|
|
640
|
+
jsonClient: "windsurf",
|
|
641
|
+
displayPath: p,
|
|
642
|
+
gitignoreEntry: null,
|
|
643
|
+
global: true
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
case "codex": {
|
|
647
|
+
if (!home) return null;
|
|
648
|
+
const p = path2.join(home, ".codex", "config.toml");
|
|
649
|
+
return { kind: "toml", absolutePath: p, displayPath: p, gitignoreEntry: null, global: true };
|
|
650
|
+
}
|
|
651
|
+
case "gemini": {
|
|
652
|
+
if (!home) return null;
|
|
653
|
+
const p = path2.join(home, ".gemini", "settings.json");
|
|
654
|
+
return { kind: "json", absolutePath: p, displayPath: p, gitignoreEntry: null, global: true };
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
async function writeMcpConfig(tool, cwd) {
|
|
659
|
+
const loc = resolveMcpLocation(tool, cwd);
|
|
660
|
+
if (!loc) {
|
|
661
|
+
console.log(pc2.yellow(` Skipped ${tool}`), pc2.dim("(HOME not set)"));
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
if (!loc.global) {
|
|
665
|
+
fs2.mkdirSync(path2.dirname(loc.absolutePath), { recursive: true });
|
|
666
|
+
}
|
|
667
|
+
try {
|
|
668
|
+
if (loc.kind === "json") {
|
|
669
|
+
writeJsonMcp(loc);
|
|
670
|
+
} else {
|
|
671
|
+
writeTomlMcp(loc);
|
|
672
|
+
}
|
|
673
|
+
} catch (err) {
|
|
674
|
+
console.log(
|
|
675
|
+
pc2.yellow(` Skipped ${loc.displayPath}`),
|
|
676
|
+
pc2.dim(err instanceof Error ? err.message : String(err))
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function writeJsonMcp(loc) {
|
|
681
|
+
const writeFile = loc.global ? (target, content) => writeSecretGlobalConfig(target, content) : (target, content) => fs2.writeFileSync(target, content);
|
|
682
|
+
if (!fs2.existsSync(loc.absolutePath)) {
|
|
683
|
+
writeFile(loc.absolutePath, getMcpConfigTemplate(loc.jsonClient));
|
|
684
|
+
console.log(pc2.green(" Created"), loc.displayPath);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
const rootKey = getMcpRootKey(loc.jsonClient);
|
|
688
|
+
let existing;
|
|
689
|
+
try {
|
|
690
|
+
existing = JSON.parse(fs2.readFileSync(loc.absolutePath, "utf-8"));
|
|
691
|
+
} catch {
|
|
692
|
+
console.log(pc2.yellow(" Skipped"), loc.displayPath, pc2.dim("(could not parse existing file)"));
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
const nextEntry = getMcpServerEntry(loc.jsonClient);
|
|
696
|
+
const existingServers = existing[rootKey] ?? void 0;
|
|
697
|
+
if (existingServers?.["01software"] && JSON.stringify(existingServers["01software"]) === JSON.stringify(nextEntry)) {
|
|
698
|
+
console.log(pc2.dim(" Unchanged"), loc.displayPath);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const servers = existing[rootKey] ?? {};
|
|
702
|
+
servers["01software"] = nextEntry;
|
|
703
|
+
existing[rootKey] = servers;
|
|
704
|
+
writeFile(loc.absolutePath, JSON.stringify(existing, null, 2) + "\n");
|
|
705
|
+
console.log(pc2.green(" Updated"), loc.displayPath);
|
|
706
|
+
}
|
|
707
|
+
function writeTomlMcp(loc) {
|
|
708
|
+
const section = getCodexMcpTomlSection();
|
|
709
|
+
const writeFile = loc.global ? (target, content) => writeSecretGlobalConfig(target, content) : (target, content) => fs2.writeFileSync(target, content);
|
|
710
|
+
if (!fs2.existsSync(loc.absolutePath)) {
|
|
711
|
+
writeFile(loc.absolutePath, section.trimStart());
|
|
712
|
+
console.log(pc2.green(" Created"), loc.displayPath);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
const existing = fs2.readFileSync(loc.absolutePath, "utf-8");
|
|
716
|
+
if (!existing.includes(CODEX_MCP_SECTION_MARKER)) {
|
|
717
|
+
const sep = existing.endsWith("\n") ? "" : "\n";
|
|
718
|
+
writeFile(loc.absolutePath, existing + sep + section);
|
|
719
|
+
console.log(pc2.green(" Updated"), loc.displayPath);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const replaced = replaceTomlMcpSection(existing, section);
|
|
723
|
+
if (replaced === existing) {
|
|
724
|
+
console.log(pc2.dim(" Unchanged"), loc.displayPath);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
writeFile(loc.absolutePath, replaced);
|
|
728
|
+
console.log(pc2.green(" Updated"), loc.displayPath);
|
|
729
|
+
}
|
|
730
|
+
function addToGitignore(cwd, tools) {
|
|
731
|
+
const entries = [];
|
|
732
|
+
for (const tool of tools) {
|
|
733
|
+
const loc = resolveMcpLocation(tool, cwd);
|
|
734
|
+
if (loc?.gitignoreEntry) entries.push(loc.gitignoreEntry);
|
|
735
|
+
}
|
|
736
|
+
if (entries.length === 0) return;
|
|
737
|
+
const gitignorePath = path2.join(cwd, ".gitignore");
|
|
738
|
+
const existing = fs2.existsSync(gitignorePath) ? fs2.readFileSync(gitignorePath, "utf-8") : "";
|
|
739
|
+
const toAdd = entries.filter((e) => !existing.includes(e));
|
|
740
|
+
if (toAdd.length === 0) return;
|
|
741
|
+
const content = "\n# MCP configs\n" + toAdd.join("\n") + "\n";
|
|
742
|
+
if (fs2.existsSync(gitignorePath)) {
|
|
743
|
+
fs2.appendFileSync(gitignorePath, content);
|
|
744
|
+
} else {
|
|
745
|
+
fs2.writeFileSync(gitignorePath, content.trimStart());
|
|
746
|
+
}
|
|
747
|
+
console.log(pc2.green(" Updated"), ".gitignore", pc2.dim(`(added ${toAdd.join(", ")})`));
|
|
748
|
+
}
|
|
749
|
+
async function writeClaudeDocs(cwd, publishableKey, secretKey, tenantName, policy) {
|
|
750
|
+
let ctx = {
|
|
751
|
+
tenantName: tenantName || "Your Tenant",
|
|
752
|
+
features: void 0,
|
|
753
|
+
collections: void 0
|
|
754
|
+
};
|
|
755
|
+
if (publishableKey && secretKey) {
|
|
756
|
+
const fetched = await fetchTenantContext(publishableKey, secretKey);
|
|
757
|
+
if (fetched) {
|
|
758
|
+
ctx = {
|
|
759
|
+
tenantName: fetched.tenantName || ctx.tenantName,
|
|
760
|
+
features: fetched.features,
|
|
761
|
+
collections: fetched.collections
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
const claudeDir = path2.join(cwd, ".claude");
|
|
766
|
+
const softwareDir = path2.join(claudeDir, "01software");
|
|
767
|
+
const skillsDir = path2.join(claudeDir, "skills");
|
|
768
|
+
fs2.mkdirSync(softwareDir, { recursive: true });
|
|
769
|
+
fs2.mkdirSync(skillsDir, { recursive: true });
|
|
770
|
+
const contextPath = path2.join(softwareDir, "context.md");
|
|
771
|
+
const contextExists = fs2.existsSync(contextPath);
|
|
772
|
+
fs2.writeFileSync(contextPath, generateClaudeMd(ctx));
|
|
773
|
+
console.log(pc2.green(contextExists ? " Updated" : " Created"), ".claude/01software/context.md");
|
|
774
|
+
const claudeMdPath = path2.join(claudeDir, "CLAUDE.md");
|
|
775
|
+
const importLine = "@.claude/01software/context.md";
|
|
776
|
+
if (!fs2.existsSync(claudeMdPath)) {
|
|
777
|
+
fs2.writeFileSync(claudeMdPath, importLine + "\n");
|
|
778
|
+
console.log(pc2.green(" Created"), ".claude/CLAUDE.md");
|
|
779
|
+
} else {
|
|
780
|
+
const existing = fs2.readFileSync(claudeMdPath, "utf-8");
|
|
781
|
+
if (!existing.includes(importLine)) {
|
|
782
|
+
const prefix = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
783
|
+
fs2.appendFileSync(claudeMdPath, prefix + importLine + "\n");
|
|
784
|
+
console.log(pc2.green(" Updated"), ".claude/CLAUDE.md", pc2.dim("(added @import)"));
|
|
785
|
+
} else {
|
|
786
|
+
console.log(pc2.dim(" Unchanged"), ".claude/CLAUDE.md");
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
for (const { dirName, content } of getSkillFiles()) {
|
|
790
|
+
const skillDir = path2.join(skillsDir, dirName);
|
|
791
|
+
const skillPath = path2.join(skillDir, "SKILL.md");
|
|
792
|
+
fs2.mkdirSync(skillDir, { recursive: true });
|
|
793
|
+
await writeFileWithPolicy(cwd, skillPath, content, policy);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
var WS_FILE = "pnpm-workspace.yaml";
|
|
797
|
+
var WS_BACKUP = "pnpm-workspace.yaml.bak";
|
|
798
|
+
function hasPnpmWorkspace(cwd) {
|
|
799
|
+
return fs2.existsSync(path2.join(cwd, WS_FILE));
|
|
800
|
+
}
|
|
801
|
+
function patchPnpmWorkspace(cwd) {
|
|
802
|
+
const wsPath = path2.join(cwd, WS_FILE);
|
|
803
|
+
if (!fs2.existsSync(wsPath)) return false;
|
|
804
|
+
const content = fs2.readFileSync(wsPath, "utf-8");
|
|
805
|
+
if (content.includes("packages:")) return false;
|
|
806
|
+
fs2.copyFileSync(wsPath, path2.join(cwd, WS_BACKUP));
|
|
807
|
+
fs2.writeFileSync(wsPath, content.trimEnd() + "\npackages: []\n");
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
function restorePnpmWorkspace(cwd) {
|
|
811
|
+
const backupPath = path2.join(cwd, WS_BACKUP);
|
|
812
|
+
if (!fs2.existsSync(backupPath)) return;
|
|
813
|
+
fs2.copyFileSync(backupPath, path2.join(cwd, WS_FILE));
|
|
814
|
+
fs2.unlinkSync(backupPath);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
export {
|
|
818
|
+
detectProject,
|
|
819
|
+
init
|
|
820
|
+
};
|
|
821
|
+
//# sourceMappingURL=chunk-3RQE6YVO.js.map
|