@korajs/cli 0.1.15 → 0.3.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 +49 -0
- package/dist/bin.cjs +2517 -332
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +12 -10
- package/dist/bin.js.map +1 -1
- package/dist/chunk-E4JG7THU.js +1550 -0
- package/dist/chunk-E4JG7THU.js.map +1 -0
- package/dist/chunk-KTSRAPSE.js +752 -0
- package/dist/chunk-KTSRAPSE.js.map +1 -0
- package/dist/chunk-ZGYRDYXS.js +609 -0
- package/dist/chunk-ZGYRDYXS.js.map +1 -0
- package/dist/create.cjs +974 -159
- package/dist/create.cjs.map +1 -1
- package/dist/create.js +2 -2
- package/dist/index.cjs +2103 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +617 -1
- package/dist/index.d.ts +617 -1
- package/dist/index.js +91 -5
- package/package.json +9 -3
- package/dist/chunk-N36PFOSA.js +0 -103
- package/dist/chunk-N36PFOSA.js.map +0 -1
- package/dist/chunk-SYOFLJLB.js +0 -437
- package/dist/chunk-SYOFLJLB.js.map +0 -1
- package/dist/chunk-VGOOQ56H.js +0 -103
- package/dist/chunk-VGOOQ56H.js.map +0 -1
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
PACKAGE_MANAGERS,
|
|
4
|
+
PreferenceStore,
|
|
5
|
+
ProjectExistsError,
|
|
6
|
+
PromptCancelledError,
|
|
7
|
+
TEMPLATES,
|
|
8
|
+
applySyncProviderPreset,
|
|
9
|
+
createLogger,
|
|
10
|
+
createPromptClient,
|
|
11
|
+
directoryExists,
|
|
12
|
+
resolveCreatePreferencesFlow,
|
|
13
|
+
saveResolvedPreferences,
|
|
14
|
+
shouldSavePreferences,
|
|
15
|
+
validateProjectName
|
|
16
|
+
} from "./chunk-KTSRAPSE.js";
|
|
17
|
+
|
|
18
|
+
// src/commands/create/create-command.ts
|
|
19
|
+
import { execSync as execSync2 } from "child_process";
|
|
20
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
21
|
+
import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
22
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
23
|
+
import { defineCommand } from "citty";
|
|
24
|
+
|
|
25
|
+
// src/utils/package-manager.ts
|
|
26
|
+
import { execSync } from "child_process";
|
|
27
|
+
function detectPackageManager() {
|
|
28
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
29
|
+
if (!userAgent) return "npm";
|
|
30
|
+
if (userAgent.startsWith("pnpm/")) return "pnpm";
|
|
31
|
+
if (userAgent.startsWith("yarn/")) return "yarn";
|
|
32
|
+
if (userAgent.startsWith("bun/")) return "bun";
|
|
33
|
+
return "npm";
|
|
34
|
+
}
|
|
35
|
+
function getInstallCommand(pm) {
|
|
36
|
+
return pm === "yarn" ? "yarn" : `${pm} install`;
|
|
37
|
+
}
|
|
38
|
+
function getRunDevCommand(pm) {
|
|
39
|
+
if (pm === "npm") return "npm run dev";
|
|
40
|
+
return `${pm} dev`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/commands/create/environment-detection.ts
|
|
44
|
+
import { access, mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
45
|
+
import { dirname, join, resolve } from "path";
|
|
46
|
+
function detectEditorFromEnvironment(env = process.env) {
|
|
47
|
+
const termProgram = String(env.TERM_PROGRAM ?? "").toLowerCase();
|
|
48
|
+
const editorValue = `${String(env.EDITOR ?? "")} ${String(env.VISUAL ?? "")}`.toLowerCase();
|
|
49
|
+
if (termProgram.includes("cursor") || env.CURSOR_TRACE_ID !== void 0 || env.CURSOR_SESSION_ID !== void 0 || editorValue.includes("cursor")) {
|
|
50
|
+
return { editor: "cursor", source: "env" };
|
|
51
|
+
}
|
|
52
|
+
if (termProgram.includes("windsurf") || env.WINDSURF_SESSION_ID !== void 0 || editorValue.includes("windsurf")) {
|
|
53
|
+
return { editor: "windsurf", source: "env" };
|
|
54
|
+
}
|
|
55
|
+
if (termProgram.includes("vscode") || env.VSCODE_GIT_IPC_HANDLE !== void 0 || env.VSCODE_IPC_HOOK !== void 0 || env.VSCODE_PID !== void 0 || editorValue.includes("code")) {
|
|
56
|
+
return { editor: "vscode", source: "env" };
|
|
57
|
+
}
|
|
58
|
+
if (termProgram.includes("zed") || env.ZED_TERM !== void 0 || editorValue.includes("zed")) {
|
|
59
|
+
return { editor: "zed", source: "env" };
|
|
60
|
+
}
|
|
61
|
+
return { editor: "unknown", source: "none" };
|
|
62
|
+
}
|
|
63
|
+
async function detectGitContext(startDir) {
|
|
64
|
+
const root = await findNearestAncestorWithEntry(startDir, ".git");
|
|
65
|
+
return {
|
|
66
|
+
hasRepository: root !== null,
|
|
67
|
+
repositoryRoot: root
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function detectMonorepoContext(startDir) {
|
|
71
|
+
let current = resolve(startDir);
|
|
72
|
+
for (; ; ) {
|
|
73
|
+
if (await fileExists(join(current, "pnpm-workspace.yaml"))) {
|
|
74
|
+
return { isMonorepo: true, root: current, kind: "pnpm-workspace" };
|
|
75
|
+
}
|
|
76
|
+
if (await fileExists(join(current, "turbo.json"))) {
|
|
77
|
+
return { isMonorepo: true, root: current, kind: "turborepo" };
|
|
78
|
+
}
|
|
79
|
+
const packageJsonPath = join(current, "package.json");
|
|
80
|
+
if (await fileExists(packageJsonPath)) {
|
|
81
|
+
try {
|
|
82
|
+
const packageJsonRaw = await readFile(packageJsonPath, "utf-8");
|
|
83
|
+
const parsed = JSON.parse(packageJsonRaw);
|
|
84
|
+
if (Array.isArray(parsed.workspaces) || isNpmWorkspaceObject(parsed.workspaces)) {
|
|
85
|
+
return { isMonorepo: true, root: current, kind: "npm-workspaces" };
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const parent = dirname(current);
|
|
91
|
+
if (parent === current) {
|
|
92
|
+
return { isMonorepo: false, root: null, kind: "none" };
|
|
93
|
+
}
|
|
94
|
+
current = parent;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function applyEditorWorkspacePreset(params) {
|
|
98
|
+
const { targetDir, editor } = params;
|
|
99
|
+
if (editor !== "vscode" && editor !== "cursor" && editor !== "windsurf") {
|
|
100
|
+
return { applied: false, filePath: null };
|
|
101
|
+
}
|
|
102
|
+
const vscodeDir = join(targetDir, ".vscode");
|
|
103
|
+
const extensionsPath = join(vscodeDir, "extensions.json");
|
|
104
|
+
await mkdir(vscodeDir, { recursive: true });
|
|
105
|
+
const recommendations = ["korajs.kora-devtools"];
|
|
106
|
+
const existing = await readJsonObject(extensionsPath);
|
|
107
|
+
const existingRecommendations = Array.isArray(existing?.recommendations) ? existing.recommendations.filter((item) => typeof item === "string") : [];
|
|
108
|
+
const mergedRecommendations = dedupeStrings([...existingRecommendations, ...recommendations]);
|
|
109
|
+
const next = {
|
|
110
|
+
recommendations: mergedRecommendations
|
|
111
|
+
};
|
|
112
|
+
await writeFile(extensionsPath, `${JSON.stringify(next, null, 2)}
|
|
113
|
+
`, "utf-8");
|
|
114
|
+
return { applied: true, filePath: extensionsPath };
|
|
115
|
+
}
|
|
116
|
+
function resolveMonorepoTargetDirectory(monorepoRoot, projectName) {
|
|
117
|
+
return join(monorepoRoot, "packages", projectName);
|
|
118
|
+
}
|
|
119
|
+
function dedupeStrings(values) {
|
|
120
|
+
return Array.from(new Set(values));
|
|
121
|
+
}
|
|
122
|
+
async function readJsonObject(path) {
|
|
123
|
+
try {
|
|
124
|
+
const content = await readFile(path, "utf-8");
|
|
125
|
+
const parsed = JSON.parse(content);
|
|
126
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
127
|
+
return parsed;
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
} catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function isNpmWorkspaceObject(value) {
|
|
135
|
+
if (typeof value !== "object" || value === null) return false;
|
|
136
|
+
const record = value;
|
|
137
|
+
return Array.isArray(record.packages);
|
|
138
|
+
}
|
|
139
|
+
async function fileExists(path) {
|
|
140
|
+
try {
|
|
141
|
+
await access(path);
|
|
142
|
+
return true;
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function findNearestAncestorWithEntry(startDir, entryName) {
|
|
148
|
+
let current = resolve(startDir);
|
|
149
|
+
for (; ; ) {
|
|
150
|
+
const candidate = join(current, entryName);
|
|
151
|
+
try {
|
|
152
|
+
await stat(candidate);
|
|
153
|
+
return current;
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
const parent = dirname(current);
|
|
157
|
+
if (parent === current) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
current = parent;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/templates/composer.ts
|
|
165
|
+
import { existsSync } from "fs";
|
|
166
|
+
import { copyFile, mkdir as mkdir2, readFile as readFile2, readdir, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
167
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
168
|
+
import { fileURLToPath } from "url";
|
|
169
|
+
function substituteVariables(template, context) {
|
|
170
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
171
|
+
const value = context[key];
|
|
172
|
+
return value !== void 0 ? value : `{{${key}}}`;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function getTemplatePath(templateName) {
|
|
176
|
+
let dir = dirname2(fileURLToPath(import.meta.url));
|
|
177
|
+
for (let i = 0; i < 7; i++) {
|
|
178
|
+
const candidate = resolve2(dir, "templates", templateName);
|
|
179
|
+
if (existsSync(candidate)) {
|
|
180
|
+
return candidate;
|
|
181
|
+
}
|
|
182
|
+
dir = dirname2(dir);
|
|
183
|
+
}
|
|
184
|
+
const currentDir = dirname2(fileURLToPath(import.meta.url));
|
|
185
|
+
return resolve2(currentDir, "..", "..", "..", "templates", templateName);
|
|
186
|
+
}
|
|
187
|
+
function createCompatibilityLayerPlan(templateName) {
|
|
188
|
+
const baseLayer = { category: "base", name: "base", sourceTemplate: "react-basic" };
|
|
189
|
+
const uiLayer = { category: "ui", name: "react", sourceTemplate: null };
|
|
190
|
+
const authLayer = { category: "auth", name: "none", sourceTemplate: null };
|
|
191
|
+
switch (templateName) {
|
|
192
|
+
case "react-basic":
|
|
193
|
+
return {
|
|
194
|
+
compatibilityTarget: templateName,
|
|
195
|
+
layers: [
|
|
196
|
+
baseLayer,
|
|
197
|
+
uiLayer,
|
|
198
|
+
{ category: "style", name: "plain", sourceTemplate: null },
|
|
199
|
+
{ category: "sync", name: "disabled", sourceTemplate: null },
|
|
200
|
+
{ category: "db", name: "none", sourceTemplate: null },
|
|
201
|
+
authLayer
|
|
202
|
+
]
|
|
203
|
+
};
|
|
204
|
+
case "react-tailwind":
|
|
205
|
+
return {
|
|
206
|
+
compatibilityTarget: templateName,
|
|
207
|
+
layers: [
|
|
208
|
+
baseLayer,
|
|
209
|
+
uiLayer,
|
|
210
|
+
{ category: "style", name: "tailwind", sourceTemplate: "react-tailwind" },
|
|
211
|
+
{ category: "sync", name: "disabled", sourceTemplate: null },
|
|
212
|
+
{ category: "db", name: "none", sourceTemplate: null },
|
|
213
|
+
authLayer
|
|
214
|
+
]
|
|
215
|
+
};
|
|
216
|
+
case "react-sync":
|
|
217
|
+
return {
|
|
218
|
+
compatibilityTarget: templateName,
|
|
219
|
+
layers: [
|
|
220
|
+
baseLayer,
|
|
221
|
+
uiLayer,
|
|
222
|
+
{ category: "style", name: "plain", sourceTemplate: null },
|
|
223
|
+
{ category: "sync", name: "enabled", sourceTemplate: "react-sync" },
|
|
224
|
+
{ category: "db", name: "sqlite", sourceTemplate: null },
|
|
225
|
+
authLayer
|
|
226
|
+
]
|
|
227
|
+
};
|
|
228
|
+
case "react-tailwind-sync":
|
|
229
|
+
return {
|
|
230
|
+
compatibilityTarget: templateName,
|
|
231
|
+
layers: [
|
|
232
|
+
baseLayer,
|
|
233
|
+
uiLayer,
|
|
234
|
+
{ category: "style", name: "tailwind", sourceTemplate: "react-tailwind" },
|
|
235
|
+
{ category: "sync", name: "enabled", sourceTemplate: "react-tailwind-sync" },
|
|
236
|
+
{ category: "db", name: "sqlite", sourceTemplate: null },
|
|
237
|
+
authLayer
|
|
238
|
+
]
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async function composeTemplateLayers(plan, targetDir, context) {
|
|
243
|
+
const vars = {
|
|
244
|
+
projectName: context.projectName,
|
|
245
|
+
packageManager: context.packageManager,
|
|
246
|
+
koraVersion: context.koraVersion,
|
|
247
|
+
dbProvider: context.dbProvider ?? "none"
|
|
248
|
+
};
|
|
249
|
+
for (const layer of plan.layers) {
|
|
250
|
+
if (layer.sourceTemplate === null) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const sourceDir = getTemplatePath(layer.sourceTemplate);
|
|
254
|
+
await copyDirectory(sourceDir, targetDir, vars);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function copyDirectory(src, dest, vars) {
|
|
258
|
+
await mkdir2(dest, { recursive: true });
|
|
259
|
+
const entries = await readdir(src);
|
|
260
|
+
for (const entry of entries) {
|
|
261
|
+
const srcPath = join2(src, entry);
|
|
262
|
+
const srcStat = await stat2(srcPath);
|
|
263
|
+
if (srcStat.isDirectory()) {
|
|
264
|
+
await copyDirectory(srcPath, join2(dest, entry), vars);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (entry.endsWith(".hbs")) {
|
|
268
|
+
const content = await readFile2(srcPath, "utf-8");
|
|
269
|
+
const outputName = entry.slice(0, -4);
|
|
270
|
+
await writeFile2(join2(dest, outputName), substituteVariables(content, vars), "utf-8");
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
await copyFile(srcPath, join2(dest, entry));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/commands/create/template-engine.ts
|
|
278
|
+
async function scaffoldTemplate(templateName, targetDir, context) {
|
|
279
|
+
const plan = createCompatibilityLayerPlan(templateName);
|
|
280
|
+
await composeTemplateLayers(plan, targetDir, context);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/commands/create/create-command.ts
|
|
284
|
+
var createCommand = defineCommand({
|
|
285
|
+
meta: {
|
|
286
|
+
name: "create",
|
|
287
|
+
description: "Create a new Kora application"
|
|
288
|
+
},
|
|
289
|
+
args: {
|
|
290
|
+
name: {
|
|
291
|
+
type: "positional",
|
|
292
|
+
description: "Project directory name",
|
|
293
|
+
required: false
|
|
294
|
+
},
|
|
295
|
+
template: {
|
|
296
|
+
type: "string",
|
|
297
|
+
description: "Project template (react-tailwind-sync, react-tailwind, react-sync, react-basic)"
|
|
298
|
+
},
|
|
299
|
+
pm: {
|
|
300
|
+
type: "string",
|
|
301
|
+
description: "Package manager (pnpm, npm, yarn, bun)"
|
|
302
|
+
},
|
|
303
|
+
framework: {
|
|
304
|
+
type: "string",
|
|
305
|
+
description: "UI framework (react, vue, svelte, solid)"
|
|
306
|
+
},
|
|
307
|
+
db: {
|
|
308
|
+
type: "string",
|
|
309
|
+
description: "Database backend for sync templates (sqlite, postgres)"
|
|
310
|
+
},
|
|
311
|
+
"db-provider": {
|
|
312
|
+
type: "string",
|
|
313
|
+
description: "Database provider for postgres (local, supabase, neon, railway, vercel-postgres, custom)"
|
|
314
|
+
},
|
|
315
|
+
auth: {
|
|
316
|
+
type: "string",
|
|
317
|
+
description: "Authentication mode (none, email-password, oauth)"
|
|
318
|
+
},
|
|
319
|
+
"skip-install": {
|
|
320
|
+
type: "boolean",
|
|
321
|
+
description: "Skip installing dependencies",
|
|
322
|
+
default: false
|
|
323
|
+
},
|
|
324
|
+
yes: {
|
|
325
|
+
type: "boolean",
|
|
326
|
+
alias: "y",
|
|
327
|
+
description: "Accept all defaults (react-tailwind-sync + detected package manager)",
|
|
328
|
+
default: false
|
|
329
|
+
},
|
|
330
|
+
tailwind: {
|
|
331
|
+
type: "boolean",
|
|
332
|
+
description: "Use Tailwind CSS (use --no-tailwind to skip)"
|
|
333
|
+
},
|
|
334
|
+
sync: {
|
|
335
|
+
type: "boolean",
|
|
336
|
+
description: "Include sync server (use --no-sync to skip)"
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
async run({ args }) {
|
|
340
|
+
const logger = createLogger();
|
|
341
|
+
const prompts = createPromptClient();
|
|
342
|
+
const preferenceStore = new PreferenceStore();
|
|
343
|
+
logger.banner();
|
|
344
|
+
try {
|
|
345
|
+
const useDefaults = args.yes === true;
|
|
346
|
+
if (!useDefaults) {
|
|
347
|
+
prompts.intro("Kora.js \u2014 Offline-first application framework");
|
|
348
|
+
}
|
|
349
|
+
const projectName = args.name || (useDefaults ? "my-kora-app" : await prompts.text("Project name", "my-kora-app"));
|
|
350
|
+
if (!projectName) {
|
|
351
|
+
logger.error("Project name is required");
|
|
352
|
+
process.exitCode = 1;
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const nameValidation = validateProjectName(projectName);
|
|
356
|
+
if (!nameValidation.valid) {
|
|
357
|
+
logger.error("Invalid project name.");
|
|
358
|
+
for (const issue of nameValidation.issues) {
|
|
359
|
+
logger.step(`- ${issue}`);
|
|
360
|
+
}
|
|
361
|
+
if (!useDefaults) {
|
|
362
|
+
prompts.outro("Project creation aborted.");
|
|
363
|
+
}
|
|
364
|
+
process.exitCode = 1;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const preferenceFlags = {
|
|
368
|
+
framework: typeof args.framework === "string" ? args.framework : void 0,
|
|
369
|
+
auth: typeof args.auth === "string" ? args.auth : void 0,
|
|
370
|
+
db: typeof args.db === "string" ? args.db : void 0,
|
|
371
|
+
dbProvider: typeof args["db-provider"] === "string" ? args["db-provider"] : void 0,
|
|
372
|
+
tailwind: args.tailwind,
|
|
373
|
+
sync: args.sync,
|
|
374
|
+
useDefaults
|
|
375
|
+
};
|
|
376
|
+
const selection = await resolveCreatePreferencesFlow({
|
|
377
|
+
flags: preferenceFlags,
|
|
378
|
+
prompts,
|
|
379
|
+
store: preferenceStore
|
|
380
|
+
});
|
|
381
|
+
if (selection.framework !== "react") {
|
|
382
|
+
logger.error(`Framework "${selection.framework}" is not available yet. Use "react".`);
|
|
383
|
+
if (!useDefaults) {
|
|
384
|
+
prompts.outro("Project creation aborted.");
|
|
385
|
+
}
|
|
386
|
+
process.exitCode = 1;
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (selection.auth !== "none") {
|
|
390
|
+
logger.error(`Auth mode "${selection.auth}" is not available yet. Use "none".`);
|
|
391
|
+
if (!useDefaults) {
|
|
392
|
+
prompts.outro("Project creation aborted.");
|
|
393
|
+
}
|
|
394
|
+
process.exitCode = 1;
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const template = args.template && isValidTemplate(args.template) ? args.template : selection.template;
|
|
398
|
+
let pm;
|
|
399
|
+
if (args.pm && isValidPackageManager(args.pm)) {
|
|
400
|
+
pm = args.pm;
|
|
401
|
+
} else if (useDefaults) {
|
|
402
|
+
pm = detectPackageManager();
|
|
403
|
+
} else if (selection.usedStoredPreferences) {
|
|
404
|
+
pm = preferenceStore.getCreatePreferences()?.packageManager ?? detectPackageManager();
|
|
405
|
+
} else {
|
|
406
|
+
const detected = detectPackageManager();
|
|
407
|
+
pm = await prompts.select(
|
|
408
|
+
"Package manager:",
|
|
409
|
+
PACKAGE_MANAGERS.map((p) => ({
|
|
410
|
+
label: p === detected ? `${p} (detected)` : p,
|
|
411
|
+
value: p
|
|
412
|
+
}))
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
const editorDetection = detectEditorFromEnvironment();
|
|
416
|
+
const gitContext = await detectGitContext(process.cwd());
|
|
417
|
+
const monorepoContext = await detectMonorepoContext(process.cwd());
|
|
418
|
+
let targetDir = resolve3(process.cwd(), projectName);
|
|
419
|
+
if (!useDefaults && monorepoContext.isMonorepo && monorepoContext.root !== null) {
|
|
420
|
+
const useWorkspaceTarget = await prompts.confirm(
|
|
421
|
+
`Detected ${formatMonorepoKind(monorepoContext.kind)} at ${monorepoContext.root}. Create app under packages/${projectName}?`,
|
|
422
|
+
true
|
|
423
|
+
);
|
|
424
|
+
if (useWorkspaceTarget) {
|
|
425
|
+
targetDir = resolveMonorepoTargetDirectory(monorepoContext.root, projectName);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (await directoryExists(targetDir)) {
|
|
429
|
+
throw new ProjectExistsError(projectName);
|
|
430
|
+
}
|
|
431
|
+
const koraVersion = resolveKoraVersion();
|
|
432
|
+
logger.step(`Creating ${projectName} with ${template} template...`);
|
|
433
|
+
await scaffoldTemplate(template, targetDir, {
|
|
434
|
+
projectName,
|
|
435
|
+
packageManager: pm,
|
|
436
|
+
koraVersion,
|
|
437
|
+
dbProvider: selection.dbProvider
|
|
438
|
+
});
|
|
439
|
+
await applySyncProviderPreset({
|
|
440
|
+
targetDir,
|
|
441
|
+
template,
|
|
442
|
+
db: selection.db,
|
|
443
|
+
dbProvider: selection.dbProvider
|
|
444
|
+
});
|
|
445
|
+
if (!useDefaults && editorDetection.editor !== "unknown") {
|
|
446
|
+
const shouldApplyEditorPreset = await prompts.confirm(
|
|
447
|
+
`Detected ${formatEditor(editorDetection.editor)}. Add workspace recommendations for ${formatEditor(editorDetection.editor)}?`,
|
|
448
|
+
true
|
|
449
|
+
);
|
|
450
|
+
if (shouldApplyEditorPreset) {
|
|
451
|
+
const appliedPreset = await applyEditorWorkspacePreset({
|
|
452
|
+
targetDir,
|
|
453
|
+
editor: editorDetection.editor
|
|
454
|
+
});
|
|
455
|
+
if (appliedPreset.applied) {
|
|
456
|
+
logger.step(
|
|
457
|
+
`Added editor recommendations at ${relativeToTarget(targetDir, appliedPreset.filePath)}`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (selection.db === "postgres" && isSyncTemplate(template)) {
|
|
463
|
+
logger.info(
|
|
464
|
+
`Applied PostgreSQL sync preset (${formatDbProviderForLog(selection.dbProvider)}). Update DATABASE_URL in .env.example before running the sync server.`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
if (gitContext.hasRepository) {
|
|
468
|
+
logger.step(
|
|
469
|
+
`Detected existing git repository at ${gitContext.repositoryRoot}. Skipping git initialization.`
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
logger.success("Project scaffolded");
|
|
473
|
+
if (shouldSavePreferences(preferenceFlags)) {
|
|
474
|
+
saveResolvedPreferences(preferenceStore, {
|
|
475
|
+
framework: selection.framework,
|
|
476
|
+
auth: selection.auth,
|
|
477
|
+
db: selection.db,
|
|
478
|
+
dbProvider: selection.dbProvider,
|
|
479
|
+
tailwind: selection.tailwind,
|
|
480
|
+
sync: selection.sync,
|
|
481
|
+
packageManager: pm
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
if (!args["skip-install"]) {
|
|
485
|
+
logger.step("Installing dependencies...");
|
|
486
|
+
try {
|
|
487
|
+
execSync2(getInstallCommand(pm), { cwd: targetDir, stdio: "inherit" });
|
|
488
|
+
logger.success("Dependencies installed");
|
|
489
|
+
} catch {
|
|
490
|
+
logger.warn("Failed to install dependencies. Run install manually.");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
logger.blank();
|
|
494
|
+
logger.info("Done! Next steps:");
|
|
495
|
+
logger.blank();
|
|
496
|
+
logger.step(` cd ${targetDir}`);
|
|
497
|
+
logger.step(` ${getRunDevCommand(pm)}`);
|
|
498
|
+
logger.blank();
|
|
499
|
+
if (!useDefaults) {
|
|
500
|
+
prompts.outro("Project ready. Happy building with Kora!");
|
|
501
|
+
}
|
|
502
|
+
} catch (error) {
|
|
503
|
+
if (error instanceof PromptCancelledError) {
|
|
504
|
+
process.exitCode = 1;
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (error instanceof Error && error.message.startsWith("Invalid --")) {
|
|
508
|
+
logger.error(error.message);
|
|
509
|
+
if (!args.yes) {
|
|
510
|
+
prompts.outro("Project creation aborted.");
|
|
511
|
+
}
|
|
512
|
+
process.exitCode = 1;
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
throw error;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
function isValidTemplate(value) {
|
|
520
|
+
return TEMPLATES.includes(value);
|
|
521
|
+
}
|
|
522
|
+
function isValidPackageManager(value) {
|
|
523
|
+
return PACKAGE_MANAGERS.includes(value);
|
|
524
|
+
}
|
|
525
|
+
function isSyncTemplate(template) {
|
|
526
|
+
return template === "react-sync" || template === "react-tailwind-sync";
|
|
527
|
+
}
|
|
528
|
+
function formatDbProviderForLog(dbProvider) {
|
|
529
|
+
switch (dbProvider) {
|
|
530
|
+
case "supabase":
|
|
531
|
+
return "Supabase";
|
|
532
|
+
case "neon":
|
|
533
|
+
return "Neon";
|
|
534
|
+
case "railway":
|
|
535
|
+
return "Railway";
|
|
536
|
+
case "vercel-postgres":
|
|
537
|
+
return "Vercel Postgres";
|
|
538
|
+
case "custom":
|
|
539
|
+
return "Custom";
|
|
540
|
+
case "local":
|
|
541
|
+
return "Local Postgres";
|
|
542
|
+
case "none":
|
|
543
|
+
return "PostgreSQL";
|
|
544
|
+
default:
|
|
545
|
+
return dbProvider;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function formatEditor(editor) {
|
|
549
|
+
switch (editor) {
|
|
550
|
+
case "vscode":
|
|
551
|
+
return "VS Code";
|
|
552
|
+
case "cursor":
|
|
553
|
+
return "Cursor";
|
|
554
|
+
case "windsurf":
|
|
555
|
+
return "Windsurf";
|
|
556
|
+
case "zed":
|
|
557
|
+
return "Zed";
|
|
558
|
+
default:
|
|
559
|
+
return editor;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function formatMonorepoKind(kind) {
|
|
563
|
+
switch (kind) {
|
|
564
|
+
case "pnpm-workspace":
|
|
565
|
+
return "pnpm workspace";
|
|
566
|
+
case "npm-workspaces":
|
|
567
|
+
return "npm workspace";
|
|
568
|
+
case "turborepo":
|
|
569
|
+
return "Turborepo";
|
|
570
|
+
default:
|
|
571
|
+
return "monorepo";
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function relativeToTarget(targetDir, filePath) {
|
|
575
|
+
if (filePath === null) return ".";
|
|
576
|
+
const normalizedTarget = targetDir.endsWith("/") ? targetDir : `${targetDir}/`;
|
|
577
|
+
if (filePath.startsWith(normalizedTarget)) {
|
|
578
|
+
return filePath.slice(normalizedTarget.length);
|
|
579
|
+
}
|
|
580
|
+
if (filePath === targetDir) {
|
|
581
|
+
return ".";
|
|
582
|
+
}
|
|
583
|
+
return filePath;
|
|
584
|
+
}
|
|
585
|
+
function resolveKoraVersion() {
|
|
586
|
+
try {
|
|
587
|
+
let dir = dirname3(fileURLToPath2(import.meta.url));
|
|
588
|
+
for (let i = 0; i < 5; i++) {
|
|
589
|
+
const pkgPath = resolve3(dir, "package.json");
|
|
590
|
+
if (existsSync2(pkgPath)) {
|
|
591
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
592
|
+
if (pkg.name === "@korajs/cli") {
|
|
593
|
+
if (pkg.version === "0.0.0") return "latest";
|
|
594
|
+
const parts = pkg.version.split(".");
|
|
595
|
+
return `^${parts[0]}.${parts[1]}.0`;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
dir = dirname3(dir);
|
|
599
|
+
}
|
|
600
|
+
return "latest";
|
|
601
|
+
} catch {
|
|
602
|
+
return "latest";
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export {
|
|
607
|
+
createCommand
|
|
608
|
+
};
|
|
609
|
+
//# sourceMappingURL=chunk-ZGYRDYXS.js.map
|