@moneydevkit/create 0.3.1 → 0.3.3
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/README.md +11 -1
- package/dist/index.cjs +687 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +687 -18
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import { createORPCClient } from "@orpc/client";
|
|
|
5
5
|
import { RPCLink } from "@orpc/client/fetch";
|
|
6
6
|
import * as p from "@clack/prompts";
|
|
7
7
|
import minimist from "minimist";
|
|
8
|
-
import
|
|
9
|
-
import
|
|
8
|
+
import path6 from "path";
|
|
9
|
+
import fs5 from "fs";
|
|
10
10
|
import os from "os";
|
|
11
11
|
import open from "open";
|
|
12
12
|
import clipboard from "clipboardy";
|
|
@@ -49,6 +49,556 @@ function deriveProjectName(input, webhookUrl) {
|
|
|
49
49
|
return webhookUrl;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// src/utils/nextjs-detector.ts
|
|
53
|
+
import fs from "fs";
|
|
54
|
+
import path2 from "path";
|
|
55
|
+
import semver from "semver";
|
|
56
|
+
var NEXT_CONFIG_BASENAMES = [
|
|
57
|
+
"next.config.js",
|
|
58
|
+
"next.config.cjs",
|
|
59
|
+
"next.config.mjs",
|
|
60
|
+
"next.config.ts",
|
|
61
|
+
"next.config.mts"
|
|
62
|
+
];
|
|
63
|
+
var APP_DIR_CANDIDATES = ["app", path2.join("src", "app")];
|
|
64
|
+
var PAGES_DIR_CANDIDATES = ["pages", path2.join("src", "pages")];
|
|
65
|
+
function fileExists(target) {
|
|
66
|
+
try {
|
|
67
|
+
return fs.existsSync(target);
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function readPackageJson(pkgPath) {
|
|
73
|
+
try {
|
|
74
|
+
const content = fs.readFileSync(pkgPath, "utf8");
|
|
75
|
+
return JSON.parse(content);
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function hasNextDependency(pkg) {
|
|
81
|
+
const deps = pkg.dependencies;
|
|
82
|
+
const devDeps = pkg.devDependencies;
|
|
83
|
+
return Boolean(deps?.next || devDeps?.next);
|
|
84
|
+
}
|
|
85
|
+
function extractNextVersion(pkg) {
|
|
86
|
+
const deps = pkg.dependencies;
|
|
87
|
+
const devDeps = pkg.devDependencies;
|
|
88
|
+
return deps?.next ?? devDeps?.next ?? void 0;
|
|
89
|
+
}
|
|
90
|
+
function findNearestPackageJson(startDir) {
|
|
91
|
+
let current = path2.resolve(startDir);
|
|
92
|
+
while (true) {
|
|
93
|
+
const candidate = path2.join(current, "package.json");
|
|
94
|
+
if (fileExists(candidate)) {
|
|
95
|
+
return candidate;
|
|
96
|
+
}
|
|
97
|
+
const parent = path2.dirname(current);
|
|
98
|
+
if (parent === current) {
|
|
99
|
+
return void 0;
|
|
100
|
+
}
|
|
101
|
+
current = parent;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function findNextConfig(rootDir) {
|
|
105
|
+
for (const basename of NEXT_CONFIG_BASENAMES) {
|
|
106
|
+
const candidate = path2.join(rootDir, basename);
|
|
107
|
+
if (fileExists(candidate)) {
|
|
108
|
+
return candidate;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
function detectNextJsProject(startDir) {
|
|
114
|
+
const pkgPath = findNearestPackageJson(startDir);
|
|
115
|
+
const rootDir = pkgPath ? path2.dirname(pkgPath) : path2.resolve(startDir);
|
|
116
|
+
const pkg = pkgPath ? readPackageJson(pkgPath) : null;
|
|
117
|
+
const hasNext = pkg ? hasNextDependency(pkg) : false;
|
|
118
|
+
const nextVersion = pkg ? extractNextVersion(pkg) : void 0;
|
|
119
|
+
let versionIsSupported = true;
|
|
120
|
+
if (nextVersion) {
|
|
121
|
+
const minVersion = semver.minVersion(nextVersion);
|
|
122
|
+
if (minVersion) {
|
|
123
|
+
versionIsSupported = semver.gte(minVersion, "15.0.0");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const nextConfigPath = findNextConfig(rootDir);
|
|
127
|
+
const appDir = APP_DIR_CANDIDATES.map((candidate) => path2.join(rootDir, candidate)).find(
|
|
128
|
+
(candidate) => fileExists(candidate)
|
|
129
|
+
);
|
|
130
|
+
const pagesDir = PAGES_DIR_CANDIDATES.map((candidate) => path2.join(rootDir, candidate)).find(
|
|
131
|
+
(candidate) => fileExists(candidate)
|
|
132
|
+
);
|
|
133
|
+
const usesTypeScript = fileExists(path2.join(rootDir, "tsconfig.json")) || fileExists(path2.join(rootDir, "next-env.d.ts"));
|
|
134
|
+
const found = Boolean(hasNext || nextConfigPath || appDir || pagesDir);
|
|
135
|
+
return {
|
|
136
|
+
found,
|
|
137
|
+
rootDir: found ? rootDir : void 0,
|
|
138
|
+
nextConfigPath,
|
|
139
|
+
appDir,
|
|
140
|
+
pagesDir,
|
|
141
|
+
usesTypeScript,
|
|
142
|
+
nextVersion,
|
|
143
|
+
versionIsSupported
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/scaffold/nextjs.ts
|
|
148
|
+
import fs4 from "fs";
|
|
149
|
+
import path5 from "path";
|
|
150
|
+
import { spawn } from "child_process";
|
|
151
|
+
|
|
152
|
+
// src/utils/package-manager.ts
|
|
153
|
+
import fs2 from "fs";
|
|
154
|
+
import path3 from "path";
|
|
155
|
+
function detectPackageManager(rootDir) {
|
|
156
|
+
if (fs2.existsSync(path3.join(rootDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
157
|
+
if (fs2.existsSync(path3.join(rootDir, "yarn.lock"))) return "yarn";
|
|
158
|
+
if (fs2.existsSync(path3.join(rootDir, "bun.lockb"))) return "bun";
|
|
159
|
+
if (fs2.existsSync(path3.join(rootDir, "package-lock.json"))) return "npm";
|
|
160
|
+
return "npm";
|
|
161
|
+
}
|
|
162
|
+
function hasDependency(rootDir, depName) {
|
|
163
|
+
try {
|
|
164
|
+
const pkg = JSON.parse(fs2.readFileSync(path3.join(rootDir, "package.json"), "utf8"));
|
|
165
|
+
return Boolean(pkg.dependencies?.[depName] || pkg.devDependencies?.[depName]);
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/utils/fs-utils.ts
|
|
172
|
+
import fs3 from "fs";
|
|
173
|
+
import path4 from "path";
|
|
174
|
+
function ensureDir(filePath) {
|
|
175
|
+
const dir = path4.dirname(filePath);
|
|
176
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
177
|
+
}
|
|
178
|
+
function readFileSafe(filePath) {
|
|
179
|
+
try {
|
|
180
|
+
return fs3.readFileSync(filePath, "utf8");
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function writeFileIfAbsent(filePath, content) {
|
|
186
|
+
if (fs3.existsSync(filePath)) {
|
|
187
|
+
const existing = readFileSafe(filePath);
|
|
188
|
+
if (existing?.trim() === content.trim()) {
|
|
189
|
+
return { status: "skipped-exists", path: filePath };
|
|
190
|
+
}
|
|
191
|
+
return { status: "skipped-different", path: filePath };
|
|
192
|
+
}
|
|
193
|
+
ensureDir(filePath);
|
|
194
|
+
fs3.writeFileSync(filePath, content, "utf8");
|
|
195
|
+
return { status: "created", path: filePath };
|
|
196
|
+
}
|
|
197
|
+
function writeFileWithBackup(filePath, content) {
|
|
198
|
+
if (!fs3.existsSync(filePath)) {
|
|
199
|
+
ensureDir(filePath);
|
|
200
|
+
fs3.writeFileSync(filePath, content, "utf8");
|
|
201
|
+
return { status: "created", path: filePath };
|
|
202
|
+
}
|
|
203
|
+
const existing = readFileSafe(filePath) ?? "";
|
|
204
|
+
if (existing.trim() === content.trim()) {
|
|
205
|
+
return { status: "skipped-exists", path: filePath };
|
|
206
|
+
}
|
|
207
|
+
const backupPath = `${filePath}.mdk-backup`;
|
|
208
|
+
fs3.writeFileSync(backupPath, existing, "utf8");
|
|
209
|
+
fs3.writeFileSync(filePath, content, "utf8");
|
|
210
|
+
return { status: "updated-with-backup", path: filePath, backupPath };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/scaffold/nextjs.ts
|
|
214
|
+
function findExistingConfig(rootDir, preferred) {
|
|
215
|
+
if (preferred && fs4.existsSync(preferred)) return preferred;
|
|
216
|
+
const candidates = [
|
|
217
|
+
"next.config.js",
|
|
218
|
+
"next.config.cjs",
|
|
219
|
+
"next.config.mjs",
|
|
220
|
+
"next.config.ts",
|
|
221
|
+
"next.config.mts"
|
|
222
|
+
];
|
|
223
|
+
for (const candidate of candidates) {
|
|
224
|
+
const fullPath = path5.join(rootDir, candidate);
|
|
225
|
+
if (fs4.existsSync(fullPath)) {
|
|
226
|
+
return fullPath;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return void 0;
|
|
230
|
+
}
|
|
231
|
+
async function installNextjsPackage(rootDir, packageManager) {
|
|
232
|
+
if (hasDependency(rootDir, "@moneydevkit/nextjs")) {
|
|
233
|
+
return { installed: false, skipped: true };
|
|
234
|
+
}
|
|
235
|
+
const commandForPm = {
|
|
236
|
+
pnpm: ["pnpm", ["add", "@moneydevkit/nextjs"]],
|
|
237
|
+
yarn: ["yarn", ["add", "@moneydevkit/nextjs"]],
|
|
238
|
+
npm: ["npm", ["install", "@moneydevkit/nextjs"]],
|
|
239
|
+
bun: ["bun", ["add", "@moneydevkit/nextjs"]]
|
|
240
|
+
};
|
|
241
|
+
const [cmd, args] = commandForPm[packageManager];
|
|
242
|
+
await new Promise((resolve, reject) => {
|
|
243
|
+
const child = spawn(cmd, args, { stdio: "inherit", cwd: rootDir });
|
|
244
|
+
child.on("exit", (code) => {
|
|
245
|
+
if (code === 0) {
|
|
246
|
+
resolve();
|
|
247
|
+
} else {
|
|
248
|
+
reject(new Error(`${cmd} ${args.join(" ")} failed with code ${code}`));
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
child.on("error", reject);
|
|
252
|
+
});
|
|
253
|
+
return { installed: true, skipped: false };
|
|
254
|
+
}
|
|
255
|
+
function createAppRouteContent(isTypeScript) {
|
|
256
|
+
const ext = isTypeScript ? "ts" : "js";
|
|
257
|
+
if (ext === "ts") {
|
|
258
|
+
return 'export { POST } from "@moneydevkit/nextjs/server/route";\n';
|
|
259
|
+
}
|
|
260
|
+
return 'export { POST } from "@moneydevkit/nextjs/server/route";\n';
|
|
261
|
+
}
|
|
262
|
+
function createAppCheckoutPageContent(isTypeScript) {
|
|
263
|
+
if (isTypeScript) {
|
|
264
|
+
return [
|
|
265
|
+
'"use client";',
|
|
266
|
+
"",
|
|
267
|
+
'import { Checkout } from "@moneydevkit/nextjs";',
|
|
268
|
+
"",
|
|
269
|
+
"type CheckoutPageProps = { params: { id: string } };",
|
|
270
|
+
"",
|
|
271
|
+
"export default function CheckoutPage({ params }: CheckoutPageProps) {",
|
|
272
|
+
" return <Checkout id={params.id} />;",
|
|
273
|
+
"}",
|
|
274
|
+
""
|
|
275
|
+
].join("\n");
|
|
276
|
+
}
|
|
277
|
+
return [
|
|
278
|
+
'"use client";',
|
|
279
|
+
"",
|
|
280
|
+
'import { Checkout } from "@moneydevkit/nextjs";',
|
|
281
|
+
"",
|
|
282
|
+
"export default function CheckoutPage({ params }) {",
|
|
283
|
+
" return <Checkout id={params.id} />;",
|
|
284
|
+
"}",
|
|
285
|
+
""
|
|
286
|
+
].join("\n");
|
|
287
|
+
}
|
|
288
|
+
function createPagesApiRouteContent(isTypeScript) {
|
|
289
|
+
if (isTypeScript) {
|
|
290
|
+
return [
|
|
291
|
+
'import type { NextApiRequest, NextApiResponse } from "next";',
|
|
292
|
+
'import { POST as appRouteHandler } from "@moneydevkit/nextjs/server/route";',
|
|
293
|
+
"",
|
|
294
|
+
"export default async function handler(req: NextApiRequest, res: NextApiResponse) {",
|
|
295
|
+
' const url = `http://${req.headers.host ?? "localhost"}${req.url ?? "/api/mdk"}`;',
|
|
296
|
+
" const request = new Request(url, {",
|
|
297
|
+
' method: req.method || "POST",',
|
|
298
|
+
" headers: req.headers as Record<string, string>,",
|
|
299
|
+
" body:",
|
|
300
|
+
' req.method === "GET" || req.method === "HEAD"',
|
|
301
|
+
" ? undefined",
|
|
302
|
+
' : typeof req.body === "string"',
|
|
303
|
+
" ? req.body",
|
|
304
|
+
" : JSON.stringify(req.body ?? {}),",
|
|
305
|
+
" });",
|
|
306
|
+
"",
|
|
307
|
+
" const response = await appRouteHandler(request);",
|
|
308
|
+
" res.status(response.status);",
|
|
309
|
+
" response.headers.forEach((value, key) => {",
|
|
310
|
+
" res.setHeader(key, value);",
|
|
311
|
+
" });",
|
|
312
|
+
" const body = await response.arrayBuffer();",
|
|
313
|
+
" res.send(Buffer.from(body));",
|
|
314
|
+
"}",
|
|
315
|
+
""
|
|
316
|
+
].join("\n");
|
|
317
|
+
}
|
|
318
|
+
return [
|
|
319
|
+
'import { POST as appRouteHandler } from "@moneydevkit/nextjs/server/route";',
|
|
320
|
+
"",
|
|
321
|
+
"export default async function handler(req, res) {",
|
|
322
|
+
' const url = `http://${req.headers.host ?? "localhost"}${req.url ?? "/api/mdk"}`;',
|
|
323
|
+
" const request = new Request(url, {",
|
|
324
|
+
' method: req.method || "POST",',
|
|
325
|
+
" headers: req.headers,",
|
|
326
|
+
" body:",
|
|
327
|
+
' req.method === "GET" || req.method === "HEAD"',
|
|
328
|
+
" ? undefined",
|
|
329
|
+
' : typeof req.body === "string"',
|
|
330
|
+
" ? req.body",
|
|
331
|
+
" : JSON.stringify(req.body ?? {}),",
|
|
332
|
+
" });",
|
|
333
|
+
"",
|
|
334
|
+
" const response = await appRouteHandler(request);",
|
|
335
|
+
" res.status(response.status);",
|
|
336
|
+
" response.headers.forEach((value, key) => {",
|
|
337
|
+
" res.setHeader(key, value);",
|
|
338
|
+
" });",
|
|
339
|
+
" const body = await response.arrayBuffer();",
|
|
340
|
+
" res.send(Buffer.from(body));",
|
|
341
|
+
"}",
|
|
342
|
+
""
|
|
343
|
+
].join("\n");
|
|
344
|
+
}
|
|
345
|
+
function createPagesCheckoutContent(isTypeScript) {
|
|
346
|
+
if (isTypeScript) {
|
|
347
|
+
return [
|
|
348
|
+
'"use client";',
|
|
349
|
+
"",
|
|
350
|
+
'import { useRouter } from "next/router";',
|
|
351
|
+
'import { Checkout } from "@moneydevkit/nextjs";',
|
|
352
|
+
"",
|
|
353
|
+
"export default function CheckoutPage() {",
|
|
354
|
+
" const router = useRouter();",
|
|
355
|
+
" const id = Array.isArray(router.query.id)",
|
|
356
|
+
" ? router.query.id[0]",
|
|
357
|
+
" : router.query.id;",
|
|
358
|
+
"",
|
|
359
|
+
" if (!id) {",
|
|
360
|
+
" return null;",
|
|
361
|
+
" }",
|
|
362
|
+
"",
|
|
363
|
+
" return <Checkout id={id as string} />;",
|
|
364
|
+
"}",
|
|
365
|
+
""
|
|
366
|
+
].join("\n");
|
|
367
|
+
}
|
|
368
|
+
return [
|
|
369
|
+
'"use client";',
|
|
370
|
+
"",
|
|
371
|
+
'import { useRouter } from "next/router";',
|
|
372
|
+
'import { Checkout } from "@moneydevkit/nextjs";',
|
|
373
|
+
"",
|
|
374
|
+
"export default function CheckoutPage() {",
|
|
375
|
+
" const router = useRouter();",
|
|
376
|
+
" const id = Array.isArray(router.query.id)",
|
|
377
|
+
" ? router.query.id[0]",
|
|
378
|
+
" : router.query.id;",
|
|
379
|
+
"",
|
|
380
|
+
" if (!id) {",
|
|
381
|
+
" return null;",
|
|
382
|
+
" }",
|
|
383
|
+
"",
|
|
384
|
+
" return <Checkout id={id} />;",
|
|
385
|
+
"}",
|
|
386
|
+
""
|
|
387
|
+
].join("\n");
|
|
388
|
+
}
|
|
389
|
+
function isTypeScriptConfig(configPath) {
|
|
390
|
+
return configPath.endsWith(".ts") || configPath.endsWith(".mts");
|
|
391
|
+
}
|
|
392
|
+
function patchNextConfigTypes(source) {
|
|
393
|
+
let patched = source.replace(
|
|
394
|
+
/import\s+type\s+\{\s*NextConfig\s*\}\s+from\s+["']next["'];?\s*\n?/g,
|
|
395
|
+
""
|
|
396
|
+
);
|
|
397
|
+
patched = patched.replace(/:\s*NextConfig\b/g, ": NextConfigOverrides");
|
|
398
|
+
return patched;
|
|
399
|
+
}
|
|
400
|
+
function updateConfigFile(configPath) {
|
|
401
|
+
const isTs = isTypeScriptConfig(configPath);
|
|
402
|
+
const pluginImport = isTs ? 'import withMdkCheckout, { type NextConfigOverrides } from "@moneydevkit/nextjs/next-plugin";' : 'import withMdkCheckout from "@moneydevkit/nextjs/next-plugin";';
|
|
403
|
+
if (!fs4.existsSync(configPath)) {
|
|
404
|
+
const content = [
|
|
405
|
+
pluginImport,
|
|
406
|
+
"",
|
|
407
|
+
"// Wrap your existing Next.js config with withMdkCheckout to enable Money Dev Kit.",
|
|
408
|
+
"// Example: export default withMdkCheckout(yourConfig)",
|
|
409
|
+
isTs ? "const nextConfig: NextConfigOverrides = {};" : "const nextConfig = {};",
|
|
410
|
+
"",
|
|
411
|
+
"export default withMdkCheckout(nextConfig);",
|
|
412
|
+
""
|
|
413
|
+
].join("\n");
|
|
414
|
+
const writeResult = writeFileWithBackup(configPath, content);
|
|
415
|
+
return {
|
|
416
|
+
status: "created",
|
|
417
|
+
path: configPath,
|
|
418
|
+
backupPath: writeResult.status === "updated-with-backup" ? writeResult.backupPath : void 0
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
const original = readFileSafe(configPath) ?? "";
|
|
422
|
+
if (original.includes("@moneydevkit/nextjs/next-plugin") || original.includes("withMdkCheckout")) {
|
|
423
|
+
return { status: "skipped", path: configPath, reason: "already configured" };
|
|
424
|
+
}
|
|
425
|
+
if (original.includes("module.exports")) {
|
|
426
|
+
const re = /module\.exports\s*=\s*(\{[\s\S]*?\});?/;
|
|
427
|
+
const match = original.match(re);
|
|
428
|
+
if (match) {
|
|
429
|
+
const prefix = 'const withMdkCheckout = require("@moneydevkit/nextjs/next-plugin").default ?? require("@moneydevkit/nextjs/next-plugin");\n';
|
|
430
|
+
const replaced = original.replace(
|
|
431
|
+
re,
|
|
432
|
+
`module.exports = withMdkCheckout(${match[1]});`
|
|
433
|
+
);
|
|
434
|
+
const result = writeFileWithBackup(configPath, `${prefix}${replaced}`);
|
|
435
|
+
return {
|
|
436
|
+
status: "updated",
|
|
437
|
+
path: configPath,
|
|
438
|
+
backupPath: result.status === "updated-with-backup" ? result.backupPath : void 0
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (original.includes("export default")) {
|
|
443
|
+
const reDefaultObject = /export\s+default\s+(\{[\s\S]*?\});?/;
|
|
444
|
+
const objectMatch = original.match(reDefaultObject);
|
|
445
|
+
if (objectMatch) {
|
|
446
|
+
const content = [
|
|
447
|
+
pluginImport,
|
|
448
|
+
"",
|
|
449
|
+
isTs ? "const nextConfig: NextConfigOverrides = " + objectMatch[1] + ";" : "const nextConfig = " + objectMatch[1] + ";",
|
|
450
|
+
"",
|
|
451
|
+
"export default withMdkCheckout(nextConfig);",
|
|
452
|
+
""
|
|
453
|
+
].join("\n");
|
|
454
|
+
const writeResult = writeFileWithBackup(configPath, content);
|
|
455
|
+
return {
|
|
456
|
+
status: "updated",
|
|
457
|
+
path: configPath,
|
|
458
|
+
backupPath: writeResult.status === "updated-with-backup" ? writeResult.backupPath : void 0
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
const reNamed = /export\s+default\s+([a-zA-Z0-9_]+)\s*;?/;
|
|
462
|
+
const namedMatch = original.match(reNamed);
|
|
463
|
+
if (namedMatch) {
|
|
464
|
+
const name = namedMatch[1];
|
|
465
|
+
const patched = isTs && original.includes("NextConfig") ? patchNextConfigTypes(original) : original;
|
|
466
|
+
const lines = [
|
|
467
|
+
pluginImport,
|
|
468
|
+
patched.replace(reNamed, `export default withMdkCheckout(${name});`)
|
|
469
|
+
];
|
|
470
|
+
const writeResult = writeFileWithBackup(configPath, lines.join("\n"));
|
|
471
|
+
return {
|
|
472
|
+
status: "updated",
|
|
473
|
+
path: configPath,
|
|
474
|
+
backupPath: writeResult.status === "updated-with-backup" ? writeResult.backupPath : void 0
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
status: "skipped",
|
|
480
|
+
path: configPath,
|
|
481
|
+
reason: "unrecognized format; wrap your export with withMdkCheckout, e.g. export default withMdkCheckout(yourConfig)"
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
function scaffoldAppRouter(appDir, isTypeScript) {
|
|
485
|
+
const added = [];
|
|
486
|
+
const skipped = [];
|
|
487
|
+
const routePath = path5.join(
|
|
488
|
+
appDir,
|
|
489
|
+
"api",
|
|
490
|
+
"mdk",
|
|
491
|
+
`route.${isTypeScript ? "ts" : "js"}`
|
|
492
|
+
);
|
|
493
|
+
const routeResult = writeFileIfAbsent(
|
|
494
|
+
routePath,
|
|
495
|
+
createAppRouteContent(isTypeScript)
|
|
496
|
+
);
|
|
497
|
+
if (routeResult.status === "created") {
|
|
498
|
+
added.push(routeResult.path);
|
|
499
|
+
} else {
|
|
500
|
+
skipped.push(routeResult.path);
|
|
501
|
+
}
|
|
502
|
+
const pagePath = path5.join(
|
|
503
|
+
appDir,
|
|
504
|
+
"checkout",
|
|
505
|
+
"[id]",
|
|
506
|
+
`page.${isTypeScript ? "tsx" : "js"}`
|
|
507
|
+
);
|
|
508
|
+
const pageResult = writeFileIfAbsent(
|
|
509
|
+
pagePath,
|
|
510
|
+
createAppCheckoutPageContent(isTypeScript)
|
|
511
|
+
);
|
|
512
|
+
if (pageResult.status === "created") {
|
|
513
|
+
added.push(pageResult.path);
|
|
514
|
+
} else {
|
|
515
|
+
skipped.push(pageResult.path);
|
|
516
|
+
}
|
|
517
|
+
return { added, skipped };
|
|
518
|
+
}
|
|
519
|
+
function scaffoldPagesRouter(pagesDir, isTypeScript) {
|
|
520
|
+
const added = [];
|
|
521
|
+
const skipped = [];
|
|
522
|
+
const apiPath = path5.join(
|
|
523
|
+
pagesDir,
|
|
524
|
+
"api",
|
|
525
|
+
`mdk.${isTypeScript ? "ts" : "js"}`
|
|
526
|
+
);
|
|
527
|
+
const apiResult = writeFileIfAbsent(
|
|
528
|
+
apiPath,
|
|
529
|
+
createPagesApiRouteContent(isTypeScript)
|
|
530
|
+
);
|
|
531
|
+
if (apiResult.status === "created") {
|
|
532
|
+
added.push(apiResult.path);
|
|
533
|
+
} else {
|
|
534
|
+
skipped.push(apiResult.path);
|
|
535
|
+
}
|
|
536
|
+
const checkoutPath = path5.join(
|
|
537
|
+
pagesDir,
|
|
538
|
+
"checkout",
|
|
539
|
+
`[id].${isTypeScript ? "tsx" : "js"}`
|
|
540
|
+
);
|
|
541
|
+
const checkoutResult = writeFileIfAbsent(
|
|
542
|
+
checkoutPath,
|
|
543
|
+
createPagesCheckoutContent(isTypeScript)
|
|
544
|
+
);
|
|
545
|
+
if (checkoutResult.status === "created") {
|
|
546
|
+
added.push(checkoutResult.path);
|
|
547
|
+
} else {
|
|
548
|
+
skipped.push(checkoutResult.path);
|
|
549
|
+
}
|
|
550
|
+
return { added, skipped };
|
|
551
|
+
}
|
|
552
|
+
async function scaffoldNextJs(options) {
|
|
553
|
+
const { detection, jsonMode, skipInstall } = options;
|
|
554
|
+
if (!detection.rootDir) {
|
|
555
|
+
throw new Error("Next.js project root not found for scaffolding.");
|
|
556
|
+
}
|
|
557
|
+
const warnings = [];
|
|
558
|
+
const rootDir = detection.rootDir;
|
|
559
|
+
const packageManager = detectPackageManager(rootDir);
|
|
560
|
+
const installResult = skipInstall ? { installed: false, skipped: true } : await installNextjsPackage(rootDir, packageManager);
|
|
561
|
+
const configPath = findExistingConfig(rootDir, detection.nextConfigPath) ?? path5.join(rootDir, "next.config.js");
|
|
562
|
+
const configResult = updateConfigFile(configPath);
|
|
563
|
+
if (configResult.status === "skipped") {
|
|
564
|
+
warnings.push(
|
|
565
|
+
`Could not automatically update ${path5.basename(configPath)} (${configResult.reason}). Please wrap your Next.js config with withMdkCheckout manually.`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
let fileResults;
|
|
569
|
+
if (detection.appDir) {
|
|
570
|
+
fileResults = scaffoldAppRouter(detection.appDir, detection.usesTypeScript);
|
|
571
|
+
} else if (detection.pagesDir) {
|
|
572
|
+
fileResults = scaffoldPagesRouter(
|
|
573
|
+
detection.pagesDir,
|
|
574
|
+
detection.usesTypeScript
|
|
575
|
+
);
|
|
576
|
+
} else {
|
|
577
|
+
fileResults = scaffoldAppRouter(
|
|
578
|
+
path5.join(rootDir, "app"),
|
|
579
|
+
detection.usesTypeScript
|
|
580
|
+
);
|
|
581
|
+
warnings.push(
|
|
582
|
+
"No app/ or pages/ directory detected; created App Router scaffolding in app/."
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
if (!jsonMode) {
|
|
586
|
+
if (!installResult.installed && installResult.skipped) {
|
|
587
|
+
console.log("@moneydevkit/nextjs already present; skipping install.");
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return {
|
|
591
|
+
rootDir,
|
|
592
|
+
packageManager,
|
|
593
|
+
installedPackage: installResult.installed,
|
|
594
|
+
installSkipped: installResult.skipped,
|
|
595
|
+
addedFiles: fileResults.added,
|
|
596
|
+
skippedFiles: fileResults.skipped,
|
|
597
|
+
config: configResult,
|
|
598
|
+
warnings
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
52
602
|
// src/index.ts
|
|
53
603
|
var DEFAULT_BASE_URL = "https://moneydevkit.com";
|
|
54
604
|
var DEFAULT_ENV_FILE = ".env.local";
|
|
@@ -86,7 +636,7 @@ var CookieJar = class {
|
|
|
86
636
|
};
|
|
87
637
|
function parseFlags(argv) {
|
|
88
638
|
const result = minimist(argv, {
|
|
89
|
-
boolean: ["json", "no-clipboard", "no-open", "force-new-webhook", "
|
|
639
|
+
boolean: ["json", "no-clipboard", "no-open", "force-new-webhook", "scaffold-nextjs"],
|
|
90
640
|
string: [
|
|
91
641
|
"base-url",
|
|
92
642
|
"env-target",
|
|
@@ -102,14 +652,14 @@ function parseFlags(argv) {
|
|
|
102
652
|
"no-open": false,
|
|
103
653
|
json: false,
|
|
104
654
|
"force-new-webhook": false,
|
|
105
|
-
|
|
655
|
+
"scaffold-nextjs": false
|
|
106
656
|
}
|
|
107
657
|
});
|
|
108
658
|
return {
|
|
109
659
|
json: Boolean(result.json),
|
|
110
660
|
noClipboard: Boolean(result["no-clipboard"]),
|
|
111
661
|
noOpen: Boolean(result["no-open"]),
|
|
112
|
-
|
|
662
|
+
scaffoldNextjs: Boolean(result["scaffold-nextjs"]),
|
|
113
663
|
baseUrl: result["base-url"],
|
|
114
664
|
envFile: result["env-target"],
|
|
115
665
|
projectName: typeof result["project-name"] === "string" ? result["project-name"] : void 0,
|
|
@@ -119,20 +669,20 @@ function parseFlags(argv) {
|
|
|
119
669
|
};
|
|
120
670
|
}
|
|
121
671
|
function normalizeDirectory(dir) {
|
|
122
|
-
if (
|
|
123
|
-
return
|
|
672
|
+
if (path6.isAbsolute(dir)) return dir;
|
|
673
|
+
return path6.resolve(process.cwd(), dir);
|
|
124
674
|
}
|
|
125
675
|
function ensureDirectoryExists(dir) {
|
|
126
|
-
if (!
|
|
127
|
-
|
|
676
|
+
if (!fs5.existsSync(dir)) {
|
|
677
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
128
678
|
}
|
|
129
679
|
}
|
|
130
680
|
function readEnvFile(filePath) {
|
|
131
681
|
const env = /* @__PURE__ */ new Map();
|
|
132
|
-
if (!
|
|
682
|
+
if (!fs5.existsSync(filePath)) {
|
|
133
683
|
return env;
|
|
134
684
|
}
|
|
135
|
-
const contents =
|
|
685
|
+
const contents = fs5.readFileSync(filePath, "utf8");
|
|
136
686
|
for (const line of contents.split(/\r?\n/)) {
|
|
137
687
|
if (!line || line.startsWith("#")) continue;
|
|
138
688
|
const [key, ...rest] = line.split("=");
|
|
@@ -153,15 +703,24 @@ function writeEnvFile(filePath, existing, updates) {
|
|
|
153
703
|
existing.set(key, value);
|
|
154
704
|
}
|
|
155
705
|
const content = Array.from(existing.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`).join(os.EOL) + os.EOL;
|
|
156
|
-
|
|
706
|
+
fs5.writeFileSync(filePath, content, "utf8");
|
|
157
707
|
}
|
|
158
708
|
function ensureEnvFileExists(filePath) {
|
|
159
|
-
const dir =
|
|
709
|
+
const dir = path6.dirname(filePath);
|
|
160
710
|
ensureDirectoryExists(dir);
|
|
161
|
-
if (!
|
|
162
|
-
|
|
711
|
+
if (!fs5.existsSync(filePath)) {
|
|
712
|
+
fs5.writeFileSync(filePath, "", "utf8");
|
|
163
713
|
}
|
|
164
714
|
}
|
|
715
|
+
function resolveLocalEnvPath(options) {
|
|
716
|
+
const organization = options.organizationId?.trim();
|
|
717
|
+
const apiKey = options.apiKeyId?.trim();
|
|
718
|
+
if (!organization || !apiKey) {
|
|
719
|
+
return void 0;
|
|
720
|
+
}
|
|
721
|
+
const homeDir = os.homedir();
|
|
722
|
+
return path6.join(homeDir, ".mdk", organization, apiKey, ".env");
|
|
723
|
+
}
|
|
165
724
|
function isValidHttpUrl(value) {
|
|
166
725
|
if (!value) return false;
|
|
167
726
|
try {
|
|
@@ -353,7 +912,7 @@ async function main() {
|
|
|
353
912
|
}
|
|
354
913
|
envFile = envPrompt.trim() || DEFAULT_ENV_FILE;
|
|
355
914
|
}
|
|
356
|
-
const envPath =
|
|
915
|
+
const envPath = path6.join(projectDir, envFile);
|
|
357
916
|
const existingEnvValues = readEnvFile(envPath);
|
|
358
917
|
const mnemonicAlreadySet = existingEnvValues.get("MDK_MNEMONIC")?.trim();
|
|
359
918
|
if (mnemonicAlreadySet) {
|
|
@@ -404,6 +963,39 @@ async function main() {
|
|
|
404
963
|
projectName = namePrompt.trim() || void 0;
|
|
405
964
|
}
|
|
406
965
|
projectName = deriveProjectName(projectName, webhookUrl);
|
|
966
|
+
const nextJsDetection = detectNextJsProject(projectDir);
|
|
967
|
+
let shouldScaffoldNextJs = false;
|
|
968
|
+
if (flags.scaffoldNextjs) {
|
|
969
|
+
if (nextJsDetection.found) {
|
|
970
|
+
if (nextJsDetection.versionIsSupported) {
|
|
971
|
+
shouldScaffoldNextJs = true;
|
|
972
|
+
} else {
|
|
973
|
+
console.warn(
|
|
974
|
+
`Next.js version ${nextJsDetection.nextVersion ?? "unknown"} detected, but @moneydevkit/nextjs requires Next.js 15+. Skipping installation and scaffolding.`
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
} else {
|
|
978
|
+
console.warn(
|
|
979
|
+
"No NextJS app found, skipping @moneydevkit/nextjs installation. Please install manually."
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
} else if (!jsonMode && nextJsDetection.found) {
|
|
983
|
+
if (!nextJsDetection.versionIsSupported) {
|
|
984
|
+
console.warn(
|
|
985
|
+
`Next.js version ${nextJsDetection.nextVersion ?? "unknown"} detected, but @moneydevkit/nextjs requires Next.js 15+. Skipping scaffolding prompt.`
|
|
986
|
+
);
|
|
987
|
+
} else {
|
|
988
|
+
const scaffoldPrompt = await p.confirm({
|
|
989
|
+
message: `Next.js application detected at ${nextJsDetection.rootDir ?? projectDir} (version ${nextJsDetection.nextVersion ?? "unknown"}). Install and scaffold @moneydevkit/nextjs?`,
|
|
990
|
+
initialValue: true
|
|
991
|
+
});
|
|
992
|
+
if (p.isCancel(scaffoldPrompt)) {
|
|
993
|
+
p.cancel("Aborted.");
|
|
994
|
+
process.exit(1);
|
|
995
|
+
}
|
|
996
|
+
shouldScaffoldNextJs = Boolean(scaffoldPrompt);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
407
999
|
try {
|
|
408
1000
|
const result = await runDeviceFlow({
|
|
409
1001
|
flags,
|
|
@@ -421,6 +1013,15 @@ async function main() {
|
|
|
421
1013
|
const existingEnv = readEnvFile(envPath);
|
|
422
1014
|
const preview = renderEnvPreview(existingEnv, updates);
|
|
423
1015
|
writeEnvFile(envPath, existingEnv, updates);
|
|
1016
|
+
const localEnvPath = resolveLocalEnvPath({
|
|
1017
|
+
organizationId: result.credentials.organizationId,
|
|
1018
|
+
apiKeyId: result.credentials.apiKeyId
|
|
1019
|
+
});
|
|
1020
|
+
if (localEnvPath) {
|
|
1021
|
+
ensureEnvFileExists(localEnvPath);
|
|
1022
|
+
const localEnv = readEnvFile(localEnvPath);
|
|
1023
|
+
writeEnvFile(localEnvPath, localEnv, updates);
|
|
1024
|
+
}
|
|
424
1025
|
if (!jsonMode) {
|
|
425
1026
|
p.note(preview, "Env file updated");
|
|
426
1027
|
}
|
|
@@ -431,6 +1032,72 @@ async function main() {
|
|
|
431
1032
|
)
|
|
432
1033
|
);
|
|
433
1034
|
}
|
|
1035
|
+
let scaffoldSummary = null;
|
|
1036
|
+
if (shouldScaffoldNextJs && nextJsDetection.found) {
|
|
1037
|
+
try {
|
|
1038
|
+
scaffoldSummary = await scaffoldNextJs({
|
|
1039
|
+
detection: nextJsDetection,
|
|
1040
|
+
jsonMode
|
|
1041
|
+
});
|
|
1042
|
+
if (!jsonMode && scaffoldSummary) {
|
|
1043
|
+
const lines = [
|
|
1044
|
+
scaffoldSummary.installedPackage ? `Installed @moneydevkit/nextjs with ${scaffoldSummary.packageManager}.` : scaffoldSummary.installSkipped ? "@moneydevkit/nextjs already installed; skipped package install." : "Skipped @moneydevkit/nextjs installation."
|
|
1045
|
+
];
|
|
1046
|
+
if (scaffoldSummary.config) {
|
|
1047
|
+
const cfg = scaffoldSummary.config;
|
|
1048
|
+
if (cfg.status === "created") {
|
|
1049
|
+
lines.push(`Created ${cfg.path} with withMdkCheckout().`);
|
|
1050
|
+
if (cfg.backupPath) {
|
|
1051
|
+
lines.push(`Created backup at ${cfg.backupPath}.`);
|
|
1052
|
+
}
|
|
1053
|
+
} else if (cfg.status === "updated") {
|
|
1054
|
+
lines.push(`Updated ${cfg.path} with withMdkCheckout().`);
|
|
1055
|
+
if (cfg.backupPath) {
|
|
1056
|
+
lines.push(`Created backup at ${cfg.backupPath}.`);
|
|
1057
|
+
}
|
|
1058
|
+
} else {
|
|
1059
|
+
lines.push(
|
|
1060
|
+
`Could not update ${cfg.path} automatically (${cfg.reason ?? "unknown reason"}).`
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
if (scaffoldSummary.addedFiles.length > 0) {
|
|
1065
|
+
lines.push(
|
|
1066
|
+
`Added: ${scaffoldSummary.addedFiles.map((p2) => path6.relative(projectDir, p2)).join(", ")}`
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
if (scaffoldSummary.skippedFiles.length > 0) {
|
|
1070
|
+
lines.push(
|
|
1071
|
+
`Skipped existing files: ${scaffoldSummary.skippedFiles.map((p2) => path6.relative(projectDir, p2)).join(", ")}`
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
p.note(lines.join("\n"), "Next.js scaffolding");
|
|
1075
|
+
}
|
|
1076
|
+
if (scaffoldSummary?.warnings.length) {
|
|
1077
|
+
for (const warning of scaffoldSummary.warnings) {
|
|
1078
|
+
console.warn(warning);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1083
|
+
if (jsonMode) {
|
|
1084
|
+
console.error(
|
|
1085
|
+
JSON.stringify(
|
|
1086
|
+
{
|
|
1087
|
+
status: "error",
|
|
1088
|
+
error: {
|
|
1089
|
+
message: `Next.js scaffolding failed: ${message}`
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
null,
|
|
1093
|
+
2
|
|
1094
|
+
)
|
|
1095
|
+
);
|
|
1096
|
+
} else {
|
|
1097
|
+
console.warn(`Next.js scaffolding failed: ${message}`);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
434
1101
|
const summary = {
|
|
435
1102
|
projectDir,
|
|
436
1103
|
envFile: envPath,
|
|
@@ -438,7 +1105,8 @@ async function main() {
|
|
|
438
1105
|
webhookId: result.credentials.webhookId,
|
|
439
1106
|
organizationId: result.credentials.organizationId,
|
|
440
1107
|
webhookUrl: result.credentials.webhookUrl,
|
|
441
|
-
mnemonic: updates.MDK_MNEMONIC
|
|
1108
|
+
mnemonic: updates.MDK_MNEMONIC,
|
|
1109
|
+
scaffoldedNextjs: Boolean(scaffoldSummary)
|
|
442
1110
|
};
|
|
443
1111
|
if (jsonMode) {
|
|
444
1112
|
console.log(
|
|
@@ -453,7 +1121,8 @@ async function main() {
|
|
|
453
1121
|
webhookSecret: result.credentials.webhookSecret,
|
|
454
1122
|
webhookUrl: result.credentials.webhookUrl,
|
|
455
1123
|
organizationId: result.credentials.organizationId,
|
|
456
|
-
mnemonic: updates.MDK_MNEMONIC
|
|
1124
|
+
mnemonic: updates.MDK_MNEMONIC,
|
|
1125
|
+
scaffoldedNextjs: Boolean(scaffoldSummary)
|
|
457
1126
|
}
|
|
458
1127
|
},
|
|
459
1128
|
null,
|