@01.software/init 0.9.2 → 0.10.1
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.d.ts +13 -0
- package/dist/browser-auth-CJDrpp5T.d.ts +11 -0
- package/dist/{chunk-UA7WNT2F.js → chunk-4LHYICUL.js} +1 -1
- package/dist/chunk-4LHYICUL.js.map +1 -0
- package/dist/{chunk-R4GGO33X.js → chunk-NJ4X7VNK.js} +1 -1
- package/dist/{chunk-R4GGO33X.js.map → chunk-NJ4X7VNK.js.map} +1 -1
- package/dist/chunk-STM4DKVZ.js +183 -0
- package/dist/chunk-STM4DKVZ.js.map +1 -0
- package/dist/{chunk-ENQSB4OF.js → chunk-WDWJ73KP.js} +40 -214
- package/dist/chunk-WDWJ73KP.js.map +1 -0
- package/dist/create-app-templates/ecommerce/AGENTS.md +88 -0
- package/dist/create-app-templates/ecommerce/CHANGELOG.md +48 -0
- package/dist/create-app-templates/ecommerce/CLAUDE.md +1 -0
- package/dist/create-app-templates/ecommerce/README.md +154 -0
- package/dist/create-app-templates/ecommerce/app/api/auth/login/route.ts +30 -0
- package/dist/create-app-templates/ecommerce/app/api/auth/logout/route.ts +18 -0
- package/dist/create-app-templates/ecommerce/app/api/auth/register/route.ts +41 -0
- package/dist/create-app-templates/ecommerce/app/api/cart/clear/route.ts +12 -0
- package/dist/create-app-templates/ecommerce/app/api/cart/items/route.ts +45 -0
- package/dist/create-app-templates/ecommerce/app/api/cart/route.ts +14 -0
- package/dist/create-app-templates/ecommerce/app/api/checkout/payment-return/route.ts +86 -0
- package/dist/create-app-templates/ecommerce/app/api/checkout/reconcile/route.ts +50 -0
- package/dist/create-app-templates/ecommerce/app/api/checkout/route.ts +41 -0
- package/dist/create-app-templates/ecommerce/app/cart/page.tsx +10 -0
- package/dist/create-app-templates/ecommerce/app/checkout/page.tsx +10 -0
- package/dist/create-app-templates/ecommerce/app/checkout/success/page.tsx +34 -0
- package/dist/create-app-templates/ecommerce/app/favicon.ico +0 -0
- package/dist/create-app-templates/ecommerce/app/globals.css +67 -0
- package/dist/create-app-templates/ecommerce/app/layout.tsx +23 -0
- package/dist/create-app-templates/ecommerce/app/login/page.tsx +11 -0
- package/dist/create-app-templates/ecommerce/app/page.tsx +5 -0
- package/dist/create-app-templates/ecommerce/app/products/[slug]/page.tsx +46 -0
- package/dist/create-app-templates/ecommerce/app/products/page.tsx +45 -0
- package/dist/create-app-templates/ecommerce/app/register/page.tsx +11 -0
- package/dist/create-app-templates/ecommerce/app/webhook/payment/route.ts +20 -0
- package/dist/create-app-templates/ecommerce/app-config.ts +54 -0
- package/dist/create-app-templates/ecommerce/components/auth/auth-form.tsx +109 -0
- package/dist/create-app-templates/ecommerce/components/cart/cart-content.tsx +129 -0
- package/dist/create-app-templates/ecommerce/components/checkout/checkout-form.tsx +307 -0
- package/dist/create-app-templates/ecommerce/components/checkout/checkout-reconcile.tsx +78 -0
- package/dist/create-app-templates/ecommerce/components/layout/account-nav.tsx +48 -0
- package/dist/create-app-templates/ecommerce/components/layout/account-slot.tsx +12 -0
- package/dist/create-app-templates/ecommerce/components/layout/cart-link.tsx +13 -0
- package/dist/create-app-templates/ecommerce/components/layout/page-shell.tsx +11 -0
- package/dist/create-app-templates/ecommerce/components/layout/site-header.tsx +22 -0
- package/dist/create-app-templates/ecommerce/components/product/add-to-cart.tsx +116 -0
- package/dist/create-app-templates/ecommerce/components/product/product-card.tsx +50 -0
- package/dist/create-app-templates/ecommerce/components/product/product-gallery.tsx +39 -0
- package/dist/create-app-templates/ecommerce/data/mock-catalog.json +173 -0
- package/dist/create-app-templates/ecommerce/eslint.config.mjs +18 -0
- package/dist/create-app-templates/ecommerce/lib/cart/cookie.ts +40 -0
- package/dist/create-app-templates/ecommerce/lib/cart/normalize.ts +32 -0
- package/dist/create-app-templates/ecommerce/lib/cart/parse-cart-request.ts +56 -0
- package/dist/create-app-templates/ecommerce/lib/cart/route-helpers.ts +17 -0
- package/dist/create-app-templates/ecommerce/lib/cart/select-provider.ts +44 -0
- package/dist/create-app-templates/ecommerce/lib/cart/server-cart.ts +135 -0
- package/dist/create-app-templates/ecommerce/lib/cart/sync-on-login.server.ts +34 -0
- package/dist/create-app-templates/ecommerce/lib/cart/use-cart.tsx +151 -0
- package/dist/create-app-templates/ecommerce/lib/checkout/checkout-errors.ts +22 -0
- package/dist/create-app-templates/ecommerce/lib/checkout/checkout-provider.ts +28 -0
- package/dist/create-app-templates/ecommerce/lib/checkout/parse-checkout-payload.ts +86 -0
- package/dist/create-app-templates/ecommerce/lib/checkout/start-checkout.ts +73 -0
- package/dist/create-app-templates/ecommerce/lib/checkout/types.ts +6 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/adapters/mock.ts +346 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/adapters/software-mappers.ts +312 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/adapters/software.ts +930 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/product-summary.ts +37 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/provider.server.ts +60 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/provider.ts +96 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/stock.ts +37 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/types.ts +208 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/variant-selection.ts +23 -0
- package/dist/create-app-templates/ecommerce/lib/customer/auth-actions.ts +131 -0
- package/dist/create-app-templates/ecommerce/lib/customer/cart-sync.ts +44 -0
- package/dist/create-app-templates/ecommerce/lib/customer/client.server.ts +109 -0
- package/dist/create-app-templates/ecommerce/lib/customer/current-customer.ts +15 -0
- package/dist/create-app-templates/ecommerce/lib/customer/route-guard.ts +58 -0
- package/dist/create-app-templates/ecommerce/lib/customer/route-helpers.ts +75 -0
- package/dist/create-app-templates/ecommerce/lib/customer/session.ts +108 -0
- package/dist/create-app-templates/ecommerce/lib/format.ts +7 -0
- package/dist/create-app-templates/ecommerce/lib/payment/adapters/mock.ts +84 -0
- package/dist/create-app-templates/ecommerce/lib/payment/adapters/portone.ts +254 -0
- package/dist/create-app-templates/ecommerce/lib/payment/adapters/tosspayments.ts +287 -0
- package/dist/create-app-templates/ecommerce/lib/payment/amount-gate.ts +86 -0
- package/dist/create-app-templates/ecommerce/lib/payment/provider.server.ts +51 -0
- package/dist/create-app-templates/ecommerce/lib/payment/provider.ts +18 -0
- package/dist/create-app-templates/ecommerce/lib/payment/sync-order-payment.ts +96 -0
- package/dist/create-app-templates/ecommerce/lib/payment/types.ts +71 -0
- package/dist/create-app-templates/ecommerce/lib/server-only-guard.ts +20 -0
- package/dist/create-app-templates/ecommerce/next-env.d.ts +6 -0
- package/dist/create-app-templates/ecommerce/next.config.ts +16 -0
- package/dist/create-app-templates/ecommerce/package.json +33 -0
- package/dist/create-app-templates/ecommerce/postcss.config.mjs +7 -0
- package/dist/create-app-templates/ecommerce/tests/customer-auth.test.ts +263 -0
- package/dist/create-app-templates/ecommerce/tests/customer-cart.test.ts +401 -0
- package/dist/create-app-templates/ecommerce/tests/domain.test.ts +1632 -0
- package/dist/create-app-templates/ecommerce/tsconfig.json +35 -0
- package/dist/create-app-templates/registry.json +66 -0
- package/dist/create-app.d.ts +40 -0
- package/dist/create-app.js +652 -0
- package/dist/create-app.js.map +1 -0
- package/dist/detect-Bjxp9wcS.d.ts +13 -0
- package/dist/file-ops.d.ts +21 -0
- package/dist/file-ops.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +40 -0
- package/dist/init.js +4 -3
- package/dist/templates.d.ts +27 -0
- package/dist/templates.js +1 -1
- package/package.json +18 -3
- package/dist/chunk-ENQSB4OF.js.map +0 -1
- package/dist/chunk-UA7WNT2F.js.map +0 -1
|
@@ -4,6 +4,9 @@ import {
|
|
|
4
4
|
generateClaudeMd,
|
|
5
5
|
getSkillFiles
|
|
6
6
|
} from "./chunk-Q6MSORYN.js";
|
|
7
|
+
import {
|
|
8
|
+
startBrowserAuth
|
|
9
|
+
} from "./chunk-STM4DKVZ.js";
|
|
7
10
|
import {
|
|
8
11
|
chmodSecretFile,
|
|
9
12
|
readEnvValue,
|
|
@@ -11,7 +14,7 @@ import {
|
|
|
11
14
|
setEnvValue,
|
|
12
15
|
writeEnvFile,
|
|
13
16
|
writeSecretGlobalConfig
|
|
14
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-4LHYICUL.js";
|
|
15
18
|
import {
|
|
16
19
|
CODEX_MCP_SECTION_MARKER,
|
|
17
20
|
getAnalyticsTemplate,
|
|
@@ -23,14 +26,14 @@ import {
|
|
|
23
26
|
getMcpServerEntry,
|
|
24
27
|
getQueryProviderTemplate,
|
|
25
28
|
getServerTemplate
|
|
26
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-NJ4X7VNK.js";
|
|
27
30
|
|
|
28
31
|
// src/init.ts
|
|
29
32
|
import fs2 from "fs";
|
|
30
33
|
import path2 from "path";
|
|
31
34
|
import os from "os";
|
|
32
35
|
import { execSync } from "child_process";
|
|
33
|
-
import
|
|
36
|
+
import pc from "picocolors";
|
|
34
37
|
import prompts from "prompts";
|
|
35
38
|
|
|
36
39
|
// src/detect.ts
|
|
@@ -117,183 +120,6 @@ function getPublishableKeyEnvVar(env) {
|
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
|
|
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
123
|
// src/init.ts
|
|
298
124
|
var SECRET_KEY_ENV_VAR = "SOFTWARE_SECRET_KEY";
|
|
299
125
|
async function init(cwd, info, answers, deps = {}) {
|
|
@@ -388,7 +214,7 @@ async function init(cwd, info, answers, deps = {}) {
|
|
|
388
214
|
}
|
|
389
215
|
} catch (err) {
|
|
390
216
|
console.log(
|
|
391
|
-
|
|
217
|
+
pc.yellow(" Browser auth skipped:"),
|
|
392
218
|
err instanceof Error ? err.message : String(err)
|
|
393
219
|
);
|
|
394
220
|
}
|
|
@@ -418,24 +244,24 @@ function installDeps(cwd, pm, hasSdk, hasReactQuery, wantsReactQuery) {
|
|
|
418
244
|
const toInstall = allDeps.filter((d) => d.needed && !d.installed).map((d) => d.name);
|
|
419
245
|
const fullCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), fullList);
|
|
420
246
|
if (toInstall.length === 0) {
|
|
421
|
-
console.log(
|
|
247
|
+
console.log(pc.dim(` Dependencies already installed: ${fullList.join(", ")}`));
|
|
422
248
|
return { installFailed: false, installSkipped: true, installCmd: fullCmd };
|
|
423
249
|
}
|
|
424
250
|
const addCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), toInstall);
|
|
425
|
-
console.log(
|
|
251
|
+
console.log(pc.dim(` Installing ${toInstall.join(" and ")}...`));
|
|
426
252
|
const wsPatched = pm === "pnpm" && patchPnpmWorkspace(cwd);
|
|
427
253
|
let installFailed = false;
|
|
428
254
|
try {
|
|
429
255
|
execSync(addCmd, { cwd, stdio: "pipe" });
|
|
430
|
-
console.log(
|
|
256
|
+
console.log(pc.green(" Installed"), toInstall.join(", "));
|
|
431
257
|
} catch (error) {
|
|
432
258
|
installFailed = true;
|
|
433
259
|
const err = error;
|
|
434
260
|
const msg = String(err.stderr || "").trim() || String(err.stdout || "").trim() || String(error);
|
|
435
|
-
console.log(
|
|
261
|
+
console.log(pc.yellow(" Install failed \u2014 continuing with scaffolding"));
|
|
436
262
|
const firstLines = msg.split("\n").slice(0, 3).map((l) => ` ${l}`).join("\n");
|
|
437
|
-
if (firstLines) console.log(
|
|
438
|
-
console.log(
|
|
263
|
+
if (firstLines) console.log(pc.dim(firstLines));
|
|
264
|
+
console.log(pc.dim(` Run manually: ${addCmd}`));
|
|
439
265
|
} finally {
|
|
440
266
|
if (wsPatched) restorePnpmWorkspace(cwd);
|
|
441
267
|
}
|
|
@@ -468,8 +294,8 @@ async function planConflictsAndEnv(cwd, baseDir, env, answers) {
|
|
|
468
294
|
const conflicts = candidates.filter((p) => fs2.existsSync(p));
|
|
469
295
|
let policy = "skip";
|
|
470
296
|
if (conflicts.length > 0) {
|
|
471
|
-
console.log(
|
|
472
|
-
for (const c of conflicts) console.log(
|
|
297
|
+
console.log(pc.yellow(` ${conflicts.length} file(s) already exist:`));
|
|
298
|
+
for (const c of conflicts) console.log(pc.dim(` ${path2.relative(cwd, c)}`));
|
|
473
299
|
const { selected } = await prompts({
|
|
474
300
|
type: "select",
|
|
475
301
|
name: "selected",
|
|
@@ -516,12 +342,12 @@ async function writeFileWithPolicy(cwd, filePath, content, policy) {
|
|
|
516
342
|
if (!fs2.existsSync(filePath)) {
|
|
517
343
|
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
518
344
|
fs2.writeFileSync(filePath, content);
|
|
519
|
-
console.log(
|
|
345
|
+
console.log(pc.green(" Created"), rel);
|
|
520
346
|
return;
|
|
521
347
|
}
|
|
522
348
|
const existing = fs2.readFileSync(filePath, "utf-8");
|
|
523
349
|
if (existing === content) {
|
|
524
|
-
console.log(
|
|
350
|
+
console.log(pc.dim(" Unchanged"), rel);
|
|
525
351
|
return;
|
|
526
352
|
}
|
|
527
353
|
let shouldWrite = false;
|
|
@@ -538,9 +364,9 @@ async function writeFileWithPolicy(cwd, filePath, content, policy) {
|
|
|
538
364
|
}
|
|
539
365
|
if (shouldWrite) {
|
|
540
366
|
fs2.writeFileSync(filePath, content);
|
|
541
|
-
console.log(
|
|
367
|
+
console.log(pc.green(" Overwrote"), rel);
|
|
542
368
|
} else {
|
|
543
|
-
console.log(
|
|
369
|
+
console.log(pc.yellow(" Skipped"), rel, pc.dim("(already exists)"));
|
|
544
370
|
}
|
|
545
371
|
}
|
|
546
372
|
async function writeEnv(cwd, envFile, publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar, policy, fromBrowserAuth = false) {
|
|
@@ -554,7 +380,7 @@ async function writeEnv(cwd, envFile, publishableKey, secretKey, publishableKeyE
|
|
|
554
380
|
if (!fs2.existsSync(envPath)) {
|
|
555
381
|
const initial = getEnvContent(publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar);
|
|
556
382
|
writeEnvFile(envPath, initial.trimStart());
|
|
557
|
-
console.log(
|
|
383
|
+
console.log(pc.green(" Created"), envFile);
|
|
558
384
|
return;
|
|
559
385
|
}
|
|
560
386
|
let content = fs2.readFileSync(envPath, "utf-8");
|
|
@@ -597,13 +423,13 @@ async function writeEnv(cwd, envFile, publishableKey, secretKey, publishableKeyE
|
|
|
597
423
|
if (modified) {
|
|
598
424
|
writeEnvFile(envPath, content);
|
|
599
425
|
console.log(
|
|
600
|
-
|
|
426
|
+
pc.green(" Updated"),
|
|
601
427
|
envFile,
|
|
602
|
-
fromBrowserAuth ?
|
|
428
|
+
fromBrowserAuth ? pc.dim("(SDK credentials)") : ""
|
|
603
429
|
);
|
|
604
430
|
} else {
|
|
605
431
|
chmodSecretFile(envPath);
|
|
606
|
-
console.log(
|
|
432
|
+
console.log(pc.dim(" Unchanged"), envFile);
|
|
607
433
|
}
|
|
608
434
|
}
|
|
609
435
|
function resolveMcpLocation(tool, cwd) {
|
|
@@ -658,7 +484,7 @@ function resolveMcpLocation(tool, cwd) {
|
|
|
658
484
|
async function writeMcpConfig(tool, cwd) {
|
|
659
485
|
const loc = resolveMcpLocation(tool, cwd);
|
|
660
486
|
if (!loc) {
|
|
661
|
-
console.log(
|
|
487
|
+
console.log(pc.yellow(` Skipped ${tool}`), pc.dim("(HOME not set)"));
|
|
662
488
|
return;
|
|
663
489
|
}
|
|
664
490
|
if (!loc.global) {
|
|
@@ -672,8 +498,8 @@ async function writeMcpConfig(tool, cwd) {
|
|
|
672
498
|
}
|
|
673
499
|
} catch (err) {
|
|
674
500
|
console.log(
|
|
675
|
-
|
|
676
|
-
|
|
501
|
+
pc.yellow(` Skipped ${loc.displayPath}`),
|
|
502
|
+
pc.dim(err instanceof Error ? err.message : String(err))
|
|
677
503
|
);
|
|
678
504
|
}
|
|
679
505
|
}
|
|
@@ -681,7 +507,7 @@ function writeJsonMcp(loc) {
|
|
|
681
507
|
const writeFile = loc.global ? (target, content) => writeSecretGlobalConfig(target, content) : (target, content) => fs2.writeFileSync(target, content);
|
|
682
508
|
if (!fs2.existsSync(loc.absolutePath)) {
|
|
683
509
|
writeFile(loc.absolutePath, getMcpConfigTemplate(loc.jsonClient));
|
|
684
|
-
console.log(
|
|
510
|
+
console.log(pc.green(" Created"), loc.displayPath);
|
|
685
511
|
return;
|
|
686
512
|
}
|
|
687
513
|
const rootKey = getMcpRootKey(loc.jsonClient);
|
|
@@ -689,43 +515,43 @@ function writeJsonMcp(loc) {
|
|
|
689
515
|
try {
|
|
690
516
|
existing = JSON.parse(fs2.readFileSync(loc.absolutePath, "utf-8"));
|
|
691
517
|
} catch {
|
|
692
|
-
console.log(
|
|
518
|
+
console.log(pc.yellow(" Skipped"), loc.displayPath, pc.dim("(could not parse existing file)"));
|
|
693
519
|
return;
|
|
694
520
|
}
|
|
695
521
|
const nextEntry = getMcpServerEntry(loc.jsonClient);
|
|
696
522
|
const existingServers = existing[rootKey] ?? void 0;
|
|
697
523
|
if (existingServers?.["01software"] && JSON.stringify(existingServers["01software"]) === JSON.stringify(nextEntry)) {
|
|
698
|
-
console.log(
|
|
524
|
+
console.log(pc.dim(" Unchanged"), loc.displayPath);
|
|
699
525
|
return;
|
|
700
526
|
}
|
|
701
527
|
const servers = existing[rootKey] ?? {};
|
|
702
528
|
servers["01software"] = nextEntry;
|
|
703
529
|
existing[rootKey] = servers;
|
|
704
530
|
writeFile(loc.absolutePath, JSON.stringify(existing, null, 2) + "\n");
|
|
705
|
-
console.log(
|
|
531
|
+
console.log(pc.green(" Updated"), loc.displayPath);
|
|
706
532
|
}
|
|
707
533
|
function writeTomlMcp(loc) {
|
|
708
534
|
const section = getCodexMcpTomlSection();
|
|
709
535
|
const writeFile = loc.global ? (target, content) => writeSecretGlobalConfig(target, content) : (target, content) => fs2.writeFileSync(target, content);
|
|
710
536
|
if (!fs2.existsSync(loc.absolutePath)) {
|
|
711
537
|
writeFile(loc.absolutePath, section.trimStart());
|
|
712
|
-
console.log(
|
|
538
|
+
console.log(pc.green(" Created"), loc.displayPath);
|
|
713
539
|
return;
|
|
714
540
|
}
|
|
715
541
|
const existing = fs2.readFileSync(loc.absolutePath, "utf-8");
|
|
716
542
|
if (!existing.includes(CODEX_MCP_SECTION_MARKER)) {
|
|
717
543
|
const sep = existing.endsWith("\n") ? "" : "\n";
|
|
718
544
|
writeFile(loc.absolutePath, existing + sep + section);
|
|
719
|
-
console.log(
|
|
545
|
+
console.log(pc.green(" Updated"), loc.displayPath);
|
|
720
546
|
return;
|
|
721
547
|
}
|
|
722
548
|
const replaced = replaceTomlMcpSection(existing, section);
|
|
723
549
|
if (replaced === existing) {
|
|
724
|
-
console.log(
|
|
550
|
+
console.log(pc.dim(" Unchanged"), loc.displayPath);
|
|
725
551
|
return;
|
|
726
552
|
}
|
|
727
553
|
writeFile(loc.absolutePath, replaced);
|
|
728
|
-
console.log(
|
|
554
|
+
console.log(pc.green(" Updated"), loc.displayPath);
|
|
729
555
|
}
|
|
730
556
|
function addToGitignore(cwd, tools) {
|
|
731
557
|
const entries = [];
|
|
@@ -744,7 +570,7 @@ function addToGitignore(cwd, tools) {
|
|
|
744
570
|
} else {
|
|
745
571
|
fs2.writeFileSync(gitignorePath, content.trimStart());
|
|
746
572
|
}
|
|
747
|
-
console.log(
|
|
573
|
+
console.log(pc.green(" Updated"), ".gitignore", pc.dim(`(added ${toAdd.join(", ")})`));
|
|
748
574
|
}
|
|
749
575
|
async function writeClaudeDocs(cwd, publishableKey, secretKey, tenantName, policy) {
|
|
750
576
|
let ctx = {
|
|
@@ -770,20 +596,20 @@ async function writeClaudeDocs(cwd, publishableKey, secretKey, tenantName, polic
|
|
|
770
596
|
const contextPath = path2.join(softwareDir, "context.md");
|
|
771
597
|
const contextExists = fs2.existsSync(contextPath);
|
|
772
598
|
fs2.writeFileSync(contextPath, generateClaudeMd(ctx));
|
|
773
|
-
console.log(
|
|
599
|
+
console.log(pc.green(contextExists ? " Updated" : " Created"), ".claude/01software/context.md");
|
|
774
600
|
const claudeMdPath = path2.join(claudeDir, "CLAUDE.md");
|
|
775
601
|
const importLine = "@.claude/01software/context.md";
|
|
776
602
|
if (!fs2.existsSync(claudeMdPath)) {
|
|
777
603
|
fs2.writeFileSync(claudeMdPath, importLine + "\n");
|
|
778
|
-
console.log(
|
|
604
|
+
console.log(pc.green(" Created"), ".claude/CLAUDE.md");
|
|
779
605
|
} else {
|
|
780
606
|
const existing = fs2.readFileSync(claudeMdPath, "utf-8");
|
|
781
607
|
if (!existing.includes(importLine)) {
|
|
782
608
|
const prefix = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
783
609
|
fs2.appendFileSync(claudeMdPath, prefix + importLine + "\n");
|
|
784
|
-
console.log(
|
|
610
|
+
console.log(pc.green(" Updated"), ".claude/CLAUDE.md", pc.dim("(added @import)"));
|
|
785
611
|
} else {
|
|
786
|
-
console.log(
|
|
612
|
+
console.log(pc.dim(" Unchanged"), ".claude/CLAUDE.md");
|
|
787
613
|
}
|
|
788
614
|
}
|
|
789
615
|
for (const { dirName, content } of getSkillFiles()) {
|
|
@@ -818,4 +644,4 @@ export {
|
|
|
818
644
|
detectProject,
|
|
819
645
|
init
|
|
820
646
|
};
|
|
821
|
-
//# sourceMappingURL=chunk-
|
|
647
|
+
//# sourceMappingURL=chunk-WDWJ73KP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/init.ts","../src/detect.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport os from 'node:os'\nimport { execSync } from 'node:child_process'\nimport pc from 'picocolors'\nimport prompts from 'prompts'\nimport type { PackageManager, ProjectEnv, ProjectInfo } from './detect'\nimport {\n needsClient,\n needsServer,\n needsReactQuery,\n getPublishableKeyEnvVar,\n} from './detect'\nimport type { AiTool, InitAnswers } from './prompts'\nimport {\n getClientTemplate,\n getAnalyticsTemplate,\n getQueryProviderTemplate,\n getServerTemplate,\n getEnvContent,\n getMcpConfigTemplate,\n getMcpRootKey,\n getMcpServerEntry,\n getCodexMcpTomlSection,\n CODEX_MCP_SECTION_MARKER,\n} from './templates'\nimport { startBrowserAuth } from './browser-auth'\nimport {\n generateClaudeMd,\n getSkillFiles,\n fetchTenantContext,\n} from './ai-docs'\nimport {\n readEnvValue,\n setEnvValue,\n replaceTomlMcpSection,\n writeEnvFile,\n chmodSecretFile,\n writeSecretGlobalConfig,\n} from './file-ops'\n\ntype ResolvedProjectInfo = Omit<ProjectInfo, 'packageManager'> & {\n packageManager: PackageManager\n}\n\nconst SECRET_KEY_ENV_VAR = 'SOFTWARE_SECRET_KEY'\n\nexport type ConflictPolicy = 'overwrite' | 'skip' | 'ask'\n\nexport interface InitResult {\n installFailed: boolean\n installSkipped: boolean\n installCmd: string\n /** Set when browser-auth or manual entry produced a publishable key the\n * caller should prefer over `answers.publishableKey` when computing\n * next-step messages. */\n publishableKey?: string\n /** Set when browser-auth or manual entry produced a secret key. */\n secretKey?: string\n /** Tenant display name from browser-auth, when available. */\n tenantName?: string\n}\n\nexport interface InitDeps {\n /** Test seam — replaces the real browser-auth flow. */\n startBrowserAuth?: typeof startBrowserAuth\n /** Test seam — skips actually invoking the package manager. */\n skipInstall?: boolean\n}\n\nexport async function init(\n cwd: string,\n info: ResolvedProjectInfo,\n answers: InitAnswers,\n deps: InitDeps = {},\n): Promise<InitResult> {\n const { packageManager, srcDir } = info\n const env = answers.env\n const baseDir = srcDir ? path.join(cwd, 'src') : cwd\n const browserAuth = deps.startBrowserAuth ?? startBrowserAuth\n\n const publishableKeyEnvVar = getPublishableKeyEnvVar(env)\n const wantsClient = needsClient(env)\n const wantsServer = needsServer(env)\n const wantsReactQuery = needsReactQuery(env)\n\n // 0. Plan: scan for conflicts and pick the env file. Both prompts are\n // front-loaded so the rest of init() doesn't interleave I/O with prompts.\n const plan = await planConflictsAndEnv(cwd, baseDir, env, answers)\n\n // 1. Install dependencies — skip whatever's already in package.json\n const installResult = deps.skipInstall\n ? {\n installFailed: false,\n installSkipped: true,\n installCmd: buildAddCmd(packageManager, hasPnpmWorkspace(cwd), [\n '@01.software/sdk',\n ]),\n }\n : installDeps(\n cwd,\n packageManager,\n info.hasSdk,\n info.hasReactQuery,\n wantsReactQuery,\n )\n\n // 2. Write lib/software/ files\n if (wantsClient || wantsReactQuery || wantsServer) {\n fs.mkdirSync(path.join(baseDir, 'lib', 'software'), { recursive: true })\n }\n const libDir = path.join(baseDir, 'lib', 'software')\n\n if (wantsClient) {\n await writeFileWithPolicy(\n cwd,\n path.join(libDir, 'client.ts'),\n getClientTemplate(env, publishableKeyEnvVar),\n plan.policy,\n )\n await writeFileWithPolicy(\n cwd,\n path.join(libDir, 'analytics.ts'),\n getAnalyticsTemplate(env, publishableKeyEnvVar),\n plan.policy,\n )\n }\n if (wantsReactQuery) {\n await writeFileWithPolicy(\n cwd,\n path.join(libDir, 'query-provider.tsx'),\n getQueryProviderTemplate(env),\n plan.policy,\n )\n }\n if (wantsServer) {\n await writeFileWithPolicy(\n cwd,\n path.join(libDir, 'server.ts'),\n getServerTemplate(env, publishableKeyEnvVar, SECRET_KEY_ENV_VAR),\n plan.policy,\n )\n }\n\n // 3. Append to env file (browser auth handles its own write in step 4)\n if (plan.envFile && answers.authMethod !== 'browser') {\n await writeEnv(\n cwd,\n plan.envFile,\n answers.publishableKey || '',\n answers.secretKey || '',\n publishableKeyEnvVar,\n wantsServer ? SECRET_KEY_ENV_VAR : null,\n plan.policy,\n )\n }\n\n // 4. Browser auth — get real credentials and force-write env\n let publishableKey = answers.publishableKey\n let secretKey = answers.secretKey\n let tenantName = ''\n\n if (answers.authMethod === 'browser' && answers.aiTools.length > 0) {\n try {\n console.log()\n const creds = await browserAuth()\n publishableKey = creds.publishableKey\n secretKey = creds.secretKey\n tenantName = creds.tenantName\n\n // Browser auth just minted fresh credentials → policy is overwrite.\n if (plan.envFile && publishableKey) {\n await writeEnv(\n cwd,\n plan.envFile,\n publishableKey,\n secretKey,\n publishableKeyEnvVar,\n wantsServer ? SECRET_KEY_ENV_VAR : null,\n 'overwrite',\n true,\n )\n }\n } catch (err) {\n console.log(\n pc.yellow(' Browser auth skipped:'),\n err instanceof Error ? err.message : String(err),\n )\n }\n }\n\n // 5. AI tool configs (MCP + Claude docs)\n if (answers.aiTools.length > 0) {\n for (const tool of answers.aiTools) {\n await writeMcpConfig(tool, cwd)\n }\n\n addToGitignore(cwd, answers.aiTools)\n\n if (answers.aiTools.includes('claude')) {\n await writeClaudeDocs(cwd, publishableKey, secretKey, tenantName, plan.policy)\n }\n }\n\n return {\n ...installResult,\n publishableKey: publishableKey || undefined,\n secretKey: secretKey || undefined,\n tenantName: tenantName || undefined,\n }\n}\n\n// ── Install ──────────────────────────────────────────────────────────\n\ninterface DepSpec {\n name: string\n installed: boolean\n needed: boolean\n}\n\nfunction installDeps(\n cwd: string,\n pm: PackageManager,\n hasSdk: boolean,\n hasReactQuery: boolean,\n wantsReactQuery: boolean,\n): InitResult {\n const allDeps: DepSpec[] = [\n { name: '@01.software/sdk', installed: hasSdk, needed: true },\n { name: '@tanstack/react-query', installed: hasReactQuery, needed: wantsReactQuery },\n ]\n const fullList = allDeps.filter((d) => d.needed).map((d) => d.name)\n const toInstall = allDeps.filter((d) => d.needed && !d.installed).map((d) => d.name)\n const fullCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), fullList)\n\n if (toInstall.length === 0) {\n console.log(pc.dim(` Dependencies already installed: ${fullList.join(', ')}`))\n return { installFailed: false, installSkipped: true, installCmd: fullCmd }\n }\n\n const addCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), toInstall)\n console.log(pc.dim(` Installing ${toInstall.join(' and ')}...`))\n\n const wsPatched = pm === 'pnpm' && patchPnpmWorkspace(cwd)\n let installFailed = false\n\n try {\n execSync(addCmd, { cwd, stdio: 'pipe' })\n console.log(pc.green(' Installed'), toInstall.join(', '))\n } catch (error) {\n installFailed = true\n const err = error as { stdout?: Buffer; stderr?: Buffer }\n const msg =\n String(err.stderr || '').trim() ||\n String(err.stdout || '').trim() ||\n String(error)\n console.log(pc.yellow(' Install failed — continuing with scaffolding'))\n const firstLines = msg.split('\\n').slice(0, 3).map((l) => ` ${l}`).join('\\n')\n if (firstLines) console.log(pc.dim(firstLines))\n console.log(pc.dim(` Run manually: ${addCmd}`))\n } finally {\n if (wsPatched) restorePnpmWorkspace(cwd)\n }\n\n return { installFailed, installSkipped: false, installCmd: addCmd }\n}\n\nfunction buildAddCmd(\n pm: PackageManager,\n hasPnpmWs: boolean,\n deps: string[],\n): string {\n const pkgs = deps.join(' ')\n switch (pm) {\n case 'pnpm':\n return hasPnpmWs ? `pnpm add -w ${pkgs}` : `pnpm add ${pkgs}`\n case 'yarn':\n return `yarn add ${pkgs}`\n case 'bun':\n return `bun add ${pkgs}`\n default:\n return `npm install ${pkgs}`\n }\n}\n\n// ── Conflict planning ────────────────────────────────────────────────\n\ninterface PlanResult {\n policy: ConflictPolicy\n /** Empty string when env file is not used (vanilla / edge) */\n envFile: string\n}\n\nasync function planConflictsAndEnv(\n cwd: string,\n baseDir: string,\n env: ProjectEnv,\n answers: InitAnswers,\n): Promise<PlanResult> {\n const candidates: string[] = []\n const libDir = path.join(baseDir, 'lib', 'software')\n if (needsClient(env)) candidates.push(path.join(libDir, 'client.ts'))\n if (needsReactQuery(env)) candidates.push(path.join(libDir, 'query-provider.tsx'))\n if (needsServer(env)) candidates.push(path.join(libDir, 'server.ts'))\n if (answers.aiTools.includes('claude')) {\n for (const { dirName } of getSkillFiles()) {\n candidates.push(path.join(cwd, '.claude', 'skills', dirName, 'SKILL.md'))\n }\n }\n\n const conflicts = candidates.filter((p) => fs.existsSync(p))\n\n let policy: ConflictPolicy = 'skip'\n if (conflicts.length > 0) {\n console.log(pc.yellow(` ${conflicts.length} file(s) already exist:`))\n for (const c of conflicts) console.log(pc.dim(` ${path.relative(cwd, c)}`))\n const { selected } = await prompts({\n type: 'select',\n name: 'selected',\n message: 'How should I handle existing files?',\n choices: [\n { title: 'Keep existing (skip)', value: 'skip' },\n { title: 'Overwrite all', value: 'overwrite' },\n { title: 'Ask for each', value: 'ask' },\n ],\n initial: 0,\n })\n policy = (selected as ConflictPolicy) ?? 'skip'\n }\n\n const envFile =\n env === 'vanilla' || env === 'edge' ? '' : await pickEnvFile(cwd, env)\n\n return { policy, envFile }\n}\n\nasync function pickEnvFile(cwd: string, env: ProjectEnv): Promise<string> {\n // Order matters — `.env.local` first so it's the default selection on\n // Next.js, where it's the conventional secret store.\n const candidates = ['.env.local', '.env', '.env.development']\n const existing = candidates.filter((f) => fs.existsSync(path.join(cwd, f)))\n const preferred = env === 'nextjs' ? '.env.local' : '.env'\n\n if (existing.length === 0) return preferred\n if (existing.length === 1 && existing[0] === preferred) return existing[0]\n\n // Multiple files exist OR the only existing file isn't the preferred default.\n const options = Array.from(new Set([...existing, preferred]))\n const choices = options.map((f) => ({\n title: f,\n description: existing.includes(f) ? 'exists' : 'create',\n value: f,\n }))\n const initial = Math.max(\n 0,\n choices.findIndex((c) => c.value === preferred),\n )\n const { file } = await prompts({\n type: 'select',\n name: 'file',\n message: 'Which env file should I write SDK credentials to?',\n choices,\n initial,\n })\n return file ?? preferred\n}\n\n// ── Generic file write ───────────────────────────────────────────────\n\nasync function writeFileWithPolicy(\n cwd: string,\n filePath: string,\n content: string,\n policy: ConflictPolicy,\n): Promise<void> {\n const rel = path.relative(cwd, filePath)\n if (!fs.existsSync(filePath)) {\n fs.mkdirSync(path.dirname(filePath), { recursive: true })\n fs.writeFileSync(filePath, content)\n console.log(pc.green(' Created'), rel)\n return\n }\n\n const existing = fs.readFileSync(filePath, 'utf-8')\n if (existing === content) {\n console.log(pc.dim(' Unchanged'), rel)\n return\n }\n\n let shouldWrite = false\n if (policy === 'overwrite') {\n shouldWrite = true\n } else if (policy === 'ask') {\n const { confirm } = await prompts({\n type: 'confirm',\n name: 'confirm',\n message: `Overwrite ${rel}?`,\n initial: false,\n })\n shouldWrite = !!confirm\n }\n\n if (shouldWrite) {\n fs.writeFileSync(filePath, content)\n console.log(pc.green(' Overwrote'), rel)\n } else {\n console.log(pc.yellow(' Skipped'), rel, pc.dim('(already exists)'))\n }\n}\n\n// ── Env file write ───────────────────────────────────────────────────\n\nasync function writeEnv(\n cwd: string,\n envFile: string,\n publishableKey: string,\n secretKey: string,\n publishableKeyEnvVar: string,\n secretKeyEnvVar: string | null,\n policy: ConflictPolicy,\n fromBrowserAuth = false,\n): Promise<void> {\n const envPath = path.join(cwd, envFile)\n\n const targets: { name: string; value: string }[] = [\n { name: publishableKeyEnvVar, value: publishableKey },\n ]\n if (secretKeyEnvVar) {\n targets.push({ name: secretKeyEnvVar, value: secretKey })\n }\n\n if (!fs.existsSync(envPath)) {\n const initial = getEnvContent(publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar)\n writeEnvFile(envPath, initial.trimStart())\n console.log(pc.green(' Created'), envFile)\n return\n }\n\n let content = fs.readFileSync(envPath, 'utf-8')\n let modified = false\n let appendedHeader = false\n const headerAlreadyPresent = targets.some(\n (t) => readEnvValue(content, t.name) !== null,\n )\n\n for (const { name, value } of targets) {\n const existing = readEnvValue(content, name)\n\n if (existing === null) {\n // Append. Add the `# 01.software` section header on first append, but\n // only if no other 01.software keys are already in the file.\n if (!headerAlreadyPresent && !appendedHeader) {\n if (content.length > 0 && !content.endsWith('\\n')) content += '\\n'\n content += '\\n# 01.software\\n'\n appendedHeader = true\n }\n content = setEnvValue(content, name, value)\n modified = true\n continue\n }\n\n if (existing === value) continue\n // Don't overwrite an existing real value with empty (e.g. user skipped key entry).\n if (!value) continue\n\n let shouldOverwrite = false\n if (policy === 'overwrite') {\n shouldOverwrite = true\n } else if (policy === 'ask') {\n const { confirm } = await prompts({\n type: 'confirm',\n name: 'confirm',\n message: `${name} already set in ${envFile}. Overwrite?`,\n initial: fromBrowserAuth,\n })\n shouldOverwrite = !!confirm\n }\n\n if (shouldOverwrite) {\n content = setEnvValue(content, name, value)\n modified = true\n }\n }\n\n if (modified) {\n writeEnvFile(envPath, content)\n console.log(\n pc.green(' Updated'),\n envFile,\n fromBrowserAuth ? pc.dim('(SDK credentials)') : '',\n )\n } else {\n // Tighten mode on existing files we did not modify, so re-runs harden\n // permissions on env files created by an earlier looser version.\n chmodSecretFile(envPath)\n console.log(pc.dim(' Unchanged'), envFile)\n }\n}\n\n// ── MCP targets registry ─────────────────────────────────────────────\n\ntype McpFormat = 'json' | 'toml'\ntype McpJsonClient = 'generic' | 'windsurf' | 'vscode'\ninterface McpLocation {\n kind: McpFormat\n absolutePath: string\n jsonClient?: McpJsonClient\n displayPath: string\n gitignoreEntry: string | null // null = global path, not worth ignoring\n /** Global config under user HOME — apply 0o600 mode, atomic rename, and\n * symlink/hardlink rejection (secret-handling caveats in\n * `packages/init/AGENTS.md`). */\n global?: boolean\n}\n\nfunction resolveMcpLocation(tool: AiTool, cwd: string): McpLocation | null {\n const home = os.homedir()\n\n switch (tool) {\n case 'claude':\n return {\n kind: 'json',\n absolutePath: path.join(cwd, '.mcp.json'),\n displayPath: '.mcp.json',\n gitignoreEntry: '.mcp.json',\n }\n case 'cursor':\n return {\n kind: 'json',\n absolutePath: path.join(cwd, '.cursor', 'mcp.json'),\n displayPath: '.cursor/mcp.json',\n gitignoreEntry: '.cursor/mcp.json',\n }\n case 'vscode':\n return {\n kind: 'json',\n absolutePath: path.join(cwd, '.vscode', 'mcp.json'),\n jsonClient: 'vscode',\n displayPath: '.vscode/mcp.json',\n gitignoreEntry: '.vscode/mcp.json',\n }\n case 'windsurf': {\n if (!home) return null\n const p = path.join(home, '.codeium', 'windsurf', 'mcp_config.json')\n return {\n kind: 'json',\n absolutePath: p,\n jsonClient: 'windsurf',\n displayPath: p,\n gitignoreEntry: null,\n global: true,\n }\n }\n case 'codex': {\n if (!home) return null\n const p = path.join(home, '.codex', 'config.toml')\n return { kind: 'toml', absolutePath: p, displayPath: p, gitignoreEntry: null, global: true }\n }\n case 'gemini': {\n if (!home) return null\n const p = path.join(home, '.gemini', 'settings.json')\n return { kind: 'json', absolutePath: p, displayPath: p, gitignoreEntry: null, global: true }\n }\n }\n}\n\nasync function writeMcpConfig(\n tool: AiTool,\n cwd: string,\n): Promise<void> {\n const loc = resolveMcpLocation(tool, cwd)\n if (!loc) {\n console.log(pc.yellow(` Skipped ${tool}`), pc.dim('(HOME not set)'))\n return\n }\n\n // For global secret-bearing config, parent-dir creation with 0o700 is\n // handled inside `writeSecretGlobalConfig`. Repo-local writes (Claude Code,\n // Cursor, VS Code) keep loose mkdir — they only contain OAuth/discovery URLs.\n if (!loc.global) {\n fs.mkdirSync(path.dirname(loc.absolutePath), { recursive: true })\n }\n\n try {\n if (loc.kind === 'json') {\n writeJsonMcp(loc)\n } else {\n writeTomlMcp(loc)\n }\n } catch (err) {\n console.log(\n pc.yellow(` Skipped ${loc.displayPath}`),\n pc.dim(err instanceof Error ? err.message : String(err)),\n )\n }\n}\n\nfunction writeJsonMcp(loc: McpLocation): void {\n const writeFile = loc.global\n ? (target: string, content: string) => writeSecretGlobalConfig(target, content)\n : (target: string, content: string) => fs.writeFileSync(target, content)\n\n if (!fs.existsSync(loc.absolutePath)) {\n writeFile(loc.absolutePath, getMcpConfigTemplate(loc.jsonClient))\n console.log(pc.green(' Created'), loc.displayPath)\n return\n }\n\n // VS Code uses `servers` as the config root; all other JSON clients use `mcpServers`.\n const rootKey = getMcpRootKey(loc.jsonClient)\n\n let existing: Record<string, unknown>\n try {\n existing = JSON.parse(fs.readFileSync(loc.absolutePath, 'utf-8'))\n } catch {\n console.log(pc.yellow(' Skipped'), loc.displayPath, pc.dim('(could not parse existing file)'))\n return\n }\n\n const nextEntry = getMcpServerEntry(loc.jsonClient)\n const existingServers = (existing[rootKey] as Record<string, unknown> | undefined) ?? undefined\n if (\n existingServers?.['01software'] &&\n JSON.stringify(existingServers['01software']) === JSON.stringify(nextEntry)\n ) {\n console.log(pc.dim(' Unchanged'), loc.displayPath)\n return\n }\n\n const servers = (existing[rootKey] as Record<string, unknown> | undefined) ?? {}\n servers['01software'] = nextEntry\n existing[rootKey] = servers\n writeFile(loc.absolutePath, JSON.stringify(existing, null, 2) + '\\n')\n console.log(pc.green(' Updated'), loc.displayPath)\n}\n\nfunction writeTomlMcp(loc: McpLocation): void {\n const section = getCodexMcpTomlSection()\n const writeFile = loc.global\n ? (target: string, content: string) => writeSecretGlobalConfig(target, content)\n : (target: string, content: string) => fs.writeFileSync(target, content)\n\n if (!fs.existsSync(loc.absolutePath)) {\n writeFile(loc.absolutePath, section.trimStart())\n console.log(pc.green(' Created'), loc.displayPath)\n return\n }\n\n const existing = fs.readFileSync(loc.absolutePath, 'utf-8')\n\n if (!existing.includes(CODEX_MCP_SECTION_MARKER)) {\n const sep = existing.endsWith('\\n') ? '' : '\\n'\n // Atomic rewrite (read-modify-write) — `appendFileSync` would bypass the\n // symlink/hardlink guards and the 0o600 mode of `writeSecretGlobalConfig`.\n writeFile(loc.absolutePath, existing + sep + section)\n console.log(pc.green(' Updated'), loc.displayPath)\n return\n }\n\n const replaced = replaceTomlMcpSection(existing, section)\n if (replaced === existing) {\n console.log(pc.dim(' Unchanged'), loc.displayPath)\n return\n }\n\n writeFile(loc.absolutePath, replaced)\n console.log(pc.green(' Updated'), loc.displayPath)\n}\n\nfunction addToGitignore(cwd: string, tools: AiTool[]): void {\n const entries: string[] = []\n for (const tool of tools) {\n const loc = resolveMcpLocation(tool, cwd)\n if (loc?.gitignoreEntry) entries.push(loc.gitignoreEntry)\n }\n\n if (entries.length === 0) return\n\n const gitignorePath = path.join(cwd, '.gitignore')\n const existing = fs.existsSync(gitignorePath)\n ? fs.readFileSync(gitignorePath, 'utf-8')\n : ''\n const toAdd = entries.filter((e) => !existing.includes(e))\n if (toAdd.length === 0) return\n\n const content = '\\n# MCP configs\\n' + toAdd.join('\\n') + '\\n'\n if (fs.existsSync(gitignorePath)) {\n fs.appendFileSync(gitignorePath, content)\n } else {\n fs.writeFileSync(gitignorePath, content.trimStart())\n }\n console.log(pc.green(' Updated'), '.gitignore', pc.dim(`(added ${toAdd.join(', ')})`))\n}\n\nasync function writeClaudeDocs(\n cwd: string,\n publishableKey: string,\n secretKey: string,\n tenantName: string,\n policy: ConflictPolicy,\n): Promise<void> {\n let ctx = {\n tenantName: tenantName || 'Your Tenant',\n features: undefined as string[] | undefined,\n collections: undefined as string[] | undefined,\n }\n\n if (publishableKey && secretKey) {\n const fetched = await fetchTenantContext(publishableKey, secretKey)\n if (fetched) {\n ctx = {\n tenantName: fetched.tenantName || ctx.tenantName,\n features: fetched.features,\n collections: fetched.collections,\n }\n }\n }\n\n const claudeDir = path.join(cwd, '.claude')\n const softwareDir = path.join(claudeDir, '01software')\n const skillsDir = path.join(claudeDir, 'skills')\n fs.mkdirSync(softwareDir, { recursive: true })\n fs.mkdirSync(skillsDir, { recursive: true })\n\n // context.md is intentionally always overwritten so re-running init\n // refreshes tenant data; users don't customize this file.\n const contextPath = path.join(softwareDir, 'context.md')\n const contextExists = fs.existsSync(contextPath)\n fs.writeFileSync(contextPath, generateClaudeMd(ctx))\n console.log(pc.green(contextExists ? ' Updated' : ' Created'), '.claude/01software/context.md')\n\n // CLAUDE.md — append @import line (idempotent, never overwrites user content)\n const claudeMdPath = path.join(claudeDir, 'CLAUDE.md')\n const importLine = '@.claude/01software/context.md'\n if (!fs.existsSync(claudeMdPath)) {\n fs.writeFileSync(claudeMdPath, importLine + '\\n')\n console.log(pc.green(' Created'), '.claude/CLAUDE.md')\n } else {\n const existing = fs.readFileSync(claudeMdPath, 'utf-8')\n if (!existing.includes(importLine)) {\n const prefix = existing.endsWith('\\n') ? '\\n' : '\\n\\n'\n fs.appendFileSync(claudeMdPath, prefix + importLine + '\\n')\n console.log(pc.green(' Updated'), '.claude/CLAUDE.md', pc.dim('(added @import)'))\n } else {\n console.log(pc.dim(' Unchanged'), '.claude/CLAUDE.md')\n }\n }\n\n // Skill files honour the user's conflict policy (templates the user may have\n // tuned to their workflow).\n for (const { dirName, content } of getSkillFiles()) {\n const skillDir = path.join(skillsDir, dirName)\n const skillPath = path.join(skillDir, 'SKILL.md')\n fs.mkdirSync(skillDir, { recursive: true })\n await writeFileWithPolicy(cwd, skillPath, content, policy)\n }\n}\n\nconst WS_FILE = 'pnpm-workspace.yaml'\nconst WS_BACKUP = 'pnpm-workspace.yaml.bak'\n\nfunction hasPnpmWorkspace(cwd: string): boolean {\n return fs.existsSync(path.join(cwd, WS_FILE))\n}\n\nfunction patchPnpmWorkspace(cwd: string): boolean {\n const wsPath = path.join(cwd, WS_FILE)\n if (!fs.existsSync(wsPath)) return false\n const content = fs.readFileSync(wsPath, 'utf-8')\n if (content.includes('packages:')) return false\n fs.copyFileSync(wsPath, path.join(cwd, WS_BACKUP))\n fs.writeFileSync(wsPath, content.trimEnd() + '\\npackages: []\\n')\n return true\n}\n\nfunction restorePnpmWorkspace(cwd: string): void {\n const backupPath = path.join(cwd, WS_BACKUP)\n if (!fs.existsSync(backupPath)) return\n fs.copyFileSync(backupPath, path.join(cwd, WS_FILE))\n fs.unlinkSync(backupPath)\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\nexport type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun'\n\nexport type ProjectEnv =\n | 'nextjs'\n | 'react-vite'\n | 'react-cra'\n | 'vanilla'\n | 'node'\n | 'edge'\n | 'other' // Astro, Remix, SvelteKit, etc. — print guidance only\n\nexport interface ProjectInfo {\n hasPackageJson: boolean\n parseError: boolean\n env: ProjectEnv\n packageManager: PackageManager | null\n hasSdk: boolean\n hasReactQuery: boolean\n srcDir: boolean\n}\n\nexport function detectProject(cwd: string): ProjectInfo {\n const pkgPath = path.join(cwd, 'package.json')\n const hasPackageJson = fs.existsSync(pkgPath)\n\n let env: ProjectEnv = 'node'\n let hasSdk = false\n let hasReactQuery = false\n let parseError = false\n\n if (hasPackageJson) {\n let pkg: Record<string, unknown>\n try {\n pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))\n } catch {\n return {\n hasPackageJson: true,\n parseError: true,\n env: 'node',\n packageManager: null,\n hasSdk: false,\n hasReactQuery: false,\n srcDir: false,\n }\n }\n\n const deps = {\n ...((pkg.dependencies as Record<string, string>) || {}),\n ...((pkg.devDependencies as Record<string, string>) || {}),\n }\n hasSdk = '@01.software/sdk' in deps\n hasReactQuery = '@tanstack/react-query' in deps\n\n if ('next' in deps) {\n env = 'nextjs'\n } else if ('astro' in deps || '@astrojs/node' in deps) {\n env = 'other'\n } else if ('@remix-run/node' in deps || '@remix-run/react' in deps) {\n env = 'other'\n } else if ('@sveltejs/kit' in deps) {\n env = 'other'\n } else if ('react' in deps) {\n if ('vite' in deps) {\n env = 'react-vite'\n } else if ('react-scripts' in deps) {\n env = 'react-cra'\n } else {\n // React + unknown bundler (Webpack, Parcel, etc.) — leave as 'node'\n // so the prompt selector is shown\n env = 'node'\n }\n }\n // else: no react/next → 'node' (covers real Node.js, Deno, Bun, etc.)\n }\n\n // Detect package manager from lockfile\n let packageManager: PackageManager | null = null\n if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {\n packageManager = 'pnpm'\n } else if (fs.existsSync(path.join(cwd, 'yarn.lock'))) {\n packageManager = 'yarn'\n } else if (\n fs.existsSync(path.join(cwd, 'bun.lockb')) ||\n fs.existsSync(path.join(cwd, 'bun.lock'))\n ) {\n packageManager = 'bun'\n } else if (fs.existsSync(path.join(cwd, 'package-lock.json'))) {\n packageManager = 'npm'\n }\n\n // Detect src directory structure\n const srcDir =\n env === 'nextjs'\n ? fs.existsSync(path.join(cwd, 'src', 'app'))\n : fs.existsSync(path.join(cwd, 'src'))\n\n return { hasPackageJson, parseError, env, packageManager, hasSdk, hasReactQuery, srcDir }\n}\n\n/** Environments that generate a browser client file */\nexport function needsClient(env: ProjectEnv): boolean {\n return env === 'nextjs' || env === 'react-vite' || env === 'react-cra' || env === 'vanilla'\n}\n\n/** Environments that generate a server client file */\nexport function needsServer(env: ProjectEnv): boolean {\n return env === 'nextjs' || env === 'node' || env === 'edge'\n}\n\n/** Environments that generate a query-provider file */\nexport function needsReactQuery(env: ProjectEnv): boolean {\n return env === 'nextjs' || env === 'react-vite' || env === 'react-cra'\n}\n\n/** Environments that support MCP setup (need both client + secret key) */\nexport function supportsMcp(env: ProjectEnv): boolean {\n return env === 'nextjs' || env === 'node' || env === 'edge'\n}\n\n/** Get the env var name for the public publishable key */\nexport function getPublishableKeyEnvVar(env: ProjectEnv): string {\n switch (env) {\n case 'nextjs':\n return 'NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY'\n case 'react-vite':\n return 'VITE_SOFTWARE_PUBLISHABLE_KEY'\n case 'react-cra':\n return 'REACT_APP_SOFTWARE_PUBLISHABLE_KEY'\n default:\n return 'SOFTWARE_PUBLISHABLE_KEY'\n }\n}\n"],"mappings":"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","names":["fs","path","path","fs"]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<!-- BEGIN:nextjs-agent-rules -->
|
|
2
|
+
# This is NOT the Next.js you know
|
|
3
|
+
|
|
4
|
+
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
|
5
|
+
<!-- END:nextjs-agent-rules -->
|
|
6
|
+
|
|
7
|
+
# Commerce architecture (non-obvious)
|
|
8
|
+
|
|
9
|
+
- The cart is **server-authoritative** (`commerce.cart.*`), never localStorage.
|
|
10
|
+
The browser talks only to the template's own `app/api/cart*` route handlers;
|
|
11
|
+
the unguessable `cartToken` capability lives in an **HttpOnly cookie**
|
|
12
|
+
(`lib/cart/cookie.ts`) and is the guest ownership check — the SDK re-verifies
|
|
13
|
+
it per mutation. `useCart` (`lib/cart/use-cart.tsx`) holds only the rendered
|
|
14
|
+
view; it never sees the token.
|
|
15
|
+
- Checkout uses `orders.checkout({ cartId })`, not `orders.create`. Checkout
|
|
16
|
+
creates an **OPEN Checkout** (no Order/Transaction until payment confirmation);
|
|
17
|
+
`cart.totalAmount` is the single payable quote. There is **no local order
|
|
18
|
+
index** — orders resolve by PG payment id via `orders.getByPaymentId`.
|
|
19
|
+
- `pgPaymentId` is minted template-side **after** checkout (passing it to
|
|
20
|
+
checkout routes into the Toss-only Console-capture path). `confirmPayment`
|
|
21
|
+
resolves the open checkout by `orderNumber` (→ `checkoutToken`); the
|
|
22
|
+
return-path amount comes from the provider re-fetch (`getPayment`), never the
|
|
23
|
+
redirect query.
|
|
24
|
+
- The mock adapter emulates the whole cart/checkout in-memory for the
|
|
25
|
+
zero-backend demo (no files). The guest cart/checkout flow is the always-on
|
|
26
|
+
baseline; customer accounts are a `starter`+ feature layered on top.
|
|
27
|
+
<!-- scaffold:customer:start -->
|
|
28
|
+
- **Customer auth** (`lib/customer/*`, `app/api/auth/*`) is separate from the
|
|
29
|
+
guest cart and only activates when `NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY` is
|
|
30
|
+
set (the mock demo stays guest-only). It uses the **browser** SDK client
|
|
31
|
+
(`createClient` with `customer: { persist: false, token }`) — **no secret
|
|
32
|
+
key**; the customer JWT lives in an HttpOnly session cookie
|
|
33
|
+
(`appConfig.customerKey`, `lib/customer/session.ts`), set/cleared only by the
|
|
34
|
+
CSRF-guarded `app/api/auth/{login,register,logout}` routes (same-origin +
|
|
35
|
+
`application/json`, rejected hard otherwise). The cookie is set by
|
|
36
|
+
login/register and dropped by logout. `me()` returns `null` on 401, so on the
|
|
37
|
+
render path `getCurrentCustomer`/`loadCustomer` resolve an expired/invalid
|
|
38
|
+
token — or any backend error — to guest **read-only** (cookies are not
|
|
39
|
+
writable during RSC render, and a render path must never throw); the stale
|
|
40
|
+
HttpOnly cookie is overwritten on the next login or cleared on logout, so there
|
|
41
|
+
is no refresh loop. `session.ts` imports `app-config` relatively (not the `@/`
|
|
42
|
+
alias) so its pure cookie helpers stay importable under `node --test`.
|
|
43
|
+
- **Customer server cart**: a logged-in customer's cart is server-owned and
|
|
44
|
+
device-portable. `server-cart.ts` branches on the session cookie
|
|
45
|
+
(`resolveCartProvider`, `lib/cart/select-provider.ts`): logged-in cart
|
|
46
|
+
reads/writes go through a **customer-JWT commerce client** (publishable key +
|
|
47
|
+
JWT, no secret — `assertCartAccess` denies a no-`customerId` caller on a
|
|
48
|
+
customer-bound cart), so a logged-in `addItem` with no cart mints a
|
|
49
|
+
customer-bound cart via that client's `create()`, never the guest
|
|
50
|
+
`createCart()`. The customer cart provider is the **same** provider builder
|
|
51
|
+
bound to the customer client (no copy), narrowed to `CartProvider`. Checkout
|
|
52
|
+
branches on the same session cookie via `lib/checkout/checkout-provider.ts`:
|
|
53
|
+
logged-in checkout uses the customer-JWT client (publishable key + JWT, no
|
|
54
|
+
secret key) so `orders.checkout` runs under the customer identity; guest
|
|
55
|
+
checkout keeps the secret-key adapter. On
|
|
56
|
+
login/register the route calls `syncActiveCartCookieOnLogin`
|
|
57
|
+
(`lib/cart/sync-on-login.server.ts`): union a pre-login guest cart via
|
|
58
|
+
`commerce.cart.merge`, else load the customer cart via `commerce.cart.mine`,
|
|
59
|
+
then stamp the returned `cartToken` into the single active-cart cookie (clear
|
|
60
|
+
it when the customer has no cart). The cart cookie is **slaved to the
|
|
61
|
+
session**: logout (`clearSessionAndCart`) drops both cookies atomically so no
|
|
62
|
+
later session inherits a prior identity's cart handle. Pure pieces
|
|
63
|
+
(`lib/customer/cart-sync.ts`, `select-provider.ts`,
|
|
64
|
+
`lib/checkout/checkout-provider.ts`) are `node --test`-importable with fake
|
|
65
|
+
clients; the cookie-writing wrappers use the `server-only` package like
|
|
66
|
+
`cookie.ts`.
|
|
67
|
+
<!-- scaffold:customer:end -->
|
|
68
|
+
- Payment lifecycle is hardened (#1544 / I6): a shared, unconditional
|
|
69
|
+
amount-equality gate (`lib/payment/amount-gate.ts`) runs on the return,
|
|
70
|
+
success-render, and webhook paths. Both PG adapters verify-then-server-refetch
|
|
71
|
+
and reject unsigned webhooks outside `development` — but note the Toss HMAC
|
|
72
|
+
signature is **opt-in** (`TOSSPAYMENTS_WEBHOOK_SECRET`); Toss payment webhooks
|
|
73
|
+
often ship signature-less, so the **authenticated server re-fetch is the
|
|
74
|
+
load-bearing trust check**, not the signature. The return and webhook confirms
|
|
75
|
+
do NOT share one idempotency key (the return/success path keys by `paymentId`,
|
|
76
|
+
the webhook by `providerEventId`); the single `paid` transition is guaranteed
|
|
77
|
+
by the Console resolving both confirms to the same `pgPaymentId` transaction.
|
|
78
|
+
The `orderNumber↔pgPaymentId` mapping rides on the PG payment itself (PortOne
|
|
79
|
+
`customData`, Toss `metadata`) as a non-authoritative resolution hint, so a
|
|
80
|
+
webhook can resolve a checkout with no local index. Success-page reconciliation
|
|
81
|
+
runs in a route handler (`app/api/checkout/reconcile`), never RSC render.
|
|
82
|
+
Runtime-bearing modules import `lib/server-only-guard.ts` — a `typeof window`
|
|
83
|
+
hard runtime error; the `server-only` npm package is avoided in test-imported
|
|
84
|
+
modules because its `default` export throws under `node --test`. Only
|
|
85
|
+
PG-re-fetched amounts cross the confirm boundary; the Console `confirmPayment`
|
|
86
|
+
endpoint stays the authoritative quote check.
|
|
87
|
+
- Still deferred: postalCode-driven cart shipping recompute (Console
|
|
88
|
+
`recalculateCartTotals` / cart shipping-authority work, not the template).
|