@moneydevkit/create 0.3.2 → 0.4.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -6
- package/dist/index.cjs +514 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +513 -26
- package/dist/index.js.map +1 -1
- package/dist/templates/nextjs/app/api/mdk/route.js +1 -0
- package/dist/templates/nextjs/app/api/mdk/route.ts +1 -0
- package/dist/templates/nextjs/app/checkout/[id]/page.js +8 -0
- package/dist/templates/nextjs/app/checkout/[id]/page.tsx +8 -0
- package/dist/templates/readme-sync.test.ts +56 -0
- package/package.json +4 -2
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,391 @@ 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
|
+
var templateRoot = new URL("../templates/nextjs/", import.meta.url);
|
|
215
|
+
function readTemplate(relativePath) {
|
|
216
|
+
return fs4.readFileSync(new URL(relativePath, templateRoot), "utf8");
|
|
217
|
+
}
|
|
218
|
+
function findExistingConfig(rootDir, preferred) {
|
|
219
|
+
if (preferred && fs4.existsSync(preferred)) return preferred;
|
|
220
|
+
const candidates = [
|
|
221
|
+
"next.config.js",
|
|
222
|
+
"next.config.cjs",
|
|
223
|
+
"next.config.mjs",
|
|
224
|
+
"next.config.ts",
|
|
225
|
+
"next.config.mts"
|
|
226
|
+
];
|
|
227
|
+
for (const candidate of candidates) {
|
|
228
|
+
const fullPath = path5.join(rootDir, candidate);
|
|
229
|
+
if (fs4.existsSync(fullPath)) {
|
|
230
|
+
return fullPath;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return void 0;
|
|
234
|
+
}
|
|
235
|
+
async function installNextjsPackage(rootDir, packageManager) {
|
|
236
|
+
if (hasDependency(rootDir, "@moneydevkit/nextjs")) {
|
|
237
|
+
return { installed: false, skipped: true };
|
|
238
|
+
}
|
|
239
|
+
const commandForPm = {
|
|
240
|
+
pnpm: ["pnpm", ["add", "@moneydevkit/nextjs"]],
|
|
241
|
+
yarn: ["yarn", ["add", "@moneydevkit/nextjs"]],
|
|
242
|
+
npm: ["npm", ["install", "@moneydevkit/nextjs"]],
|
|
243
|
+
bun: ["bun", ["add", "@moneydevkit/nextjs"]]
|
|
244
|
+
};
|
|
245
|
+
const [cmd, args] = commandForPm[packageManager];
|
|
246
|
+
await new Promise((resolve, reject) => {
|
|
247
|
+
const child = spawn(cmd, args, { stdio: "inherit", cwd: rootDir });
|
|
248
|
+
child.on("exit", (code) => {
|
|
249
|
+
if (code === 0) {
|
|
250
|
+
resolve();
|
|
251
|
+
} else {
|
|
252
|
+
reject(new Error(`${cmd} ${args.join(" ")} failed with code ${code}`));
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
child.on("error", reject);
|
|
256
|
+
});
|
|
257
|
+
return { installed: true, skipped: false };
|
|
258
|
+
}
|
|
259
|
+
function createAppRouteContent(isTypeScript) {
|
|
260
|
+
return readTemplate(`app/api/mdk/route.${isTypeScript ? "ts" : "js"}`);
|
|
261
|
+
}
|
|
262
|
+
function createAppCheckoutPageContent(isTypeScript) {
|
|
263
|
+
return readTemplate(
|
|
264
|
+
`app/checkout/[id]/page.${isTypeScript ? "tsx" : "js"}`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
function isTypeScriptConfig(configPath) {
|
|
268
|
+
return configPath.endsWith(".ts") || configPath.endsWith(".mts");
|
|
269
|
+
}
|
|
270
|
+
function patchNextConfigTypes(source) {
|
|
271
|
+
let patched = source.replace(
|
|
272
|
+
/import\s+type\s+\{\s*NextConfig\s*\}\s+from\s+["']next["'];?\s*\n?/g,
|
|
273
|
+
""
|
|
274
|
+
);
|
|
275
|
+
patched = patched.replace(/:\s*NextConfig\b/g, ": NextConfigOverrides");
|
|
276
|
+
return patched;
|
|
277
|
+
}
|
|
278
|
+
function updateConfigFile(configPath) {
|
|
279
|
+
const isTs = isTypeScriptConfig(configPath);
|
|
280
|
+
const pluginImport = isTs ? 'import withMdkCheckout, { type NextConfigOverrides } from "@moneydevkit/nextjs/next-plugin";' : 'import withMdkCheckout from "@moneydevkit/nextjs/next-plugin";';
|
|
281
|
+
if (!fs4.existsSync(configPath)) {
|
|
282
|
+
const content = [
|
|
283
|
+
pluginImport,
|
|
284
|
+
"",
|
|
285
|
+
"// Wrap your existing Next.js config with withMdkCheckout to enable Money Dev Kit.",
|
|
286
|
+
"// Example: export default withMdkCheckout(yourConfig)",
|
|
287
|
+
isTs ? "const nextConfig: NextConfigOverrides = {};" : "const nextConfig = {};",
|
|
288
|
+
"",
|
|
289
|
+
"export default withMdkCheckout(nextConfig);",
|
|
290
|
+
""
|
|
291
|
+
].join("\n");
|
|
292
|
+
const writeResult = writeFileWithBackup(configPath, content);
|
|
293
|
+
return {
|
|
294
|
+
status: "created",
|
|
295
|
+
path: configPath,
|
|
296
|
+
backupPath: writeResult.status === "updated-with-backup" ? writeResult.backupPath : void 0
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const original = readFileSafe(configPath) ?? "";
|
|
300
|
+
if (original.includes("@moneydevkit/nextjs/next-plugin") || original.includes("withMdkCheckout")) {
|
|
301
|
+
return { status: "skipped", path: configPath, reason: "already configured" };
|
|
302
|
+
}
|
|
303
|
+
if (original.includes("module.exports")) {
|
|
304
|
+
const re = /module\.exports\s*=\s*(\{[\s\S]*?\});?/;
|
|
305
|
+
const match = original.match(re);
|
|
306
|
+
if (match) {
|
|
307
|
+
const prefix = 'const withMdkCheckout = require("@moneydevkit/nextjs/next-plugin").default ?? require("@moneydevkit/nextjs/next-plugin");\n';
|
|
308
|
+
const replaced = original.replace(
|
|
309
|
+
re,
|
|
310
|
+
`module.exports = withMdkCheckout(${match[1]});`
|
|
311
|
+
);
|
|
312
|
+
const result = writeFileWithBackup(configPath, `${prefix}${replaced}`);
|
|
313
|
+
return {
|
|
314
|
+
status: "updated",
|
|
315
|
+
path: configPath,
|
|
316
|
+
backupPath: result.status === "updated-with-backup" ? result.backupPath : void 0
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (original.includes("export default")) {
|
|
321
|
+
const reDefaultObject = /export\s+default\s+(\{[\s\S]*?\});?/;
|
|
322
|
+
const objectMatch = original.match(reDefaultObject);
|
|
323
|
+
if (objectMatch) {
|
|
324
|
+
const content = [
|
|
325
|
+
pluginImport,
|
|
326
|
+
"",
|
|
327
|
+
isTs ? "const nextConfig: NextConfigOverrides = " + objectMatch[1] + ";" : "const nextConfig = " + objectMatch[1] + ";",
|
|
328
|
+
"",
|
|
329
|
+
"export default withMdkCheckout(nextConfig);",
|
|
330
|
+
""
|
|
331
|
+
].join("\n");
|
|
332
|
+
const writeResult = writeFileWithBackup(configPath, content);
|
|
333
|
+
return {
|
|
334
|
+
status: "updated",
|
|
335
|
+
path: configPath,
|
|
336
|
+
backupPath: writeResult.status === "updated-with-backup" ? writeResult.backupPath : void 0
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
const reNamed = /export\s+default\s+([a-zA-Z0-9_]+)\s*;?/;
|
|
340
|
+
const namedMatch = original.match(reNamed);
|
|
341
|
+
if (namedMatch) {
|
|
342
|
+
const name = namedMatch[1];
|
|
343
|
+
const patched = isTs && original.includes("NextConfig") ? patchNextConfigTypes(original) : original;
|
|
344
|
+
const lines = [
|
|
345
|
+
pluginImport,
|
|
346
|
+
patched.replace(reNamed, `export default withMdkCheckout(${name});`)
|
|
347
|
+
];
|
|
348
|
+
const writeResult = writeFileWithBackup(configPath, lines.join("\n"));
|
|
349
|
+
return {
|
|
350
|
+
status: "updated",
|
|
351
|
+
path: configPath,
|
|
352
|
+
backupPath: writeResult.status === "updated-with-backup" ? writeResult.backupPath : void 0
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
status: "skipped",
|
|
358
|
+
path: configPath,
|
|
359
|
+
reason: "unrecognized format; wrap your export with withMdkCheckout, e.g. export default withMdkCheckout(yourConfig)"
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function scaffoldAppRouter(appDir, isTypeScript) {
|
|
363
|
+
const added = [];
|
|
364
|
+
const skipped = [];
|
|
365
|
+
const routePath = path5.join(
|
|
366
|
+
appDir,
|
|
367
|
+
"api",
|
|
368
|
+
"mdk",
|
|
369
|
+
`route.${isTypeScript ? "ts" : "js"}`
|
|
370
|
+
);
|
|
371
|
+
const routeResult = writeFileIfAbsent(
|
|
372
|
+
routePath,
|
|
373
|
+
createAppRouteContent(isTypeScript)
|
|
374
|
+
);
|
|
375
|
+
if (routeResult.status === "created") {
|
|
376
|
+
added.push(routeResult.path);
|
|
377
|
+
} else {
|
|
378
|
+
skipped.push(routeResult.path);
|
|
379
|
+
}
|
|
380
|
+
const pagePath = path5.join(
|
|
381
|
+
appDir,
|
|
382
|
+
"checkout",
|
|
383
|
+
"[id]",
|
|
384
|
+
`page.${isTypeScript ? "tsx" : "js"}`
|
|
385
|
+
);
|
|
386
|
+
const pageResult = writeFileIfAbsent(
|
|
387
|
+
pagePath,
|
|
388
|
+
createAppCheckoutPageContent(isTypeScript)
|
|
389
|
+
);
|
|
390
|
+
if (pageResult.status === "created") {
|
|
391
|
+
added.push(pageResult.path);
|
|
392
|
+
} else {
|
|
393
|
+
skipped.push(pageResult.path);
|
|
394
|
+
}
|
|
395
|
+
return { added, skipped };
|
|
396
|
+
}
|
|
397
|
+
async function scaffoldNextJs(options) {
|
|
398
|
+
const { detection, jsonMode, skipInstall } = options;
|
|
399
|
+
if (!detection.rootDir) {
|
|
400
|
+
throw new Error("Next.js project root not found for scaffolding.");
|
|
401
|
+
}
|
|
402
|
+
const warnings = [];
|
|
403
|
+
const rootDir = detection.rootDir;
|
|
404
|
+
const packageManager = detectPackageManager(rootDir);
|
|
405
|
+
const installResult = skipInstall ? { installed: false, skipped: true } : await installNextjsPackage(rootDir, packageManager);
|
|
406
|
+
const configPath = findExistingConfig(rootDir, detection.nextConfigPath) ?? path5.join(rootDir, "next.config.js");
|
|
407
|
+
const configResult = updateConfigFile(configPath);
|
|
408
|
+
if (configResult.status === "skipped") {
|
|
409
|
+
warnings.push(
|
|
410
|
+
`Could not automatically update ${path5.basename(configPath)} (${configResult.reason}). Please wrap your Next.js config with withMdkCheckout manually.`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
const appDir = detection.appDir ?? path5.join(rootDir, "app");
|
|
414
|
+
const fileResults = scaffoldAppRouter(appDir, detection.usesTypeScript);
|
|
415
|
+
if (!detection.appDir) {
|
|
416
|
+
warnings.push(
|
|
417
|
+
"No app/ directory detected; created App Router scaffolding in app/."
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
if (!jsonMode) {
|
|
421
|
+
if (!installResult.installed && installResult.skipped) {
|
|
422
|
+
console.log("@moneydevkit/nextjs already present; skipping install.");
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
rootDir,
|
|
427
|
+
packageManager,
|
|
428
|
+
installedPackage: installResult.installed,
|
|
429
|
+
installSkipped: installResult.skipped,
|
|
430
|
+
addedFiles: fileResults.added,
|
|
431
|
+
skippedFiles: fileResults.skipped,
|
|
432
|
+
config: configResult,
|
|
433
|
+
warnings
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
52
437
|
// src/index.ts
|
|
53
438
|
var DEFAULT_BASE_URL = "https://moneydevkit.com";
|
|
54
439
|
var DEFAULT_ENV_FILE = ".env.local";
|
|
@@ -86,7 +471,7 @@ var CookieJar = class {
|
|
|
86
471
|
};
|
|
87
472
|
function parseFlags(argv) {
|
|
88
473
|
const result = minimist(argv, {
|
|
89
|
-
boolean: ["json", "no-clipboard", "no-open", "force-new-webhook", "
|
|
474
|
+
boolean: ["json", "no-clipboard", "no-open", "force-new-webhook", "scaffold-nextjs"],
|
|
90
475
|
string: [
|
|
91
476
|
"base-url",
|
|
92
477
|
"env-target",
|
|
@@ -102,14 +487,14 @@ function parseFlags(argv) {
|
|
|
102
487
|
"no-open": false,
|
|
103
488
|
json: false,
|
|
104
489
|
"force-new-webhook": false,
|
|
105
|
-
|
|
490
|
+
"scaffold-nextjs": false
|
|
106
491
|
}
|
|
107
492
|
});
|
|
108
493
|
return {
|
|
109
494
|
json: Boolean(result.json),
|
|
110
495
|
noClipboard: Boolean(result["no-clipboard"]),
|
|
111
496
|
noOpen: Boolean(result["no-open"]),
|
|
112
|
-
|
|
497
|
+
scaffoldNextjs: Boolean(result["scaffold-nextjs"]),
|
|
113
498
|
baseUrl: result["base-url"],
|
|
114
499
|
envFile: result["env-target"],
|
|
115
500
|
projectName: typeof result["project-name"] === "string" ? result["project-name"] : void 0,
|
|
@@ -119,20 +504,20 @@ function parseFlags(argv) {
|
|
|
119
504
|
};
|
|
120
505
|
}
|
|
121
506
|
function normalizeDirectory(dir) {
|
|
122
|
-
if (
|
|
123
|
-
return
|
|
507
|
+
if (path6.isAbsolute(dir)) return dir;
|
|
508
|
+
return path6.resolve(process.cwd(), dir);
|
|
124
509
|
}
|
|
125
510
|
function ensureDirectoryExists(dir) {
|
|
126
|
-
if (!
|
|
127
|
-
|
|
511
|
+
if (!fs5.existsSync(dir)) {
|
|
512
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
128
513
|
}
|
|
129
514
|
}
|
|
130
515
|
function readEnvFile(filePath) {
|
|
131
516
|
const env = /* @__PURE__ */ new Map();
|
|
132
|
-
if (!
|
|
517
|
+
if (!fs5.existsSync(filePath)) {
|
|
133
518
|
return env;
|
|
134
519
|
}
|
|
135
|
-
const contents =
|
|
520
|
+
const contents = fs5.readFileSync(filePath, "utf8");
|
|
136
521
|
for (const line of contents.split(/\r?\n/)) {
|
|
137
522
|
if (!line || line.startsWith("#")) continue;
|
|
138
523
|
const [key, ...rest] = line.split("=");
|
|
@@ -153,22 +538,23 @@ function writeEnvFile(filePath, existing, updates) {
|
|
|
153
538
|
existing.set(key, value);
|
|
154
539
|
}
|
|
155
540
|
const content = Array.from(existing.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`).join(os.EOL) + os.EOL;
|
|
156
|
-
|
|
541
|
+
fs5.writeFileSync(filePath, content, "utf8");
|
|
157
542
|
}
|
|
158
543
|
function ensureEnvFileExists(filePath) {
|
|
159
|
-
const dir =
|
|
544
|
+
const dir = path6.dirname(filePath);
|
|
160
545
|
ensureDirectoryExists(dir);
|
|
161
|
-
if (!
|
|
162
|
-
|
|
546
|
+
if (!fs5.existsSync(filePath)) {
|
|
547
|
+
fs5.writeFileSync(filePath, "", "utf8");
|
|
163
548
|
}
|
|
164
549
|
}
|
|
165
550
|
function resolveLocalEnvPath(options) {
|
|
166
|
-
const
|
|
167
|
-
|
|
551
|
+
const organization = options.organizationId?.trim();
|
|
552
|
+
const apiKey = options.apiKeyId?.trim();
|
|
553
|
+
if (!organization || !apiKey) {
|
|
168
554
|
return void 0;
|
|
169
555
|
}
|
|
170
556
|
const homeDir = os.homedir();
|
|
171
|
-
return
|
|
557
|
+
return path6.join(homeDir, ".mdk", organization, apiKey, ".env");
|
|
172
558
|
}
|
|
173
559
|
function isValidHttpUrl(value) {
|
|
174
560
|
if (!value) return false;
|
|
@@ -232,7 +618,7 @@ async function runDeviceFlow(options) {
|
|
|
232
618
|
p.note(
|
|
233
619
|
[
|
|
234
620
|
`Device code: ${device.userCode}`,
|
|
235
|
-
`
|
|
621
|
+
`Domain: ${webhookUrl}`,
|
|
236
622
|
"Open the authorization page, click Authorize, then return to this terminal."
|
|
237
623
|
].join("\n"),
|
|
238
624
|
"Authorize this device"
|
|
@@ -361,7 +747,7 @@ async function main() {
|
|
|
361
747
|
}
|
|
362
748
|
envFile = envPrompt.trim() || DEFAULT_ENV_FILE;
|
|
363
749
|
}
|
|
364
|
-
const envPath =
|
|
750
|
+
const envPath = path6.join(projectDir, envFile);
|
|
365
751
|
const existingEnvValues = readEnvFile(envPath);
|
|
366
752
|
const mnemonicAlreadySet = existingEnvValues.get("MDK_MNEMONIC")?.trim();
|
|
367
753
|
if (mnemonicAlreadySet) {
|
|
@@ -388,7 +774,7 @@ async function main() {
|
|
|
388
774
|
}
|
|
389
775
|
while (!webhookUrl) {
|
|
390
776
|
const webhookInput = await p.text({
|
|
391
|
-
message: "
|
|
777
|
+
message: "Domain for your application",
|
|
392
778
|
initialValue: "https://",
|
|
393
779
|
placeholder: "https://yourapp.com",
|
|
394
780
|
validate: (value) => isValidHttpUrl(value?.trim()) ? void 0 : "Enter a valid http(s) URL (e.g. https://yourapp.com)"
|
|
@@ -412,6 +798,39 @@ async function main() {
|
|
|
412
798
|
projectName = namePrompt.trim() || void 0;
|
|
413
799
|
}
|
|
414
800
|
projectName = deriveProjectName(projectName, webhookUrl);
|
|
801
|
+
const nextJsDetection = detectNextJsProject(projectDir);
|
|
802
|
+
let shouldScaffoldNextJs = false;
|
|
803
|
+
if (flags.scaffoldNextjs) {
|
|
804
|
+
if (nextJsDetection.found) {
|
|
805
|
+
if (nextJsDetection.versionIsSupported) {
|
|
806
|
+
shouldScaffoldNextJs = true;
|
|
807
|
+
} else {
|
|
808
|
+
console.warn(
|
|
809
|
+
`Next.js version ${nextJsDetection.nextVersion ?? "unknown"} detected, but @moneydevkit/nextjs requires Next.js 15+. Skipping installation and scaffolding.`
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
} else {
|
|
813
|
+
console.warn(
|
|
814
|
+
"No NextJS app found, skipping @moneydevkit/nextjs installation. Please install manually."
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
} else if (!jsonMode && nextJsDetection.found) {
|
|
818
|
+
if (!nextJsDetection.versionIsSupported) {
|
|
819
|
+
console.warn(
|
|
820
|
+
`Next.js version ${nextJsDetection.nextVersion ?? "unknown"} detected, but @moneydevkit/nextjs requires Next.js 15+. Skipping scaffolding prompt.`
|
|
821
|
+
);
|
|
822
|
+
} else {
|
|
823
|
+
const scaffoldPrompt = await p.confirm({
|
|
824
|
+
message: `Next.js application detected at ${nextJsDetection.rootDir ?? projectDir} (version ${nextJsDetection.nextVersion ?? "unknown"}). Install and scaffold @moneydevkit/nextjs?`,
|
|
825
|
+
initialValue: true
|
|
826
|
+
});
|
|
827
|
+
if (p.isCancel(scaffoldPrompt)) {
|
|
828
|
+
p.cancel("Aborted.");
|
|
829
|
+
process.exit(1);
|
|
830
|
+
}
|
|
831
|
+
shouldScaffoldNextJs = Boolean(scaffoldPrompt);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
415
834
|
try {
|
|
416
835
|
const result = await runDeviceFlow({
|
|
417
836
|
flags,
|
|
@@ -422,7 +841,6 @@ async function main() {
|
|
|
422
841
|
});
|
|
423
842
|
const updates = {
|
|
424
843
|
MDK_ACCESS_TOKEN: result.credentials.apiKey,
|
|
425
|
-
MDK_WEBHOOK_SECRET: result.credentials.webhookSecret,
|
|
426
844
|
MDK_MNEMONIC: result.mnemonic
|
|
427
845
|
};
|
|
428
846
|
ensureEnvFileExists(envPath);
|
|
@@ -430,7 +848,8 @@ async function main() {
|
|
|
430
848
|
const preview = renderEnvPreview(existingEnv, updates);
|
|
431
849
|
writeEnvFile(envPath, existingEnv, updates);
|
|
432
850
|
const localEnvPath = resolveLocalEnvPath({
|
|
433
|
-
organizationId: result.credentials.organizationId
|
|
851
|
+
organizationId: result.credentials.organizationId,
|
|
852
|
+
apiKeyId: result.credentials.apiKeyId
|
|
434
853
|
});
|
|
435
854
|
if (localEnvPath) {
|
|
436
855
|
ensureEnvFileExists(localEnvPath);
|
|
@@ -442,11 +861,77 @@ async function main() {
|
|
|
442
861
|
}
|
|
443
862
|
if (!flags.noClipboard) {
|
|
444
863
|
await clipboard.write(
|
|
445
|
-
[`MDK_ACCESS_TOKEN=${updates.MDK_ACCESS_TOKEN}`, `
|
|
864
|
+
[`MDK_ACCESS_TOKEN=${updates.MDK_ACCESS_TOKEN}`, `MDK_MNEMONIC=${updates.MDK_MNEMONIC}`].join(
|
|
446
865
|
"\n"
|
|
447
866
|
)
|
|
448
867
|
);
|
|
449
868
|
}
|
|
869
|
+
let scaffoldSummary = null;
|
|
870
|
+
if (shouldScaffoldNextJs && nextJsDetection.found) {
|
|
871
|
+
try {
|
|
872
|
+
scaffoldSummary = await scaffoldNextJs({
|
|
873
|
+
detection: nextJsDetection,
|
|
874
|
+
jsonMode
|
|
875
|
+
});
|
|
876
|
+
if (!jsonMode && scaffoldSummary) {
|
|
877
|
+
const lines = [
|
|
878
|
+
scaffoldSummary.installedPackage ? `Installed @moneydevkit/nextjs with ${scaffoldSummary.packageManager}.` : scaffoldSummary.installSkipped ? "@moneydevkit/nextjs already installed; skipped package install." : "Skipped @moneydevkit/nextjs installation."
|
|
879
|
+
];
|
|
880
|
+
if (scaffoldSummary.config) {
|
|
881
|
+
const cfg = scaffoldSummary.config;
|
|
882
|
+
if (cfg.status === "created") {
|
|
883
|
+
lines.push(`Created ${cfg.path} with withMdkCheckout().`);
|
|
884
|
+
if (cfg.backupPath) {
|
|
885
|
+
lines.push(`Created backup at ${cfg.backupPath}.`);
|
|
886
|
+
}
|
|
887
|
+
} else if (cfg.status === "updated") {
|
|
888
|
+
lines.push(`Updated ${cfg.path} with withMdkCheckout().`);
|
|
889
|
+
if (cfg.backupPath) {
|
|
890
|
+
lines.push(`Created backup at ${cfg.backupPath}.`);
|
|
891
|
+
}
|
|
892
|
+
} else {
|
|
893
|
+
lines.push(
|
|
894
|
+
`Could not update ${cfg.path} automatically (${cfg.reason ?? "unknown reason"}).`
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (scaffoldSummary.addedFiles.length > 0) {
|
|
899
|
+
lines.push(
|
|
900
|
+
`Added: ${scaffoldSummary.addedFiles.map((p2) => path6.relative(projectDir, p2)).join(", ")}`
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
if (scaffoldSummary.skippedFiles.length > 0) {
|
|
904
|
+
lines.push(
|
|
905
|
+
`Skipped existing files: ${scaffoldSummary.skippedFiles.map((p2) => path6.relative(projectDir, p2)).join(", ")}`
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
p.note(lines.join("\n"), "Next.js scaffolding");
|
|
909
|
+
}
|
|
910
|
+
if (scaffoldSummary?.warnings.length) {
|
|
911
|
+
for (const warning of scaffoldSummary.warnings) {
|
|
912
|
+
console.warn(warning);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
} catch (error) {
|
|
916
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
917
|
+
if (jsonMode) {
|
|
918
|
+
console.error(
|
|
919
|
+
JSON.stringify(
|
|
920
|
+
{
|
|
921
|
+
status: "error",
|
|
922
|
+
error: {
|
|
923
|
+
message: `Next.js scaffolding failed: ${message}`
|
|
924
|
+
}
|
|
925
|
+
},
|
|
926
|
+
null,
|
|
927
|
+
2
|
|
928
|
+
)
|
|
929
|
+
);
|
|
930
|
+
} else {
|
|
931
|
+
console.warn(`Next.js scaffolding failed: ${message}`);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
450
935
|
const summary = {
|
|
451
936
|
projectDir,
|
|
452
937
|
envFile: envPath,
|
|
@@ -454,7 +939,8 @@ async function main() {
|
|
|
454
939
|
webhookId: result.credentials.webhookId,
|
|
455
940
|
organizationId: result.credentials.organizationId,
|
|
456
941
|
webhookUrl: result.credentials.webhookUrl,
|
|
457
|
-
mnemonic: updates.MDK_MNEMONIC
|
|
942
|
+
mnemonic: updates.MDK_MNEMONIC,
|
|
943
|
+
scaffoldedNextjs: Boolean(scaffoldSummary)
|
|
458
944
|
};
|
|
459
945
|
if (jsonMode) {
|
|
460
946
|
console.log(
|
|
@@ -469,7 +955,8 @@ async function main() {
|
|
|
469
955
|
webhookSecret: result.credentials.webhookSecret,
|
|
470
956
|
webhookUrl: result.credentials.webhookUrl,
|
|
471
957
|
organizationId: result.credentials.organizationId,
|
|
472
|
-
mnemonic: updates.MDK_MNEMONIC
|
|
958
|
+
mnemonic: updates.MDK_MNEMONIC,
|
|
959
|
+
scaffoldedNextjs: Boolean(scaffoldSummary)
|
|
473
960
|
}
|
|
474
961
|
},
|
|
475
962
|
null,
|