create-fullstack-scaffold 0.4.4 → 0.4.6

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/cli/index.js CHANGED
@@ -1,433 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { program } from 'commander';
3
- import { hc } from 'hono/client';
4
- import { z } from '@hono/zod-openapi';
5
- import fs, { readdirSync, statSync, existsSync, readFileSync } from 'fs';
6
- import path2, { join } from 'path';
7
- import os from 'os';
2
+ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
3
+ import path, { join } from 'path';
8
4
  import { fileURLToPath, pathToFileURL } from 'url';
9
- import fs2 from 'fs-extra';
5
+ import { Command } from 'commander';
10
6
  import chalk from 'chalk';
7
+ import { select } from '@inquirer/prompts';
8
+ import fs from 'fs-extra';
11
9
  import ora from 'ora';
12
10
 
13
- function createRPCClient(baseUrl) {
14
- return hc(baseUrl);
15
- }
16
-
17
- // src/cli/utils/api.ts
18
- var DEFAULT_BASE_URL = "http://localhost:3010";
19
- var globalBaseUrl = process.env.BIOMIMIC_API_URL || DEFAULT_BASE_URL;
20
- function setBaseUrl(url) {
21
- globalBaseUrl = url;
22
- }
23
- function getBaseUrl() {
24
- return globalBaseUrl;
25
- }
26
- function getClient() {
27
- return createRPCClient(globalBaseUrl);
28
- }
29
-
30
- // src/cli/utils/logger.ts
31
- var Logger = class {
32
- level = "info";
33
- constructor(options = {}) {
34
- if (options.debug || process.env.BIOMIMIC_DEBUG === "true") {
35
- this.level = "debug";
36
- } else if (options.verbose || process.env.BIOMIMIC_VERBOSE === "true") {
37
- this.level = "info";
38
- }
39
- }
40
- setLevel(level) {
41
- this.level = level;
42
- }
43
- shouldLog(level) {
44
- const levels = ["silent", "error", "warn", "info", "debug"];
45
- return levels.indexOf(level) <= levels.indexOf(this.level);
46
- }
47
- info(message, ...args) {
48
- if (this.shouldLog("info")) {
49
- process.stdout.write(`${message}${args.length ? " " + args.join(" ") : ""}
50
- `);
51
- }
52
- }
53
- debug(message, ...args) {
54
- if (this.shouldLog("debug")) {
55
- process.stdout.write(`[debug] ${message}${args.length ? " " + args.join(" ") : ""}
56
- `);
57
- }
58
- }
59
- warn(message) {
60
- if (this.shouldLog("warn")) {
61
- process.stderr.write(`[warn] ${message}
62
- `);
63
- }
64
- }
65
- error(message) {
66
- if (this.shouldLog("error")) {
67
- process.stderr.write(`[error] ${message}
68
- `);
69
- }
70
- }
71
- success(message, ...args) {
72
- if (this.shouldLog("info")) {
73
- process.stdout.write(`\u2713 ${message}${args.length ? " " + args.join(" ") : ""}
74
- `);
75
- }
76
- }
77
- fail(message) {
78
- if (this.shouldLog("error")) {
79
- process.stderr.write(`\u2717 ${message}
80
- `);
81
- }
82
- }
83
- };
84
- var logger = null;
85
- function createLogger(options = {}) {
86
- logger = new Logger(options);
87
- return logger;
88
- }
89
- function getLogger() {
90
- if (!logger) {
91
- logger = new Logger();
92
- }
93
- return logger;
94
- }
95
-
96
- // src/cli/utils/auto-command.ts
97
- function extractZodInfo(schema) {
98
- const def = schema._def;
99
- if (def.typeName === "ZodOptional") {
100
- const inner = schema._def.innerType;
101
- const info = extractZodInfo(inner);
102
- return { ...info, required: false };
103
- }
104
- if (def.typeName === "ZodDefault") {
105
- const innerDef = schema._def;
106
- const defaultValue = innerDef.defaultValue();
107
- const info = extractZodInfo(innerDef.innerType);
108
- return { ...info, required: false, defaultValue };
109
- }
110
- if (def.typeName === "ZodEnum") {
111
- const enumValues = [...schema._def.values];
112
- return { type: "enum", required: true, enumValues };
113
- }
114
- if (def.typeName === "ZodString") return { type: "string", required: true };
115
- if (def.typeName === "ZodNumber") return { type: "number", required: true };
116
- if (def.typeName === "ZodBoolean") return { type: "boolean", required: true };
117
- if (def.typeName === "ZodObject") return { type: "object", required: true };
118
- return { type: "string", required: true };
119
- }
120
- function schemaToOptions(schema) {
121
- const options = [];
122
- const shape = schema.shape;
123
- for (const [key, zodType] of Object.entries(shape)) {
124
- const info = extractZodInfo(zodType);
125
- const longFlag = key.replace(/([A-Z])/g, "-$1").toLowerCase();
126
- let flags = `--${longFlag} <value>`;
127
- if (info.type === "boolean") {
128
- flags = `--${longFlag}`;
129
- }
130
- let description = String(info.type);
131
- if (info.enumValues) {
132
- description = `${info.type} (${info.enumValues.join("|")})`;
133
- }
134
- options.push({
135
- flags,
136
- description,
137
- required: info.required,
138
- defaultValue: info.defaultValue
139
- });
140
- }
141
- return options;
142
- }
143
- function pathToApiCall(client, _method, path3) {
144
- const pathParts = path3.split("/").filter(Boolean);
145
- let current = client.api;
146
- for (const part of pathParts) {
147
- if (part.startsWith("{") && part.endsWith("}")) {
148
- continue;
149
- }
150
- current = current[part] || current[`:${part}`];
151
- }
152
- return current;
153
- }
154
- function createCommandFromRoute(config) {
155
- const options = [];
156
- const arguments_ = [];
157
- if (config.params) {
158
- const shape = config.params.shape;
159
- for (const [key] of Object.entries(shape)) {
160
- arguments_.push({
161
- name: key,
162
- description: `${key} parameter`,
163
- required: true
164
- });
165
- }
166
- }
167
- if (config.body) {
168
- const bodySchema = config.body;
169
- if (bodySchema.shape) {
170
- options.push(...schemaToOptions(bodySchema) || []);
171
- }
172
- }
173
- if (config.query) {
174
- options.push(...schemaToOptions(config.query) || []);
175
- }
176
- return {
177
- name: config.command,
178
- description: config.description,
179
- options,
180
- arguments: arguments_,
181
- action: async (opts, args) => {
182
- const logger2 = getLogger();
183
- const client = getClient();
184
- const param = {};
185
- if (config.params) {
186
- const shape = config.params.shape;
187
- const keys = Object.keys(shape);
188
- keys.forEach((key, i) => {
189
- param[key] = args[i] || String(opts[key]);
190
- });
191
- }
192
- const json = {};
193
- if (config.body && opts) {
194
- const bodySchema = config.body;
195
- if (bodySchema.shape) {
196
- const shape = bodySchema.shape;
197
- for (const key of Object.keys(shape)) {
198
- if (opts[key] !== void 0) {
199
- json[key] = opts[key];
200
- }
201
- }
202
- }
203
- }
204
- const query = {};
205
- if (config.query && opts) {
206
- const shape = config.query.shape;
207
- for (const key of Object.keys(shape)) {
208
- if (opts[key] !== void 0) {
209
- query[key] = String(opts[key]);
210
- }
211
- }
212
- }
213
- const apiCall = pathToApiCall(client, config.method, config.path);
214
- const methodCall = apiCall[`$${config.method.charAt(0).toUpperCase() + config.method.slice(1)}`] || apiCall.$get;
215
- const res = await methodCall.call(apiCall, {
216
- param: Object.keys(param).length > 0 ? param : void 0,
217
- query: Object.keys(query).length > 0 ? query : void 0,
218
- json: Object.keys(json).length > 0 ? json : void 0
219
- });
220
- const data = await res.json();
221
- logger2.info(JSON.stringify(data, null, 2));
222
- }
223
- };
224
- }
225
- function registerAutoCommand(program2, config) {
226
- const cmd = createCommandFromRoute(config);
227
- const command = program2.command(cmd.name).description(cmd.description).action(async (firstArg, opts) => {
228
- const args = typeof firstArg === "string" ? [firstArg] : [];
229
- const options = (typeof firstArg === "object" ? firstArg : opts) || {};
230
- await cmd.action(options, args);
231
- });
232
- cmd.options?.forEach((opt) => {
233
- if (opt.required) {
234
- command.requiredOption(opt.flags, opt.description, opt.defaultValue);
235
- } else {
236
- command.option(opt.flags, opt.description, opt.defaultValue);
237
- }
238
- });
239
- cmd.arguments?.forEach((arg) => {
240
- command.argument(arg.required ? `<${arg.name}>` : `[${arg.name}]`, arg.description);
241
- });
242
- }
243
- var TodoStatusSchema = z.enum(["pending", "in_progress", "completed"]);
244
- var todoRoutes = [
245
- {
246
- method: "get",
247
- path: "/todos",
248
- command: "list",
249
- description: "List all todos"
250
- },
251
- {
252
- method: "get",
253
- path: "/todos/{id}",
254
- command: "get",
255
- description: "Get a todo by ID",
256
- params: z.object({ id: z.string() })
257
- },
258
- {
259
- method: "post",
260
- path: "/todos",
261
- command: "create",
262
- description: "Create a new todo",
263
- body: z.object({
264
- title: z.string().min(1),
265
- description: z.string().optional()
266
- })
267
- },
268
- {
269
- method: "put",
270
- path: "/todos/{id}",
271
- command: "update",
272
- description: "Update a todo",
273
- params: z.object({ id: z.string() }),
274
- body: z.object({
275
- title: z.string().optional(),
276
- description: z.string().optional(),
277
- status: TodoStatusSchema.optional()
278
- })
279
- },
280
- {
281
- method: "delete",
282
- path: "/todos/{id}",
283
- command: "delete",
284
- description: "Delete a todo",
285
- params: z.object({ id: z.string() })
286
- }
287
- ];
288
- function registerTodoCommands(program2) {
289
- const todo = program2.command("todo").description("Todo management commands");
290
- for (const route of todoRoutes) {
291
- registerAutoCommand(todo, route);
292
- }
293
- }
294
-
295
- // src/cli/modules/notification/index.ts
296
- function registerNotificationCommands(program2) {
297
- const notification = program2.command("notification").description("Notification management commands");
298
- notification.command("list").description("List all notifications").option("--unread-only", "Show only unread notifications").option("--limit <number>", "Limit number of results", "20").action(async (options) => {
299
- const logger2 = getLogger();
300
- const client = getClient();
301
- const unreadOnly = Boolean(options.unreadOnly);
302
- const limit = parseInt(options.limit || "20");
303
- const res = await client.api.notifications.$get({
304
- query: { unreadOnly: String(unreadOnly), limit: String(limit) }
305
- });
306
- const data = await res.json();
307
- logger2.info(JSON.stringify(data, null, 2));
308
- });
309
- notification.command("create").description("Create a new notification").requiredOption("-t, --title <title>", "Notification title").requiredOption("-m, --message <message>", "Notification message").option("--type <type>", "Notification type (info|warning|success|error)", "info").action(async (options) => {
310
- const logger2 = getLogger();
311
- const client = getClient();
312
- const res = await client.api.notifications.$post({
313
- json: {
314
- type: options.type,
315
- title: options.title,
316
- message: options.message
317
- }
318
- });
319
- const data = await res.json();
320
- logger2.success("Notification created");
321
- logger2.info(JSON.stringify(data, null, 2));
322
- });
323
- notification.command("unread-count").description("Get unread notification count").action(async () => {
324
- const logger2 = getLogger();
325
- const client = getClient();
326
- const res = await client.api.notifications["unread-count"].$get();
327
- const data = await res.json();
328
- logger2.info(JSON.stringify(data, null, 2));
329
- });
330
- notification.command("mark-read").description("Mark a notification as read").argument("<id>", "Notification ID").action(async (id) => {
331
- const logger2 = getLogger();
332
- const client = getClient();
333
- const res = await client.api.notifications[":id"].read.$patch({ param: { id } });
334
- const data = await res.json();
335
- logger2.success("Notification marked as read");
336
- logger2.info(JSON.stringify(data, null, 2));
337
- });
338
- notification.command("delete").description("Delete a notification").argument("<id>", "Notification ID").action(async (id) => {
339
- const logger2 = getLogger();
340
- const client = getClient();
341
- const res = await client.api.notifications[":id"].$delete({ param: { id } });
342
- const data = await res.json();
343
- logger2.success("Notification deleted");
344
- logger2.info(JSON.stringify(data, null, 2));
345
- });
346
- }
347
- var CONFIG_DIR = path2.join(os.homedir(), ".biomimic");
348
- var CONFIG_FILE = path2.join(CONFIG_DIR, "config.json");
349
- function loadConfig() {
350
- try {
351
- if (fs.existsSync(CONFIG_FILE)) {
352
- const content = fs.readFileSync(CONFIG_FILE, "utf-8");
353
- return JSON.parse(content);
354
- }
355
- } catch {
356
- }
357
- return { baseUrl: "http://localhost:3010" };
358
- }
359
- function saveConfig(config) {
360
- if (!fs.existsSync(CONFIG_DIR)) {
361
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
362
- }
363
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
364
- }
365
- function registerConfigCommands(program2) {
366
- const config = program2.command("config").description("CLI configuration and service management");
367
- config.command("get").description("Show current configuration").option("-k, --key <key>", "Get specific config key").action((options) => {
368
- const logger2 = getLogger();
369
- const cfg = loadConfig();
370
- if (options.key) {
371
- const value = cfg[options.key];
372
- logger2.info(`${options.key}: ${value ?? "not set"}`);
373
- } else {
374
- logger2.info(JSON.stringify(cfg, null, 2));
375
- }
376
- });
377
- config.command("set").description("Set configuration value").option("-u, --url <url>", "Set server URL").action((options) => {
378
- const logger2 = getLogger();
379
- const cfg = loadConfig();
380
- if (options.url) {
381
- cfg.baseUrl = options.url;
382
- setBaseUrl(options.url);
383
- logger2.success(`Server URL set to: ${options.url}`);
384
- }
385
- saveConfig(cfg);
386
- });
387
- config.command("url").description("Show or set server URL").argument("[url]", "New server URL").action((url) => {
388
- const logger2 = getLogger();
389
- if (url) {
390
- const cfg = loadConfig();
391
- cfg.baseUrl = url;
392
- setBaseUrl(url);
393
- saveConfig(cfg);
394
- logger2.success(`Server URL set to: ${url}`);
395
- } else {
396
- logger2.info(`Current server URL: ${getBaseUrl()}`);
397
- }
398
- });
399
- config.command("status").description("Check server connection status").action(async () => {
400
- const logger2 = getLogger();
401
- const client = getClient();
402
- try {
403
- const res = await client.health.$get();
404
- const data = await res.json();
405
- logger2.success("Server is reachable");
406
- logger2.info(JSON.stringify(data, null, 2));
407
- } catch (error) {
408
- logger2.error(`Server not reachable: ${getBaseUrl()}`);
409
- logger2.error(String(error));
410
- }
411
- });
412
- config.command("reset").description("Reset configuration to defaults").action(() => {
413
- const logger2 = getLogger();
414
- const defaultConfig = { baseUrl: "http://localhost:3010" };
415
- saveConfig(defaultConfig);
416
- setBaseUrl(defaultConfig.baseUrl);
417
- logger2.success("Configuration reset to defaults");
418
- });
419
- config.command("path").description("Show config file path").action(() => {
420
- const logger2 = getLogger();
421
- logger2.info(`Config file: ${CONFIG_FILE}`);
422
- });
423
- }
424
-
425
- // src/cli/modules/index.ts
426
- function registerModules(program2) {
427
- registerTodoCommands(program2);
428
- registerNotificationCommands(program2);
429
- registerConfigCommands(program2);
430
- }
431
11
  var tsImportFn;
432
12
  async function getTsImport() {
433
13
  if (tsImportFn) return tsImportFn;
@@ -1145,6 +725,11 @@ var SEED_MODULES = [
1145
725
  module: "tenant",
1146
726
  importLine: "",
1147
727
  call: "import('../module-tenant/services/tenant-service').then(m => m.seedTenantsIfEmpty())"
728
+ },
729
+ {
730
+ module: "plugin",
731
+ importLine: "",
732
+ call: "import('../module-plugin/services/plugin-seed-service').then(m => m.seedPluginsIfEmpty())"
1148
733
  }
1149
734
  ];
1150
735
  var INITIAL_PERMISSIONS = `const initialPermissions = [
@@ -3426,7 +3011,7 @@ if (typeof window !== 'undefined') {
3426
3011
 
3427
3012
  // src/commands/create.ts
3428
3013
  var __filename$1 = fileURLToPath(import.meta.url);
3429
- var __dirname$1 = path2.dirname(__filename$1);
3014
+ var __dirname$1 = path.dirname(__filename$1);
3430
3015
  var TEMPLATE_PROJECT_NAME = "biomimic-todo-app";
3431
3016
  var TEMPLATE_DB_NAME = "biomimic-todo-db";
3432
3017
  var ScaffoldError = class extends Error {
@@ -3468,11 +3053,11 @@ function generateDbName(projectName) {
3468
3053
  return `${sanitized}-db`;
3469
3054
  }
3470
3055
  async function updateWranglerToml(targetDir, projectName) {
3471
- const wranglerPath = path2.join(targetDir, "wrangler.toml");
3472
- if (!await fs2.pathExists(wranglerPath)) {
3056
+ const wranglerPath = path.join(targetDir, "wrangler.toml");
3057
+ if (!await fs.pathExists(wranglerPath)) {
3473
3058
  return;
3474
3059
  }
3475
- let content = await fs2.readFile(wranglerPath, "utf-8");
3060
+ let content = await fs.readFile(wranglerPath, "utf-8");
3476
3061
  const dbName = generateDbName(projectName);
3477
3062
  content = content.replace(
3478
3063
  new RegExp(`^name = "${TEMPLATE_PROJECT_NAME}"`, "m"),
@@ -3486,43 +3071,43 @@ async function updateWranglerToml(targetDir, projectName) {
3486
3071
  /database_id = "[^"]+"/,
3487
3072
  `database_id = "" # TODO: Run 'wrangler d1 create ${dbName}' and paste the ID here`
3488
3073
  );
3489
- await fs2.writeFile(wranglerPath, content);
3074
+ await fs.writeFile(wranglerPath, content);
3490
3075
  }
3491
3076
  async function updatePackageJson(targetDir, projectName, resolved) {
3492
- const pkgJsonPath = path2.join(targetDir, "package.json");
3493
- if (!await fs2.pathExists(pkgJsonPath)) {
3077
+ const pkgJsonPath = path.join(targetDir, "package.json");
3078
+ if (!await fs.pathExists(pkgJsonPath)) {
3494
3079
  return;
3495
3080
  }
3496
- let pkgJson = await fs2.readJson(pkgJsonPath);
3081
+ let pkgJson = await fs.readJson(pkgJsonPath);
3497
3082
  pkgJson = filterPackageJson(pkgJson, resolved);
3498
3083
  pkgJson.name = projectName;
3499
3084
  if (pkgJson.bin) {
3500
3085
  delete pkgJson.bin;
3501
3086
  }
3502
- await fs2.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
3087
+ await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
3503
3088
  }
3504
3089
  async function updatePackageLockJson(targetDir, projectName) {
3505
- const lockFilePath = path2.join(targetDir, "package-lock.json");
3506
- if (!await fs2.pathExists(lockFilePath)) {
3090
+ const lockFilePath = path.join(targetDir, "package-lock.json");
3091
+ if (!await fs.pathExists(lockFilePath)) {
3507
3092
  return;
3508
3093
  }
3509
- const lockFile = await fs2.readJson(lockFilePath);
3094
+ const lockFile = await fs.readJson(lockFilePath);
3510
3095
  if (lockFile.name === TEMPLATE_PROJECT_NAME) {
3511
3096
  lockFile.name = projectName;
3512
3097
  }
3513
3098
  if (lockFile.packages?.[""]?.name === TEMPLATE_PROJECT_NAME) {
3514
3099
  lockFile.packages[""].name = projectName;
3515
3100
  }
3516
- await fs2.writeJson(lockFilePath, lockFile, { spaces: 2 });
3101
+ await fs.writeJson(lockFilePath, lockFile, { spaces: 2 });
3517
3102
  }
3518
3103
  async function updateReadme(targetDir, projectName) {
3519
- const readmePath = path2.join(targetDir, "README.md");
3520
- if (!await fs2.pathExists(readmePath)) {
3104
+ const readmePath = path.join(targetDir, "README.md");
3105
+ if (!await fs.pathExists(readmePath)) {
3521
3106
  return;
3522
3107
  }
3523
- let content = await fs2.readFile(readmePath, "utf-8");
3108
+ let content = await fs.readFile(readmePath, "utf-8");
3524
3109
  content = content.replace(/^# (.+)$/m, `# ${projectName}`);
3525
- await fs2.writeFile(readmePath, content);
3110
+ await fs.writeFile(readmePath, content);
3526
3111
  }
3527
3112
  async function createProject(projectNameOrOptions, useCurrentDir = false, preset) {
3528
3113
  let projectName;
@@ -3545,19 +3130,19 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
3545
3130
  if (!currentDir) {
3546
3131
  validateProjectName(projectName);
3547
3132
  }
3548
- const templateDir = path2.join(__dirname$1, "../../template");
3133
+ const templateDir = path.join(__dirname$1, "../../template");
3549
3134
  let targetDir;
3550
3135
  if (currentDir) {
3551
3136
  targetDir = process.cwd();
3552
- projectName = path2.basename(targetDir);
3137
+ projectName = path.basename(targetDir);
3553
3138
  } else if (outputDir) {
3554
- targetDir = path2.resolve(outputDir);
3555
- if (await fs2.pathExists(targetDir)) {
3139
+ targetDir = path.resolve(outputDir);
3140
+ if (await fs.pathExists(targetDir)) {
3556
3141
  throw new ScaffoldError(`Directory ${outputDir} already exists`);
3557
3142
  }
3558
3143
  } else {
3559
- targetDir = path2.resolve(process.cwd(), projectName);
3560
- if (await fs2.pathExists(targetDir)) {
3144
+ targetDir = path.resolve(process.cwd(), projectName);
3145
+ if (await fs.pathExists(targetDir)) {
3561
3146
  throw new ScaffoldError(`Directory ${projectName} already exists`);
3562
3147
  }
3563
3148
  }
@@ -3584,16 +3169,16 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
3584
3169
  for (const file of generatedFiles2) {
3585
3170
  console.log(` ${chalk.green("\u2713")} ${file}`);
3586
3171
  }
3587
- const gitignorePath2 = path2.join(templateDir, ".gitignore");
3172
+ const gitignorePath2 = path.join(templateDir, ".gitignore");
3588
3173
  let ignorePatterns2 = [];
3589
- if (await fs2.pathExists(gitignorePath2)) {
3590
- const gitignoreContent = await fs2.readFile(gitignorePath2, "utf-8");
3174
+ if (await fs.pathExists(gitignorePath2)) {
3175
+ const gitignoreContent = await fs.readFile(gitignorePath2, "utf-8");
3591
3176
  ignorePatterns2 = parseGitignore(gitignoreContent);
3592
3177
  }
3593
3178
  ignorePatterns2.push("node_modules", ".wrangler");
3594
3179
  const excludePatterns2 = getExcludePatterns(resolved, allManifests);
3595
3180
  let templateFileCount = 0;
3596
- const templateFiles = await fs2.readdir(templateDir, { recursive: true });
3181
+ const templateFiles = await fs.readdir(templateDir, { recursive: true });
3597
3182
  for (const file of templateFiles) {
3598
3183
  const relative = String(file);
3599
3184
  if (!relative) continue;
@@ -3628,21 +3213,21 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
3628
3213
  }
3629
3214
  if (!currentDir) {
3630
3215
  const dirSpinner = ora("Creating project directory...").start();
3631
- await fs2.ensureDir(targetDir);
3216
+ await fs.ensureDir(targetDir);
3632
3217
  dirSpinner.succeed(chalk.green("Project directory created"));
3633
3218
  }
3634
3219
  const copySpinner = ora("Copying template files...").start();
3635
- const gitignorePath = path2.join(templateDir, ".gitignore");
3220
+ const gitignorePath = path.join(templateDir, ".gitignore");
3636
3221
  let ignorePatterns = [];
3637
- if (await fs2.pathExists(gitignorePath)) {
3638
- const gitignoreContent = await fs2.readFile(gitignorePath, "utf-8");
3222
+ if (await fs.pathExists(gitignorePath)) {
3223
+ const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
3639
3224
  ignorePatterns = parseGitignore(gitignoreContent);
3640
3225
  }
3641
3226
  ignorePatterns.push("node_modules", ".wrangler");
3642
3227
  const excludePatterns = getExcludePatterns(resolved, allManifests);
3643
- await fs2.copy(templateDir, targetDir, {
3228
+ await fs.copy(templateDir, targetDir, {
3644
3229
  filter: (src) => {
3645
- const relative = path2.relative(templateDir, src);
3230
+ const relative = path.relative(templateDir, src);
3646
3231
  if (relative === "") return true;
3647
3232
  const negated = ignorePatterns.filter((p) => p.startsWith("!"));
3648
3233
  const gitIgnored = ignorePatterns.filter((p) => !p.startsWith("!") && relative.startsWith(p));
@@ -3664,80 +3249,80 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
3664
3249
  copySpinner.succeed(chalk.green("Template files copied"));
3665
3250
  const genSpinner = ora("Generating module-specific files...").start();
3666
3251
  const routeRegistryContent = generateRouteRegistry(resolved);
3667
- await fs2.writeFile(path2.join(targetDir, "src/server/route-registry.ts"), routeRegistryContent);
3252
+ await fs.writeFile(path.join(targetDir, "src/server/route-registry.ts"), routeRegistryContent);
3668
3253
  const dbSchemaContent = generateDbSchemaBarrel(resolved);
3669
- await fs2.writeFile(path2.join(targetDir, "src/server/db/schema/index.ts"), dbSchemaContent);
3254
+ await fs.writeFile(path.join(targetDir, "src/server/db/schema/index.ts"), dbSchemaContent);
3670
3255
  if (resolved.hasClient) {
3671
3256
  const clientNavContent = generateClientNavigation(resolved);
3672
- await fs2.writeFile(
3673
- path2.join(targetDir, "src/client/components/Navigation.tsx"),
3257
+ await fs.writeFile(
3258
+ path.join(targetDir, "src/client/components/Navigation.tsx"),
3674
3259
  clientNavContent
3675
3260
  );
3676
3261
  const clientAppTestContent = generateClientAppTest(resolved);
3677
- await fs2.ensureDir(path2.join(targetDir, "src/client/components/__tests__"));
3678
- await fs2.writeFile(
3679
- path2.join(targetDir, "src/client/components/__tests__/App.test.tsx"),
3262
+ await fs.ensureDir(path.join(targetDir, "src/client/components/__tests__"));
3263
+ await fs.writeFile(
3264
+ path.join(targetDir, "src/client/components/__tests__/App.test.tsx"),
3680
3265
  clientAppTestContent
3681
3266
  );
3682
3267
  const clientNavTestContent = generateClientNavigationTest(resolved);
3683
- await fs2.writeFile(
3684
- path2.join(targetDir, "src/client/components/__tests__/Navigation.test.tsx"),
3268
+ await fs.writeFile(
3269
+ path.join(targetDir, "src/client/components/__tests__/Navigation.test.tsx"),
3685
3270
  clientNavTestContent
3686
3271
  );
3687
3272
  const presetUIConfigContent = generatePresetUIConfig(resolved, selectedPreset.id);
3688
- await fs2.writeFile(
3689
- path2.join(targetDir, "src/client/preset-ui-config.ts"),
3273
+ await fs.writeFile(
3274
+ path.join(targetDir, "src/client/preset-ui-config.ts"),
3690
3275
  presetUIConfigContent
3691
3276
  );
3692
3277
  const clientMainContent = generateClientMain(resolved, selectedPreset.id);
3693
- await fs2.writeFile(path2.join(targetDir, "src/client/main.tsx"), clientMainContent);
3278
+ await fs.writeFile(path.join(targetDir, "src/client/main.tsx"), clientMainContent);
3694
3279
  }
3695
3280
  if (resolved.hasClient && resolved.modules.has("admin")) {
3696
3281
  const adminAppContent = generateAdminApp(resolved);
3697
3282
  if (adminAppContent) {
3698
- await fs2.ensureDir(path2.join(targetDir, "src/admin"));
3699
- await fs2.writeFile(path2.join(targetDir, "src/admin/App.tsx"), adminAppContent);
3283
+ await fs.ensureDir(path.join(targetDir, "src/admin"));
3284
+ await fs.writeFile(path.join(targetDir, "src/admin/App.tsx"), adminAppContent);
3700
3285
  }
3701
3286
  }
3702
3287
  const serverAppContent = generateServerApp(resolved);
3703
- await fs2.writeFile(path2.join(targetDir, "src/server/app.ts"), serverAppContent);
3288
+ await fs.writeFile(path.join(targetDir, "src/server/app.ts"), serverAppContent);
3704
3289
  const generatedFiles = getGeneratedFiles(resolved);
3705
3290
  if (generatedFiles.includes("src/server/db/init.ts")) {
3706
3291
  const dbInitContent = generateDbInit(resolved);
3707
- await fs2.writeFile(path2.join(targetDir, "src/server/db/init.ts"), dbInitContent);
3292
+ await fs.writeFile(path.join(targetDir, "src/server/db/init.ts"), dbInitContent);
3708
3293
  }
3709
3294
  const sharedModulesContent = generateSharedModulesIndex(resolved);
3710
- await fs2.writeFile(path2.join(targetDir, "src/shared/modules/index.ts"), sharedModulesContent);
3295
+ await fs.writeFile(path.join(targetDir, "src/shared/modules/index.ts"), sharedModulesContent);
3711
3296
  const sharedSchemasContent = generateSharedSchemasIndex(resolved);
3712
- await fs2.writeFile(path2.join(targetDir, "src/shared/schemas/index.ts"), sharedSchemasContent);
3297
+ await fs.writeFile(path.join(targetDir, "src/shared/schemas/index.ts"), sharedSchemasContent);
3713
3298
  const middlewareIndexContent = generateMiddlewareIndex(resolved);
3714
- await fs2.writeFile(
3715
- path2.join(targetDir, "src/server/middleware/index.ts"),
3299
+ await fs.writeFile(
3300
+ path.join(targetDir, "src/server/middleware/index.ts"),
3716
3301
  middlewareIndexContent
3717
3302
  );
3718
3303
  if (generatedFiles.includes("src/server/middleware/auth.ts")) {
3719
3304
  const authMiddlewareContent = generateAuthMiddleware(resolved);
3720
- await fs2.writeFile(
3721
- path2.join(targetDir, "src/server/middleware/auth.ts"),
3305
+ await fs.writeFile(
3306
+ path.join(targetDir, "src/server/middleware/auth.ts"),
3722
3307
  authMiddlewareContent
3723
3308
  );
3724
3309
  }
3725
3310
  if (generatedFiles.includes("src/server/utils/auth.ts")) {
3726
3311
  const authUtilsContent = generateAuthUtils(resolved);
3727
- await fs2.writeFile(path2.join(targetDir, "src/server/utils/auth.ts"), authUtilsContent);
3312
+ await fs.writeFile(path.join(targetDir, "src/server/utils/auth.ts"), authUtilsContent);
3728
3313
  }
3729
3314
  if (resolved.hasClient) {
3730
3315
  const clientComponentsContent = generateClientComponentsIndex(resolved);
3731
- await fs2.writeFile(
3732
- path2.join(targetDir, "src/client/components/index.ts"),
3316
+ await fs.writeFile(
3317
+ path.join(targetDir, "src/client/components/index.ts"),
3733
3318
  clientComponentsContent
3734
3319
  );
3735
3320
  }
3736
3321
  const cliModulesContent = generateCliModulesIndex(resolved);
3737
- await fs2.writeFile(path2.join(targetDir, "src/cli/modules/index.ts"), cliModulesContent);
3322
+ await fs.writeFile(path.join(targetDir, "src/cli/modules/index.ts"), cliModulesContent);
3738
3323
  if (resolved.hasClient && generatedFiles.includes("vite.config.ts")) {
3739
3324
  const viteConfigContent = generateViteConfig(resolved, templateDir);
3740
- await fs2.writeFile(path2.join(targetDir, "vite.config.ts"), viteConfigContent);
3325
+ await fs.writeFile(path.join(targetDir, "vite.config.ts"), viteConfigContent);
3741
3326
  }
3742
3327
  genSpinner.succeed(chalk.green("Module-specific files generated"));
3743
3328
  const pkgSpinner = ora("Configuring package.json...").start();
@@ -3789,37 +3374,66 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
3789
3374
  }
3790
3375
  }
3791
3376
 
3792
- // src/cli/index.ts
3793
- var MODULE_COMMANDS = ["todo", "notification", "config"];
3794
- program.name("create-fullstack-scaffold").description("Create a new fullstack scaffolded project").version("0.1.1").argument("[project-name]", "Name of the project to create").option("-v, --verbose", "Enable verbose output").option("-u, --url <url>", "Server URL", "http://localhost:3010").option("-p, --preset <preset>", "Template preset to use", "fullstack-admin").option("-d, --dry-run", "Preview files without creating them").option("--current-dir", "Scaffold in the current directory").action(async (projectName, cmdOptions) => {
3795
- if (projectName && !MODULE_COMMANDS.includes(projectName)) {
3796
- const isCurrentDir = cmdOptions.currentDir === true || projectName === ".";
3797
- createLogger({ verbose: cmdOptions.verbose });
3798
- const options = {
3799
- projectName: isCurrentDir ? "." : projectName,
3800
- currentDir: isCurrentDir,
3801
- preset: cmdOptions.preset,
3802
- dryRun: cmdOptions.dryRun
3803
- };
3804
- await createProject(options).catch((err) => {
3805
- console.error(`Error: ${err.message}`);
3806
- process.exit(1);
3807
- });
3808
- return;
3809
- }
3810
- if (!projectName) {
3811
- program.outputHelp();
3812
- process.exit(1);
3377
+ // src/index.ts
3378
+ var __filename2 = fileURLToPath(import.meta.url);
3379
+ var __dirname2 = path.dirname(__filename2);
3380
+ var rootDir = __dirname2.endsWith(path.join("src")) || __dirname2.endsWith(path.join("dist")) ? path.resolve(__dirname2, "..") : path.resolve(__dirname2, "..", "..");
3381
+ var packageJson = JSON.parse(readFileSync(path.join(rootDir, "package.json"), "utf-8"));
3382
+ var program = new Command();
3383
+ program.name("create-fullstack-scaffold").description("Create a new full-stack scaffold app with Todo List example").version(packageJson.version).argument("[project-name]", "Name of your project").option("-c, --current-dir", "Create project in current directory").option(
3384
+ "-p, --preset <preset>",
3385
+ "Template preset to use (fullstack-admin, todo-app, cli-only, minimal)"
3386
+ ).option("-o, --output-dir <path>", "Output directory (defaults to project name)").option("--dry-run", "Show what would be generated without creating files").action(
3387
+ async (projectName = "my-fullstack-app", options) => {
3388
+ console.log("");
3389
+ console.log(chalk.cyan.bold(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
3390
+ console.log(chalk.cyan.bold(" \u2551 Create Fullstack Scaffold App \u2551"));
3391
+ console.log(chalk.cyan.bold(" \u2551 React + Hono + Vite + Zustand + TS \u2551"));
3392
+ console.log(chalk.cyan.bold(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
3393
+ console.log("");
3394
+ let preset = options.preset;
3395
+ if (!preset && process.stdin.isTTY) {
3396
+ const templateDir = path.join(__dirname2, "../template");
3397
+ const presets = await loadPresets(templateDir);
3398
+ preset = await select({
3399
+ message: "Choose a template preset:",
3400
+ choices: presets.map((p) => ({
3401
+ value: p.id,
3402
+ name: `${p.name} \u2014 ${p.description}`
3403
+ }))
3404
+ });
3405
+ }
3406
+ if (!preset) {
3407
+ preset = "fullstack-admin";
3408
+ }
3409
+ try {
3410
+ await createProject({
3411
+ projectName,
3412
+ currentDir: options.currentDir ?? false,
3413
+ preset,
3414
+ outputDir: options.outputDir,
3415
+ dryRun: options.dryRun ?? false
3416
+ });
3417
+ } catch (error) {
3418
+ if (error instanceof ScaffoldError) {
3419
+ console.error(chalk.red(` \u2716 ${error.message}`));
3420
+ process.exit(1);
3421
+ }
3422
+ throw error;
3423
+ }
3813
3424
  }
3814
- });
3815
- program.hook("preAction", (thisCommand) => {
3816
- const options = thisCommand.opts();
3817
- createLogger({ verbose: options.verbose });
3818
- if (options.url) {
3819
- setBaseUrl(options.url);
3425
+ );
3426
+ program.command("presets").description("List available template presets").action(async () => {
3427
+ const templateDir = path.join(__dirname2, "../template");
3428
+ const presets = await loadPresets(templateDir);
3429
+ console.log(chalk.cyan("\nAvailable presets:\n"));
3430
+ for (const preset of presets) {
3431
+ console.log(` ${chalk.green(preset.id.padEnd(20))} ${preset.name}`);
3432
+ console.log(` ${" ".repeat(20)} ${preset.description}`);
3433
+ console.log(` ${" ".repeat(20)} Modules: ${preset.modules.join(", ")}`);
3434
+ console.log();
3820
3435
  }
3821
3436
  });
3822
- registerModules(program);
3823
3437
  program.parse();
3824
3438
  //# sourceMappingURL=index.js.map
3825
3439
  //# sourceMappingURL=index.js.map