@igniter-js/cli 0.2.0 → 0.2.2
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 +57 -0
- package/dist/index.js +1137 -961
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1132 -956
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -1
package/dist/index.mjs
CHANGED
|
@@ -302,8 +302,7 @@ var init_spinner = __esm({
|
|
|
302
302
|
});
|
|
303
303
|
|
|
304
304
|
// src/adapters/logger.ts
|
|
305
|
-
import { createConsoleLogger } from "@igniter-js/core";
|
|
306
|
-
import { IgniterLogLevel } from "@igniter-js/core";
|
|
305
|
+
import { IgniterLogLevel, createConsoleLogger } from "@igniter-js/core";
|
|
307
306
|
function getLogLevel() {
|
|
308
307
|
if (process.env.DEBUG === "true") {
|
|
309
308
|
return IgniterLogLevel.DEBUG;
|
|
@@ -323,6 +322,7 @@ function isInteractiveMode() {
|
|
|
323
322
|
}
|
|
324
323
|
function createChildLogger(context, options = {}) {
|
|
325
324
|
const childLogger = logger.child(context);
|
|
325
|
+
childLogger.spinner = (text, id) => cliSpinnerManager.createSpinner(text, id);
|
|
326
326
|
if (options.level) {
|
|
327
327
|
childLogger?.setLevel?.(options.level);
|
|
328
328
|
}
|
|
@@ -3518,7 +3518,7 @@ var require_lib = __commonJS({
|
|
|
3518
3518
|
}
|
|
3519
3519
|
return utf8;
|
|
3520
3520
|
};
|
|
3521
|
-
var
|
|
3521
|
+
var generate2 = module.exports = function generate3(str) {
|
|
3522
3522
|
var char;
|
|
3523
3523
|
var i = 0;
|
|
3524
3524
|
var start = -1;
|
|
@@ -3544,9 +3544,9 @@ var require_lib = __commonJS({
|
|
|
3544
3544
|
module.exports.generateMulti = function generateMulti(keys) {
|
|
3545
3545
|
var i = 1;
|
|
3546
3546
|
var len = keys.length;
|
|
3547
|
-
var base =
|
|
3547
|
+
var base = generate2(keys[0]);
|
|
3548
3548
|
while (i < len) {
|
|
3549
|
-
if (
|
|
3549
|
+
if (generate2(keys[i++]) !== base) return -1;
|
|
3550
3550
|
}
|
|
3551
3551
|
return base;
|
|
3552
3552
|
};
|
|
@@ -4275,19 +4275,21 @@ var require_supports_color = __commonJS({
|
|
|
4275
4275
|
var tty = __require("tty");
|
|
4276
4276
|
var hasFlag = require_has_flag();
|
|
4277
4277
|
var { env } = process;
|
|
4278
|
-
var
|
|
4278
|
+
var flagForceColor;
|
|
4279
4279
|
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
4280
|
-
|
|
4280
|
+
flagForceColor = 0;
|
|
4281
4281
|
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
4282
|
-
|
|
4282
|
+
flagForceColor = 1;
|
|
4283
4283
|
}
|
|
4284
|
-
|
|
4285
|
-
if (
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4284
|
+
function envForceColor() {
|
|
4285
|
+
if ("FORCE_COLOR" in env) {
|
|
4286
|
+
if (env.FORCE_COLOR === "true") {
|
|
4287
|
+
return 1;
|
|
4288
|
+
}
|
|
4289
|
+
if (env.FORCE_COLOR === "false") {
|
|
4290
|
+
return 0;
|
|
4291
|
+
}
|
|
4292
|
+
return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
|
|
4291
4293
|
}
|
|
4292
4294
|
}
|
|
4293
4295
|
function translateLevel(level) {
|
|
@@ -4301,15 +4303,22 @@ var require_supports_color = __commonJS({
|
|
|
4301
4303
|
has16m: level >= 3
|
|
4302
4304
|
};
|
|
4303
4305
|
}
|
|
4304
|
-
function supportsColor(haveStream, streamIsTTY) {
|
|
4306
|
+
function supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
4307
|
+
const noFlagForceColor = envForceColor();
|
|
4308
|
+
if (noFlagForceColor !== void 0) {
|
|
4309
|
+
flagForceColor = noFlagForceColor;
|
|
4310
|
+
}
|
|
4311
|
+
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
4305
4312
|
if (forceColor === 0) {
|
|
4306
4313
|
return 0;
|
|
4307
4314
|
}
|
|
4308
|
-
if (
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4315
|
+
if (sniffFlags) {
|
|
4316
|
+
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
4317
|
+
return 3;
|
|
4318
|
+
}
|
|
4319
|
+
if (hasFlag("color=256")) {
|
|
4320
|
+
return 2;
|
|
4321
|
+
}
|
|
4313
4322
|
}
|
|
4314
4323
|
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
4315
4324
|
return 0;
|
|
@@ -4326,7 +4335,7 @@ var require_supports_color = __commonJS({
|
|
|
4326
4335
|
return 1;
|
|
4327
4336
|
}
|
|
4328
4337
|
if ("CI" in env) {
|
|
4329
|
-
if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
|
|
4338
|
+
if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
|
|
4330
4339
|
return 1;
|
|
4331
4340
|
}
|
|
4332
4341
|
return min;
|
|
@@ -4338,7 +4347,7 @@ var require_supports_color = __commonJS({
|
|
|
4338
4347
|
return 3;
|
|
4339
4348
|
}
|
|
4340
4349
|
if ("TERM_PROGRAM" in env) {
|
|
4341
|
-
const version = parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
4350
|
+
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
4342
4351
|
switch (env.TERM_PROGRAM) {
|
|
4343
4352
|
case "iTerm.app":
|
|
4344
4353
|
return version >= 3 ? 3 : 2;
|
|
@@ -4357,14 +4366,17 @@ var require_supports_color = __commonJS({
|
|
|
4357
4366
|
}
|
|
4358
4367
|
return min;
|
|
4359
4368
|
}
|
|
4360
|
-
function getSupportLevel(stream) {
|
|
4361
|
-
const level = supportsColor(stream,
|
|
4369
|
+
function getSupportLevel(stream, options = {}) {
|
|
4370
|
+
const level = supportsColor(stream, {
|
|
4371
|
+
streamIsTTY: stream && stream.isTTY,
|
|
4372
|
+
...options
|
|
4373
|
+
});
|
|
4362
4374
|
return translateLevel(level);
|
|
4363
4375
|
}
|
|
4364
4376
|
module.exports = {
|
|
4365
4377
|
supportsColor: getSupportLevel,
|
|
4366
|
-
stdout:
|
|
4367
|
-
stderr:
|
|
4378
|
+
stdout: getSupportLevel({ isTTY: tty.isatty(1) }),
|
|
4379
|
+
stderr: getSupportLevel({ isTTY: tty.isatty(2) })
|
|
4368
4380
|
};
|
|
4369
4381
|
}
|
|
4370
4382
|
});
|
|
@@ -10065,11 +10077,11 @@ async function runInteractiveProcesses(configs) {
|
|
|
10065
10077
|
await manager.start();
|
|
10066
10078
|
}
|
|
10067
10079
|
async function runConcurrentProcesses(options) {
|
|
10068
|
-
const
|
|
10080
|
+
const logger6 = createChildLogger({ component: "concurrent-processes" });
|
|
10069
10081
|
if (options.interactive) {
|
|
10070
10082
|
return runInteractiveProcesses(options.processes);
|
|
10071
10083
|
}
|
|
10072
|
-
|
|
10084
|
+
logger6.info("Starting concurrent processes", {
|
|
10073
10085
|
processCount: options.processes.length,
|
|
10074
10086
|
processes: options.processes.map((p) => ({ name: p.name, command: p.command }))
|
|
10075
10087
|
});
|
|
@@ -10091,9 +10103,9 @@ async function runConcurrentProcesses(options) {
|
|
|
10091
10103
|
}
|
|
10092
10104
|
);
|
|
10093
10105
|
await result;
|
|
10094
|
-
|
|
10106
|
+
logger6.success("All processes completed successfully");
|
|
10095
10107
|
} catch (error) {
|
|
10096
|
-
|
|
10108
|
+
logger6.error("Concurrent processes failed", { error: formatError(error) });
|
|
10097
10109
|
throw error;
|
|
10098
10110
|
}
|
|
10099
10111
|
}
|
|
@@ -10123,8 +10135,8 @@ function createFrameworkDevProcess(framework, command, options = {}) {
|
|
|
10123
10135
|
};
|
|
10124
10136
|
}
|
|
10125
10137
|
async function startIgniterWithFramework(options) {
|
|
10126
|
-
const
|
|
10127
|
-
|
|
10138
|
+
const logger6 = createChildLogger({ component: "igniter-with-framework" });
|
|
10139
|
+
logger6.info("Starting Igniter with framework", {
|
|
10128
10140
|
framework: options.framework,
|
|
10129
10141
|
frameworkCommand: options.frameworkCommand,
|
|
10130
10142
|
port: options.port,
|
|
@@ -11440,11 +11452,11 @@ ${ANSI_COLORS.bold}TOP OPERATIONS${ANSI_COLORS.reset}`);
|
|
|
11440
11452
|
});
|
|
11441
11453
|
|
|
11442
11454
|
// src/adapters/build/generator.ts
|
|
11443
|
-
import * as
|
|
11444
|
-
import * as
|
|
11455
|
+
import * as fs6 from "fs";
|
|
11456
|
+
import * as path7 from "path";
|
|
11445
11457
|
function getFileSize(filePath) {
|
|
11446
11458
|
try {
|
|
11447
|
-
const stats =
|
|
11459
|
+
const stats = fs6.statSync(filePath);
|
|
11448
11460
|
const bytes = stats.size;
|
|
11449
11461
|
if (bytes < 1024) return `${bytes}b`;
|
|
11450
11462
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}kb`;
|
|
@@ -11454,7 +11466,6 @@ function getFileSize(filePath) {
|
|
|
11454
11466
|
}
|
|
11455
11467
|
}
|
|
11456
11468
|
function extractRouterSchema(router) {
|
|
11457
|
-
const logger4 = createChildLogger({ component: "schema-extractor" });
|
|
11458
11469
|
const controllersSchema = {};
|
|
11459
11470
|
let totalActions = 0;
|
|
11460
11471
|
for (const [controllerName, controller] of Object.entries(router.controllers)) {
|
|
@@ -11462,17 +11473,17 @@ function extractRouterSchema(router) {
|
|
|
11462
11473
|
if (controller && controller.actions) {
|
|
11463
11474
|
for (const [actionName, action] of Object.entries(controller.actions)) {
|
|
11464
11475
|
actionsSchema[actionName] = {
|
|
11476
|
+
name: action?.name || actionName,
|
|
11477
|
+
description: action?.description || "",
|
|
11465
11478
|
path: action?.path || "",
|
|
11466
|
-
method: action?.method || "GET"
|
|
11467
|
-
description: action?.description,
|
|
11468
|
-
// Keep type inference data
|
|
11469
|
-
$Infer: action?.$Infer
|
|
11479
|
+
method: action?.method || "GET"
|
|
11470
11480
|
};
|
|
11471
11481
|
totalActions++;
|
|
11472
11482
|
}
|
|
11473
11483
|
}
|
|
11474
11484
|
controllersSchema[controllerName] = {
|
|
11475
11485
|
name: controller?.name || controllerName,
|
|
11486
|
+
description: controller?.description || "",
|
|
11476
11487
|
path: controller?.path || "",
|
|
11477
11488
|
actions: actionsSchema
|
|
11478
11489
|
};
|
|
@@ -11482,7 +11493,12 @@ function extractRouterSchema(router) {
|
|
|
11482
11493
|
baseURL: router.config?.baseURL || "",
|
|
11483
11494
|
basePATH: router.config?.basePATH || ""
|
|
11484
11495
|
},
|
|
11485
|
-
controllers: controllersSchema
|
|
11496
|
+
controllers: controllersSchema,
|
|
11497
|
+
processor: {},
|
|
11498
|
+
handler: {},
|
|
11499
|
+
$context: {},
|
|
11500
|
+
$plugins: {},
|
|
11501
|
+
$caller: {}
|
|
11486
11502
|
};
|
|
11487
11503
|
return {
|
|
11488
11504
|
schema,
|
|
@@ -11493,41 +11509,41 @@ function extractRouterSchema(router) {
|
|
|
11493
11509
|
};
|
|
11494
11510
|
}
|
|
11495
11511
|
async function generateSchemaFromRouter(router, config) {
|
|
11496
|
-
const
|
|
11512
|
+
const logger6 = createChildLogger({ component: "generator" });
|
|
11497
11513
|
const startTime = performance.now();
|
|
11498
11514
|
const isInteractiveMode2 = !!(process.env.IGNITER_INTERACTIVE_MODE === "true" || process.argv.includes("--interactive"));
|
|
11499
11515
|
try {
|
|
11500
11516
|
let extractSpinner = null;
|
|
11501
11517
|
if (isInteractiveMode2) {
|
|
11502
|
-
|
|
11518
|
+
logger6.info("Extracting router schema...");
|
|
11503
11519
|
} else {
|
|
11504
11520
|
extractSpinner = createDetachedSpinner("Extracting router schema...");
|
|
11505
11521
|
extractSpinner.start();
|
|
11506
11522
|
}
|
|
11507
11523
|
const { schema, stats } = extractRouterSchema(router);
|
|
11508
11524
|
if (isInteractiveMode2) {
|
|
11509
|
-
|
|
11525
|
+
logger6.success(`Schema extracted - ${stats.controllers} controllers, ${stats.actions} actions`);
|
|
11510
11526
|
} else if (extractSpinner) {
|
|
11511
11527
|
extractSpinner.success(`Schema extracted - ${stats.controllers} controllers, ${stats.actions} actions`);
|
|
11512
11528
|
}
|
|
11513
11529
|
let dirSpinner = null;
|
|
11514
11530
|
if (isInteractiveMode2) {
|
|
11515
|
-
|
|
11531
|
+
logger6.info("Preparing output directory...");
|
|
11516
11532
|
} else {
|
|
11517
11533
|
dirSpinner = createDetachedSpinner("Preparing output directory...");
|
|
11518
11534
|
dirSpinner.start();
|
|
11519
11535
|
}
|
|
11520
11536
|
const outputDir = config.outputDir || "generated";
|
|
11521
11537
|
await ensureDirectoryExists(outputDir);
|
|
11522
|
-
const outputPath =
|
|
11538
|
+
const outputPath = path7.resolve(outputDir);
|
|
11523
11539
|
if (isInteractiveMode2) {
|
|
11524
|
-
|
|
11540
|
+
logger6.success(`Output directory ready ${outputPath}`);
|
|
11525
11541
|
} else if (dirSpinner) {
|
|
11526
11542
|
dirSpinner.success(`Output directory ready ${outputPath}`);
|
|
11527
11543
|
}
|
|
11528
11544
|
let filesSpinner = null;
|
|
11529
11545
|
if (isInteractiveMode2) {
|
|
11530
|
-
|
|
11546
|
+
logger6.info("Generating client files...");
|
|
11531
11547
|
} else {
|
|
11532
11548
|
filesSpinner = createDetachedSpinner("Generating client files...");
|
|
11533
11549
|
filesSpinner.start();
|
|
@@ -11537,7 +11553,7 @@ async function generateSchemaFromRouter(router, config) {
|
|
|
11537
11553
|
generateClientFile(schema, outputDir, config)
|
|
11538
11554
|
]);
|
|
11539
11555
|
if (isInteractiveMode2) {
|
|
11540
|
-
|
|
11556
|
+
logger6.success("Files generated successfully");
|
|
11541
11557
|
} else if (filesSpinner) {
|
|
11542
11558
|
filesSpinner.success("Files generated successfully");
|
|
11543
11559
|
}
|
|
@@ -11549,15 +11565,15 @@ async function generateSchemaFromRouter(router, config) {
|
|
|
11549
11565
|
let totalSize = 0;
|
|
11550
11566
|
files.forEach((file) => {
|
|
11551
11567
|
const size = getFileSize(file.path);
|
|
11552
|
-
totalSize +=
|
|
11553
|
-
|
|
11568
|
+
totalSize += fs6.statSync(file.path).size;
|
|
11569
|
+
logger6.info(`Generated ${file.name}`, { size, path: path7.relative(process.cwd(), file.path) });
|
|
11554
11570
|
});
|
|
11555
11571
|
const duration = ((performance.now() - startTime) / 1e3).toFixed(2);
|
|
11556
11572
|
const totalSizeFormatted = totalSize < 1024 ? `${totalSize}b` : totalSize < 1024 * 1024 ? `${(totalSize / 1024).toFixed(1)}kb` : `${(totalSize / (1024 * 1024)).toFixed(1)}mb`;
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11573
|
+
logger6.separator();
|
|
11574
|
+
logger6.success("Igniter.js development server is up and running");
|
|
11575
|
+
logger6.info("Press Ctrl+C to stop");
|
|
11576
|
+
logger6.info("Summary", {
|
|
11561
11577
|
output: outputPath,
|
|
11562
11578
|
files: files.length,
|
|
11563
11579
|
totalSize: totalSizeFormatted,
|
|
@@ -11566,13 +11582,13 @@ async function generateSchemaFromRouter(router, config) {
|
|
|
11566
11582
|
duration: `${duration}s`,
|
|
11567
11583
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11568
11584
|
});
|
|
11569
|
-
|
|
11585
|
+
logger6.groupEnd();
|
|
11570
11586
|
} else {
|
|
11571
11587
|
const duration = ((performance.now() - startTime) / 1e3).toFixed(2);
|
|
11572
|
-
|
|
11588
|
+
logger6.success(`Client generated successfully - ${stats.controllers} controllers, ${stats.actions} actions (${duration}s)`);
|
|
11573
11589
|
}
|
|
11574
11590
|
} catch (error) {
|
|
11575
|
-
|
|
11591
|
+
logger6.error("Client generation failed", {}, error);
|
|
11576
11592
|
throw error;
|
|
11577
11593
|
}
|
|
11578
11594
|
}
|
|
@@ -11583,75 +11599,77 @@ export const AppRouterSchema = ${JSON.stringify(schema, null, 2)} as const
|
|
|
11583
11599
|
|
|
11584
11600
|
export type AppRouterSchemaType = typeof AppRouterSchema
|
|
11585
11601
|
`;
|
|
11586
|
-
const filePath =
|
|
11602
|
+
const filePath = path7.join(outputDir, "igniter.schema.ts");
|
|
11587
11603
|
await writeFileWithHeader(filePath, content, config);
|
|
11588
11604
|
return filePath;
|
|
11589
11605
|
}
|
|
11590
11606
|
async function generateClientFile(schema, outputDir, config) {
|
|
11591
|
-
const content =
|
|
11592
|
-
|
|
11593
|
-
import { isServer } from '@igniter-js/core'
|
|
11594
|
-
import { createIgniterClient, useIgniterQueryClient } from '@igniter-js/core/client'
|
|
11595
|
-
import { AppRouterSchema } from './igniter.schema'
|
|
11607
|
+
const content = `import { createIgniterClient, useIgniterQueryClient } from '@igniter-js/core/client'
|
|
11596
11608
|
import type { AppRouterType } from './igniter.router'
|
|
11597
11609
|
|
|
11598
11610
|
/**
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
|
|
11603
|
-
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
export const api = createIgniterClient<AppRouterType>(
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11611
|
+
* Type-safe API client generated from your Igniter router
|
|
11612
|
+
*
|
|
11613
|
+
* Usage in Server Components:
|
|
11614
|
+
* const users = await api.users.list.query()
|
|
11615
|
+
*
|
|
11616
|
+
* Usage in Client Components:
|
|
11617
|
+
* const { data } = api.users.list.useQuery()
|
|
11618
|
+
*/
|
|
11619
|
+
export const api = createIgniterClient<AppRouterType>({
|
|
11620
|
+
baseURL: 'http://localhost:3000',
|
|
11621
|
+
basePath: '/api/v1/',
|
|
11622
|
+
router: () => {
|
|
11623
|
+
if (typeof window === 'undefined') {
|
|
11624
|
+
return require('./igniter.router').AppRouter
|
|
11625
|
+
}
|
|
11626
|
+
|
|
11627
|
+
return require('./igniter.schema').AppRouterSchema
|
|
11628
|
+
},
|
|
11613
11629
|
})
|
|
11614
11630
|
|
|
11615
11631
|
/**
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11632
|
+
* Type-safe API client generated from your Igniter router
|
|
11633
|
+
*
|
|
11634
|
+
* Usage in Server Components:
|
|
11635
|
+
* const users = await api.users.list.query()
|
|
11636
|
+
*
|
|
11637
|
+
* Usage in Client Components:
|
|
11638
|
+
* const { data } = api.users.list.useQuery()
|
|
11639
|
+
*/
|
|
11624
11640
|
export type ApiClient = typeof api
|
|
11625
11641
|
|
|
11626
11642
|
/**
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11643
|
+
* Type-safe query client generated from your Igniter router
|
|
11644
|
+
*
|
|
11645
|
+
* Usage in Client Components:
|
|
11646
|
+
* const { invalidate } = useQueryClient()
|
|
11647
|
+
*/
|
|
11632
11648
|
export const useQueryClient = useIgniterQueryClient<AppRouterType>;
|
|
11633
11649
|
`;
|
|
11634
|
-
const filePath =
|
|
11650
|
+
const filePath = path7.join(outputDir, "igniter.client.ts");
|
|
11635
11651
|
await writeFileWithHeader(filePath, content, config);
|
|
11636
11652
|
return filePath;
|
|
11637
11653
|
}
|
|
11638
11654
|
async function writeFileWithHeader(filePath, content, config) {
|
|
11639
11655
|
const header = generateFileHeader(config);
|
|
11640
11656
|
const fullContent = header + "\n\n" + content;
|
|
11641
|
-
await
|
|
11657
|
+
await fs6.promises.writeFile(filePath, fullContent, "utf8");
|
|
11642
11658
|
}
|
|
11643
11659
|
function generateFileHeader(config) {
|
|
11644
11660
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11645
|
-
return
|
|
11661
|
+
return `/* eslint-disable @typescript-eslint/no-var-requires */
|
|
11662
|
+
|
|
11663
|
+
/**
|
|
11646
11664
|
* Generated by @igniter-js/cli
|
|
11647
|
-
*
|
|
11665
|
+
*
|
|
11648
11666
|
* WARNING: DO NOT EDIT THIS FILE MANUALLY
|
|
11649
|
-
*
|
|
11667
|
+
*
|
|
11650
11668
|
* This file was automatically generated from your Igniter router.
|
|
11651
11669
|
* Any changes made to this file will be overwritten when the CLI regenerates it.
|
|
11652
|
-
*
|
|
11670
|
+
*
|
|
11653
11671
|
* To modify the client API, update your controller files instead.
|
|
11654
|
-
*
|
|
11672
|
+
*
|
|
11655
11673
|
* Generated: ${timestamp}
|
|
11656
11674
|
* Framework: ${config.framework}
|
|
11657
11675
|
* Output: ${config.outputDir}
|
|
@@ -11659,9 +11677,9 @@ function generateFileHeader(config) {
|
|
|
11659
11677
|
}
|
|
11660
11678
|
async function ensureDirectoryExists(dirPath) {
|
|
11661
11679
|
try {
|
|
11662
|
-
await
|
|
11680
|
+
await fs6.promises.access(dirPath);
|
|
11663
11681
|
} catch (error) {
|
|
11664
|
-
await
|
|
11682
|
+
await fs6.promises.mkdir(dirPath, { recursive: true });
|
|
11665
11683
|
}
|
|
11666
11684
|
}
|
|
11667
11685
|
var init_generator = __esm({
|
|
@@ -11677,9 +11695,10 @@ var watcher_exports = {};
|
|
|
11677
11695
|
__export(watcher_exports, {
|
|
11678
11696
|
IgniterWatcher: () => IgniterWatcher
|
|
11679
11697
|
});
|
|
11680
|
-
import * as
|
|
11681
|
-
import * as
|
|
11698
|
+
import * as fs7 from "fs";
|
|
11699
|
+
import * as path8 from "path";
|
|
11682
11700
|
import { pathToFileURL } from "url";
|
|
11701
|
+
import chokidar from "chokidar";
|
|
11683
11702
|
var IgniterWatcher;
|
|
11684
11703
|
var init_watcher = __esm({
|
|
11685
11704
|
"src/adapters/build/watcher.ts"() {
|
|
@@ -11771,9 +11790,9 @@ var init_watcher = __esm({
|
|
|
11771
11790
|
* Load router from file with simplified approach
|
|
11772
11791
|
*/
|
|
11773
11792
|
async loadRouter(routerPath) {
|
|
11774
|
-
const
|
|
11775
|
-
const fullPath =
|
|
11776
|
-
|
|
11793
|
+
const logger6 = createChildLogger({ component: "router-loader" });
|
|
11794
|
+
const fullPath = path8.resolve(process.cwd(), routerPath);
|
|
11795
|
+
logger6.debug("Loading router", { path: routerPath });
|
|
11777
11796
|
try {
|
|
11778
11797
|
const module = await this.loadWithTypeScriptSupport(fullPath);
|
|
11779
11798
|
if (module) {
|
|
@@ -11786,7 +11805,7 @@ var init_watcher = __esm({
|
|
|
11786
11805
|
const controllerCount = Object.keys(router.controllers || {}).length;
|
|
11787
11806
|
return router;
|
|
11788
11807
|
} else {
|
|
11789
|
-
|
|
11808
|
+
logger6.debug("Available exports", {
|
|
11790
11809
|
exports: Object.keys(module || {})
|
|
11791
11810
|
});
|
|
11792
11811
|
}
|
|
@@ -11810,7 +11829,7 @@ var init_watcher = __esm({
|
|
|
11810
11829
|
}
|
|
11811
11830
|
fallbackSpinner.error("Could not load router");
|
|
11812
11831
|
} catch (error) {
|
|
11813
|
-
|
|
11832
|
+
logger6.error("Failed to load router", { path: routerPath }, error);
|
|
11814
11833
|
}
|
|
11815
11834
|
return null;
|
|
11816
11835
|
}
|
|
@@ -11819,24 +11838,24 @@ var init_watcher = __esm({
|
|
|
11819
11838
|
* This is the NEW robust approach that replaces the problematic transpilation
|
|
11820
11839
|
*/
|
|
11821
11840
|
async loadWithTypeScriptSupport(filePath) {
|
|
11822
|
-
const
|
|
11841
|
+
const logger6 = createChildLogger({ component: "tsx-loader" });
|
|
11823
11842
|
const jsPath = filePath.replace(/\.ts$/, ".js");
|
|
11824
|
-
if (
|
|
11843
|
+
if (fs7.existsSync(jsPath)) {
|
|
11825
11844
|
try {
|
|
11826
|
-
|
|
11845
|
+
logger6.debug("Using compiled JS version");
|
|
11827
11846
|
delete __require.cache[jsPath];
|
|
11828
11847
|
const module = __require(jsPath);
|
|
11829
11848
|
return module;
|
|
11830
11849
|
} catch (error) {
|
|
11831
|
-
|
|
11850
|
+
logger6.debug("Compiled JS loading failed, trying TypeScript...");
|
|
11832
11851
|
}
|
|
11833
11852
|
}
|
|
11834
11853
|
if (filePath.endsWith(".ts")) {
|
|
11835
11854
|
try {
|
|
11836
|
-
|
|
11837
|
-
const { spawn:
|
|
11855
|
+
logger6.debug("Using TSX runtime loader");
|
|
11856
|
+
const { spawn: spawn2 } = __require("child_process");
|
|
11838
11857
|
const tsxCheckResult = await new Promise((resolve4) => {
|
|
11839
|
-
const checkChild =
|
|
11858
|
+
const checkChild = spawn2("npx", ["tsx", "--version"], {
|
|
11840
11859
|
stdio: "pipe",
|
|
11841
11860
|
cwd: process.cwd()
|
|
11842
11861
|
});
|
|
@@ -11860,7 +11879,7 @@ var init_watcher = __esm({
|
|
|
11860
11879
|
try {
|
|
11861
11880
|
const module = await import('${pathToFileURL(filePath).href}');
|
|
11862
11881
|
const router = module?.AppRouter || module?.default?.AppRouter || module?.default || module?.router;
|
|
11863
|
-
|
|
11882
|
+
|
|
11864
11883
|
if (router && typeof router === 'object') {
|
|
11865
11884
|
// Extract safe metadata for CLI use
|
|
11866
11885
|
const safeRouter = {
|
|
@@ -11870,13 +11889,13 @@ var init_watcher = __esm({
|
|
|
11870
11889
|
},
|
|
11871
11890
|
controllers: {}
|
|
11872
11891
|
};
|
|
11873
|
-
|
|
11892
|
+
|
|
11874
11893
|
// Extract controller metadata (no handlers/functions)
|
|
11875
11894
|
if (router.controllers && typeof router.controllers === 'object') {
|
|
11876
11895
|
for (const [controllerName, controller] of Object.entries(router.controllers)) {
|
|
11877
11896
|
if (controller && typeof controller === 'object' && (controller as any).actions) {
|
|
11878
11897
|
const safeActions: Record<string, any> = {};
|
|
11879
|
-
|
|
11898
|
+
|
|
11880
11899
|
for (const [actionName, action] of Object.entries((controller as any).actions)) {
|
|
11881
11900
|
if (action && typeof action === 'object') {
|
|
11882
11901
|
// Extract only metadata, no functions
|
|
@@ -11889,7 +11908,7 @@ var init_watcher = __esm({
|
|
|
11889
11908
|
};
|
|
11890
11909
|
}
|
|
11891
11910
|
}
|
|
11892
|
-
|
|
11911
|
+
|
|
11893
11912
|
safeRouter.controllers[controllerName] = {
|
|
11894
11913
|
name: (controller as any).name || controllerName,
|
|
11895
11914
|
path: (controller as any).path || '',
|
|
@@ -11898,7 +11917,7 @@ var init_watcher = __esm({
|
|
|
11898
11917
|
}
|
|
11899
11918
|
}
|
|
11900
11919
|
}
|
|
11901
|
-
|
|
11920
|
+
|
|
11902
11921
|
console.log('__ROUTER_SUCCESS__' + JSON.stringify(safeRouter));
|
|
11903
11922
|
process.exit(0); // Force exit after success
|
|
11904
11923
|
} else {
|
|
@@ -11910,10 +11929,10 @@ var init_watcher = __esm({
|
|
|
11910
11929
|
process.exit(1); // Force exit after error
|
|
11911
11930
|
}
|
|
11912
11931
|
}
|
|
11913
|
-
|
|
11932
|
+
|
|
11914
11933
|
loadRouter();
|
|
11915
11934
|
`;
|
|
11916
|
-
const child =
|
|
11935
|
+
const child = spawn2("npx", ["tsx", "-e", tsxScript], {
|
|
11917
11936
|
stdio: "pipe",
|
|
11918
11937
|
cwd: process.cwd(),
|
|
11919
11938
|
env: { ...process.env, NODE_NO_WARNINGS: "1" }
|
|
@@ -11957,7 +11976,7 @@ var init_watcher = __esm({
|
|
|
11957
11976
|
});
|
|
11958
11977
|
return result;
|
|
11959
11978
|
} catch (error) {
|
|
11960
|
-
|
|
11979
|
+
logger6.debug("TSX runtime loader failed", {}, error);
|
|
11961
11980
|
}
|
|
11962
11981
|
}
|
|
11963
11982
|
return null;
|
|
@@ -11966,9 +11985,9 @@ var init_watcher = __esm({
|
|
|
11966
11985
|
* Load router by resolving directory imports to index files
|
|
11967
11986
|
*/
|
|
11968
11987
|
async loadRouterWithIndexResolution(routerPath) {
|
|
11969
|
-
const
|
|
11988
|
+
const logger6 = createChildLogger({ component: "index-resolver" });
|
|
11970
11989
|
try {
|
|
11971
|
-
const routerContent =
|
|
11990
|
+
const routerContent = fs7.readFileSync(routerPath, "utf8");
|
|
11972
11991
|
const importRegex = /from\s+['\"]([^'\"]+)['\"]/g;
|
|
11973
11992
|
let resolvedContent = routerContent;
|
|
11974
11993
|
const matches = Array.from(routerContent.matchAll(importRegex));
|
|
@@ -11976,14 +11995,14 @@ var init_watcher = __esm({
|
|
|
11976
11995
|
if (!importPath.startsWith(".") && !importPath.startsWith("@")) {
|
|
11977
11996
|
continue;
|
|
11978
11997
|
}
|
|
11979
|
-
const basePath =
|
|
11998
|
+
const basePath = path8.dirname(routerPath);
|
|
11980
11999
|
let resolvedPath;
|
|
11981
12000
|
if (importPath.startsWith("@/")) {
|
|
11982
|
-
resolvedPath =
|
|
12001
|
+
resolvedPath = path8.resolve(process.cwd(), "src", importPath.substring(2));
|
|
11983
12002
|
} else if (importPath.startsWith("./")) {
|
|
11984
|
-
resolvedPath =
|
|
12003
|
+
resolvedPath = path8.resolve(basePath, importPath);
|
|
11985
12004
|
} else {
|
|
11986
|
-
resolvedPath =
|
|
12005
|
+
resolvedPath = path8.resolve(basePath, importPath);
|
|
11987
12006
|
}
|
|
11988
12007
|
let finalPath = importPath;
|
|
11989
12008
|
if (!importPath.match(/\\.(js|ts|tsx|jsx)$/)) {
|
|
@@ -11995,8 +12014,8 @@ var init_watcher = __esm({
|
|
|
11995
12014
|
];
|
|
11996
12015
|
let fileFound = false;
|
|
11997
12016
|
for (const filePath of possibleFiles) {
|
|
11998
|
-
if (
|
|
11999
|
-
const ext =
|
|
12017
|
+
if (fs7.existsSync(filePath)) {
|
|
12018
|
+
const ext = path8.extname(filePath);
|
|
12000
12019
|
finalPath = importPath + ext;
|
|
12001
12020
|
fileFound = true;
|
|
12002
12021
|
break;
|
|
@@ -12004,22 +12023,22 @@ var init_watcher = __esm({
|
|
|
12004
12023
|
}
|
|
12005
12024
|
if (!fileFound) {
|
|
12006
12025
|
const possibleIndexFiles = [
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12026
|
+
path8.join(resolvedPath, "index.ts"),
|
|
12027
|
+
path8.join(resolvedPath, "index.tsx"),
|
|
12028
|
+
path8.join(resolvedPath, "index.js"),
|
|
12029
|
+
path8.join(resolvedPath, "index.jsx")
|
|
12011
12030
|
];
|
|
12012
12031
|
for (const indexFile of possibleIndexFiles) {
|
|
12013
|
-
if (
|
|
12014
|
-
const ext =
|
|
12032
|
+
if (fs7.existsSync(indexFile)) {
|
|
12033
|
+
const ext = path8.extname(indexFile);
|
|
12015
12034
|
finalPath = importPath + "/index" + ext;
|
|
12016
12035
|
break;
|
|
12017
12036
|
}
|
|
12018
12037
|
}
|
|
12019
12038
|
}
|
|
12020
12039
|
}
|
|
12021
|
-
const absolutePath =
|
|
12022
|
-
if (
|
|
12040
|
+
const absolutePath = path8.resolve(basePath, finalPath);
|
|
12041
|
+
if (fs7.existsSync(absolutePath)) {
|
|
12023
12042
|
const fileUrl = pathToFileURL(absolutePath).href;
|
|
12024
12043
|
resolvedContent = resolvedContent.replace(fullMatch, `from '${fileUrl}'`);
|
|
12025
12044
|
} else {
|
|
@@ -12027,18 +12046,18 @@ var init_watcher = __esm({
|
|
|
12027
12046
|
}
|
|
12028
12047
|
}
|
|
12029
12048
|
const tempFileName = `igniter-temp-${Date.now()}.ts`;
|
|
12030
|
-
const tempFilePath =
|
|
12049
|
+
const tempFilePath = path8.join(process.cwd(), tempFileName);
|
|
12031
12050
|
try {
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
const { spawn:
|
|
12051
|
+
fs7.writeFileSync(tempFilePath, resolvedContent);
|
|
12052
|
+
logger6.debug("Loading resolved module via TSX");
|
|
12053
|
+
const { spawn: spawn2 } = __require("child_process");
|
|
12035
12054
|
const result = await new Promise((resolve4, reject) => {
|
|
12036
12055
|
const tsxScript = `
|
|
12037
12056
|
async function loadResolvedRouter() {
|
|
12038
12057
|
try {
|
|
12039
12058
|
const module = await import('${pathToFileURL(tempFilePath).href}');
|
|
12040
12059
|
const router = module?.AppRouter || module?.default?.AppRouter || module?.default || module?.router;
|
|
12041
|
-
|
|
12060
|
+
|
|
12042
12061
|
if (router && typeof router === 'object') {
|
|
12043
12062
|
// Extract safe metadata
|
|
12044
12063
|
const safeRouter = {
|
|
@@ -12048,12 +12067,12 @@ var init_watcher = __esm({
|
|
|
12048
12067
|
},
|
|
12049
12068
|
controllers: {}
|
|
12050
12069
|
};
|
|
12051
|
-
|
|
12070
|
+
|
|
12052
12071
|
if (router.controllers && typeof router.controllers === 'object') {
|
|
12053
12072
|
for (const [controllerName, controller] of Object.entries(router.controllers)) {
|
|
12054
12073
|
if (controller && typeof controller === 'object' && (controller as any).actions) {
|
|
12055
12074
|
const safeActions: Record<string, any> = {};
|
|
12056
|
-
|
|
12075
|
+
|
|
12057
12076
|
for (const [actionName, action] of Object.entries((controller as any).actions)) {
|
|
12058
12077
|
if (action && typeof action === 'object') {
|
|
12059
12078
|
safeActions[actionName] = {
|
|
@@ -12064,7 +12083,7 @@ var init_watcher = __esm({
|
|
|
12064
12083
|
};
|
|
12065
12084
|
}
|
|
12066
12085
|
}
|
|
12067
|
-
|
|
12086
|
+
|
|
12068
12087
|
safeRouter.controllers[controllerName] = {
|
|
12069
12088
|
name: (controller as any).name || controllerName,
|
|
12070
12089
|
path: (controller as any).path || '',
|
|
@@ -12073,7 +12092,7 @@ var init_watcher = __esm({
|
|
|
12073
12092
|
}
|
|
12074
12093
|
}
|
|
12075
12094
|
}
|
|
12076
|
-
|
|
12095
|
+
|
|
12077
12096
|
console.log('__ROUTER_SUCCESS__' + JSON.stringify(safeRouter));
|
|
12078
12097
|
process.exit(0); // Force exit after success
|
|
12079
12098
|
} else {
|
|
@@ -12085,10 +12104,10 @@ var init_watcher = __esm({
|
|
|
12085
12104
|
process.exit(1); // Force exit after error
|
|
12086
12105
|
}
|
|
12087
12106
|
}
|
|
12088
|
-
|
|
12107
|
+
|
|
12089
12108
|
loadResolvedRouter();
|
|
12090
12109
|
`;
|
|
12091
|
-
const child =
|
|
12110
|
+
const child = spawn2("npx", ["tsx", "-e", tsxScript], {
|
|
12092
12111
|
stdio: "pipe",
|
|
12093
12112
|
cwd: process.cwd(),
|
|
12094
12113
|
env: { ...process.env, NODE_NO_WARNINGS: "1" }
|
|
@@ -12132,12 +12151,12 @@ var init_watcher = __esm({
|
|
|
12132
12151
|
});
|
|
12133
12152
|
return result;
|
|
12134
12153
|
} finally {
|
|
12135
|
-
if (
|
|
12136
|
-
|
|
12154
|
+
if (fs7.existsSync(tempFilePath)) {
|
|
12155
|
+
fs7.unlinkSync(tempFilePath);
|
|
12137
12156
|
}
|
|
12138
12157
|
}
|
|
12139
12158
|
} catch (error) {
|
|
12140
|
-
|
|
12159
|
+
logger6.error("Index resolution failed", { path: routerPath }, error);
|
|
12141
12160
|
throw error;
|
|
12142
12161
|
}
|
|
12143
12162
|
}
|
|
@@ -12146,7 +12165,6 @@ var init_watcher = __esm({
|
|
|
12146
12165
|
*/
|
|
12147
12166
|
async start() {
|
|
12148
12167
|
try {
|
|
12149
|
-
const chokidar = await import("chokidar");
|
|
12150
12168
|
this.logger.info("Starting file watcher", {
|
|
12151
12169
|
output: this.config.outputDir,
|
|
12152
12170
|
patterns: this.config.controllerPatterns?.join(", ")
|
|
@@ -12237,7 +12255,7 @@ var init_watcher = __esm({
|
|
|
12237
12255
|
];
|
|
12238
12256
|
let router = null;
|
|
12239
12257
|
for (const routerPath of possibleRouterPaths) {
|
|
12240
|
-
if (
|
|
12258
|
+
if (fs7.existsSync(routerPath)) {
|
|
12241
12259
|
router = await this.loadRouter(routerPath);
|
|
12242
12260
|
if (router) {
|
|
12243
12261
|
break;
|
|
@@ -12265,15 +12283,14 @@ var init_watcher = __esm({
|
|
|
12265
12283
|
|
|
12266
12284
|
// src/index.ts
|
|
12267
12285
|
import { Command } from "commander";
|
|
12268
|
-
import * as
|
|
12269
|
-
import * as
|
|
12286
|
+
import * as fs8 from "fs";
|
|
12287
|
+
import * as path9 from "path";
|
|
12270
12288
|
|
|
12271
12289
|
// src/adapters/framework/framework-detector.ts
|
|
12272
|
-
init_logger();
|
|
12273
|
-
init_concurrent_processes();
|
|
12274
12290
|
import * as fs2 from "fs";
|
|
12275
12291
|
import * as path2 from "path";
|
|
12276
|
-
|
|
12292
|
+
init_logger();
|
|
12293
|
+
init_concurrent_processes();
|
|
12277
12294
|
var frameworkConfigs = {
|
|
12278
12295
|
nextjs: {
|
|
12279
12296
|
name: "nextjs",
|
|
@@ -12358,11 +12375,11 @@ var frameworkConfigs = {
|
|
|
12358
12375
|
}
|
|
12359
12376
|
};
|
|
12360
12377
|
function detectFramework(cwd = process.cwd()) {
|
|
12361
|
-
const
|
|
12362
|
-
|
|
12378
|
+
const logger6 = createChildLogger({ component: "framework-detector" });
|
|
12379
|
+
logger6.debug("Starting framework detection", { cwd });
|
|
12363
12380
|
for (const [frameworkName, config] of Object.entries(frameworkConfigs)) {
|
|
12364
12381
|
if (frameworkName === "generic") continue;
|
|
12365
|
-
|
|
12382
|
+
logger6.debug("Checking config files", {
|
|
12366
12383
|
framework: frameworkName,
|
|
12367
12384
|
configFiles: config.configFiles
|
|
12368
12385
|
});
|
|
@@ -12370,7 +12387,7 @@ function detectFramework(cwd = process.cwd()) {
|
|
|
12370
12387
|
const configPath = path2.join(cwd, configFile);
|
|
12371
12388
|
const exists = fs2.existsSync(configPath);
|
|
12372
12389
|
if (exists) {
|
|
12373
|
-
|
|
12390
|
+
logger6.debug("Found framework config file", {
|
|
12374
12391
|
framework: frameworkName,
|
|
12375
12392
|
configFile,
|
|
12376
12393
|
path: configPath
|
|
@@ -12385,7 +12402,7 @@ function detectFramework(cwd = process.cwd()) {
|
|
|
12385
12402
|
try {
|
|
12386
12403
|
const packageJsonPath = path2.join(cwd, "package.json");
|
|
12387
12404
|
if (fs2.existsSync(packageJsonPath)) {
|
|
12388
|
-
|
|
12405
|
+
logger6.debug("Reading package.json", { path: packageJsonPath });
|
|
12389
12406
|
const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf8"));
|
|
12390
12407
|
const allDeps = {
|
|
12391
12408
|
...packageJson.dependencies,
|
|
@@ -12393,14 +12410,14 @@ function detectFramework(cwd = process.cwd()) {
|
|
|
12393
12410
|
};
|
|
12394
12411
|
for (const [frameworkName, config] of Object.entries(frameworkConfigs)) {
|
|
12395
12412
|
if (frameworkName === "generic") continue;
|
|
12396
|
-
|
|
12413
|
+
logger6.debug("Checking dependencies", {
|
|
12397
12414
|
framework: frameworkName,
|
|
12398
12415
|
dependencies: config.packageDependencies
|
|
12399
12416
|
});
|
|
12400
12417
|
const hasFrameworkDep = config.packageDependencies.some((dep) => {
|
|
12401
12418
|
const exists = !!allDeps[dep];
|
|
12402
12419
|
if (exists) {
|
|
12403
|
-
|
|
12420
|
+
logger6.debug("Found framework dependency", {
|
|
12404
12421
|
framework: frameworkName,
|
|
12405
12422
|
dependency: dep,
|
|
12406
12423
|
version: allDeps[dep]
|
|
@@ -12409,134 +12426,35 @@ function detectFramework(cwd = process.cwd()) {
|
|
|
12409
12426
|
return exists;
|
|
12410
12427
|
});
|
|
12411
12428
|
if (hasFrameworkDep) {
|
|
12412
|
-
|
|
12429
|
+
logger6.info("Framework detected via dependencies", { framework: frameworkName });
|
|
12413
12430
|
return frameworkName;
|
|
12414
12431
|
}
|
|
12415
12432
|
}
|
|
12416
12433
|
}
|
|
12417
12434
|
} catch (error) {
|
|
12418
|
-
|
|
12435
|
+
logger6.warn("Could not read package.json", { error: formatError(error) });
|
|
12419
12436
|
}
|
|
12420
|
-
|
|
12437
|
+
logger6.info("No specific framework detected, using generic");
|
|
12421
12438
|
return "generic";
|
|
12422
12439
|
}
|
|
12423
12440
|
function detectPackageManager(cwd = process.cwd()) {
|
|
12424
|
-
const
|
|
12425
|
-
|
|
12441
|
+
const logger6 = createChildLogger({ component: "package-manager-detector" });
|
|
12442
|
+
logger6.debug("Detecting package manager", { cwd });
|
|
12426
12443
|
if (fs2.existsSync(path2.join(cwd, "bun.lockb"))) {
|
|
12427
|
-
|
|
12444
|
+
logger6.info("Detected package manager", { manager: "bun" });
|
|
12428
12445
|
return "bun";
|
|
12429
12446
|
}
|
|
12430
12447
|
if (fs2.existsSync(path2.join(cwd, "pnpm-lock.yaml"))) {
|
|
12431
|
-
|
|
12448
|
+
logger6.info("Detected package manager", { manager: "pnpm" });
|
|
12432
12449
|
return "pnpm";
|
|
12433
12450
|
}
|
|
12434
12451
|
if (fs2.existsSync(path2.join(cwd, "yarn.lock"))) {
|
|
12435
|
-
|
|
12452
|
+
logger6.info("Detected package manager", { manager: "yarn" });
|
|
12436
12453
|
return "yarn";
|
|
12437
12454
|
}
|
|
12438
|
-
|
|
12455
|
+
logger6.info("No lockfile found, using npm");
|
|
12439
12456
|
return "npm";
|
|
12440
12457
|
}
|
|
12441
|
-
function buildDevCommand(framework, packageManager, customCommand) {
|
|
12442
|
-
const logger4 = createChildLogger({ component: "command-builder" });
|
|
12443
|
-
if (customCommand) {
|
|
12444
|
-
logger4.info("Using custom command", { command: customCommand });
|
|
12445
|
-
return customCommand;
|
|
12446
|
-
}
|
|
12447
|
-
const config = frameworkConfigs[framework];
|
|
12448
|
-
const baseCommand = config.devCommand;
|
|
12449
|
-
const finalCommand = baseCommand.replace("npm run", `${packageManager} run`);
|
|
12450
|
-
logger4.info("Built dev command", {
|
|
12451
|
-
framework,
|
|
12452
|
-
packageManager,
|
|
12453
|
-
baseCommand,
|
|
12454
|
-
finalCommand
|
|
12455
|
-
});
|
|
12456
|
-
return finalCommand;
|
|
12457
|
-
}
|
|
12458
|
-
function createServerLogPrefix() {
|
|
12459
|
-
return "\u2502 ";
|
|
12460
|
-
}
|
|
12461
|
-
async function startDevServer(options = {}) {
|
|
12462
|
-
const logger4 = createChildLogger({ component: "dev-server" });
|
|
12463
|
-
const cwd = options.cwd || process.cwd();
|
|
12464
|
-
const framework = options.framework || detectFramework(cwd);
|
|
12465
|
-
const packageManager = detectPackageManager(cwd);
|
|
12466
|
-
const config = frameworkConfigs[framework];
|
|
12467
|
-
const command = buildDevCommand(framework, packageManager, options.command);
|
|
12468
|
-
const port = options.port || config.defaultPort;
|
|
12469
|
-
if (options.withIgniter) {
|
|
12470
|
-
logger4.info("Starting dev server with Igniter watcher", {
|
|
12471
|
-
framework,
|
|
12472
|
-
packageManager,
|
|
12473
|
-
command,
|
|
12474
|
-
port,
|
|
12475
|
-
igniterCommand: options.igniterCommand
|
|
12476
|
-
});
|
|
12477
|
-
try {
|
|
12478
|
-
await startIgniterWithFramework({
|
|
12479
|
-
framework,
|
|
12480
|
-
frameworkCommand: command,
|
|
12481
|
-
cwd,
|
|
12482
|
-
port,
|
|
12483
|
-
debug: options.debug,
|
|
12484
|
-
igniterWatcherCommand: options.igniterCommand
|
|
12485
|
-
});
|
|
12486
|
-
return;
|
|
12487
|
-
} catch (error) {
|
|
12488
|
-
logger4.error("Failed to start concurrent processes", { error: formatError(error) });
|
|
12489
|
-
throw error;
|
|
12490
|
-
}
|
|
12491
|
-
}
|
|
12492
|
-
logger4.info("Starting dev server", {
|
|
12493
|
-
framework,
|
|
12494
|
-
packageManager,
|
|
12495
|
-
command,
|
|
12496
|
-
port
|
|
12497
|
-
});
|
|
12498
|
-
const [cmd, ...args] = command.split(" ");
|
|
12499
|
-
const serverProcess = spawn2(cmd, args, {
|
|
12500
|
-
stdio: ["inherit", "pipe", "pipe"],
|
|
12501
|
-
cwd,
|
|
12502
|
-
env: {
|
|
12503
|
-
...process.env,
|
|
12504
|
-
PORT: port.toString(),
|
|
12505
|
-
NODE_ENV: "development"
|
|
12506
|
-
}
|
|
12507
|
-
});
|
|
12508
|
-
const logPrefix = createServerLogPrefix();
|
|
12509
|
-
serverProcess.stdout?.on("data", (data) => {
|
|
12510
|
-
const lines = data.toString().split("\n");
|
|
12511
|
-
lines.forEach((line) => {
|
|
12512
|
-
if (line.trim()) {
|
|
12513
|
-
console.log(`${logPrefix}${line}`);
|
|
12514
|
-
}
|
|
12515
|
-
});
|
|
12516
|
-
});
|
|
12517
|
-
serverProcess.stderr?.on("data", (data) => {
|
|
12518
|
-
const lines = data.toString().split("\n");
|
|
12519
|
-
lines.forEach((line) => {
|
|
12520
|
-
if (line.trim()) {
|
|
12521
|
-
console.error(`${logPrefix}\x1B[31m${line}\x1B[0m`);
|
|
12522
|
-
}
|
|
12523
|
-
});
|
|
12524
|
-
});
|
|
12525
|
-
serverProcess.on("error", (error) => {
|
|
12526
|
-
logger4.error("Dev server error", { error: formatError(error) });
|
|
12527
|
-
});
|
|
12528
|
-
serverProcess.on("exit", (code) => {
|
|
12529
|
-
console.log(`
|
|
12530
|
-
\u2514${"\u2500".repeat(60)}\u2518
|
|
12531
|
-
`);
|
|
12532
|
-
if (code === 0) {
|
|
12533
|
-
logger4.success("Dev server exited successfully");
|
|
12534
|
-
} else {
|
|
12535
|
-
logger4.error("Dev server exited with error", { code });
|
|
12536
|
-
}
|
|
12537
|
-
});
|
|
12538
|
-
return serverProcess;
|
|
12539
|
-
}
|
|
12540
12458
|
function isFrameworkSupported(framework) {
|
|
12541
12459
|
return framework in frameworkConfigs;
|
|
12542
12460
|
}
|
|
@@ -12573,7 +12491,7 @@ function showWelcome() {
|
|
|
12573
12491
|
console.log(chalk2.dim("This process will configure your project with everything you need."));
|
|
12574
12492
|
console.log();
|
|
12575
12493
|
}
|
|
12576
|
-
async function runSetupPrompts(targetDir) {
|
|
12494
|
+
async function runSetupPrompts(targetDir, isExistingProject = false) {
|
|
12577
12495
|
showWelcome();
|
|
12578
12496
|
const detectedFramework = detectFramework();
|
|
12579
12497
|
const detectedPackageManager = detectPackageManager();
|
|
@@ -12581,7 +12499,7 @@ async function runSetupPrompts(targetDir) {
|
|
|
12581
12499
|
try {
|
|
12582
12500
|
const answers = await prompts([
|
|
12583
12501
|
{
|
|
12584
|
-
type: "text",
|
|
12502
|
+
type: isExistingProject ? null : "text",
|
|
12585
12503
|
name: "projectName",
|
|
12586
12504
|
message: chalk2.bold("\u2022 What will your project be called?"),
|
|
12587
12505
|
initial: projectName,
|
|
@@ -12597,7 +12515,7 @@ async function runSetupPrompts(targetDir) {
|
|
|
12597
12515
|
{
|
|
12598
12516
|
type: "select",
|
|
12599
12517
|
name: "framework",
|
|
12600
|
-
message: chalk2.bold("\u2022 Which framework are you using?"),
|
|
12518
|
+
message: isExistingProject && detectedFramework !== "generic" ? `We detected ${chalk2.cyan(detectedFramework)}. Please confirm or select another.` : chalk2.bold("\u2022 Which framework are you using?"),
|
|
12601
12519
|
choices: [
|
|
12602
12520
|
{
|
|
12603
12521
|
title: `${chalk2.green("Next.js")} ${detectedFramework === "nextjs" ? chalk2.dim("(detected)") : ""}`,
|
|
@@ -12664,6 +12582,13 @@ async function runSetupPrompts(targetDir) {
|
|
|
12664
12582
|
value: "logging",
|
|
12665
12583
|
selected: true
|
|
12666
12584
|
// Default selected
|
|
12585
|
+
},
|
|
12586
|
+
{
|
|
12587
|
+
title: `${chalk2.yellow("Telemetry")}`,
|
|
12588
|
+
description: "Telemetry for tracking requests and errors",
|
|
12589
|
+
value: "telemetry",
|
|
12590
|
+
selected: true
|
|
12591
|
+
// Default selected
|
|
12667
12592
|
}
|
|
12668
12593
|
],
|
|
12669
12594
|
instructions: chalk2.dim("Use \u2191\u2193 to navigate, space to select, enter to confirm")
|
|
@@ -12688,10 +12613,6 @@ async function runSetupPrompts(targetDir) {
|
|
|
12688
12613
|
{
|
|
12689
12614
|
title: `${chalk2.green("SQLite + Prisma")} ${chalk2.dim("- Local development")}`,
|
|
12690
12615
|
value: "sqlite"
|
|
12691
|
-
},
|
|
12692
|
-
{
|
|
12693
|
-
title: `${chalk2.yellow("MongoDB + Mongoose")} ${chalk2.dim("- Document database")}`,
|
|
12694
|
-
value: "mongodb"
|
|
12695
12616
|
}
|
|
12696
12617
|
],
|
|
12697
12618
|
initial: 0
|
|
@@ -12706,7 +12627,7 @@ async function runSetupPrompts(targetDir) {
|
|
|
12706
12627
|
{
|
|
12707
12628
|
type: "select",
|
|
12708
12629
|
name: "packageManager",
|
|
12709
|
-
message: chalk2.bold("\u2022 Which package manager?"),
|
|
12630
|
+
message: isExistingProject ? `We detected ${chalk2.cyan(detectedPackageManager)}. Please confirm or select another.` : chalk2.bold("\u2022 Which package manager?"),
|
|
12710
12631
|
choices: [
|
|
12711
12632
|
{
|
|
12712
12633
|
title: `${chalk2.red("npm")} ${detectedPackageManager === "npm" ? chalk2.dim("(detected)") : ""}`,
|
|
@@ -12728,7 +12649,7 @@ async function runSetupPrompts(targetDir) {
|
|
|
12728
12649
|
initial: getPackageManagerChoiceIndex(detectedPackageManager)
|
|
12729
12650
|
},
|
|
12730
12651
|
{
|
|
12731
|
-
type: "confirm",
|
|
12652
|
+
type: isExistingProject ? null : "confirm",
|
|
12732
12653
|
name: "initGit",
|
|
12733
12654
|
message: chalk2.bold("\u2022 Initialize Git repository?"),
|
|
12734
12655
|
initial: true
|
|
@@ -12749,15 +12670,16 @@ async function runSetupPrompts(targetDir) {
|
|
|
12749
12670
|
store: answers.features.includes("store"),
|
|
12750
12671
|
jobs: answers.features.includes("jobs"),
|
|
12751
12672
|
mcp: answers.features.includes("mcp"),
|
|
12752
|
-
logging: answers.features.includes("logging")
|
|
12673
|
+
logging: answers.features.includes("logging"),
|
|
12674
|
+
telemetry: answers.features.includes("telemetry")
|
|
12753
12675
|
};
|
|
12754
12676
|
const config = {
|
|
12755
|
-
projectName: answers.projectName,
|
|
12677
|
+
projectName: answers.projectName || projectName,
|
|
12756
12678
|
framework: answers.framework,
|
|
12757
12679
|
features: featuresObj,
|
|
12758
12680
|
database: { provider: answers.database },
|
|
12759
12681
|
packageManager: answers.packageManager,
|
|
12760
|
-
initGit: answers.initGit,
|
|
12682
|
+
initGit: answers.initGit === void 0 ? false : answers.initGit,
|
|
12761
12683
|
installDependencies: answers.installDependencies,
|
|
12762
12684
|
dockerCompose: answers.dockerCompose || false
|
|
12763
12685
|
};
|
|
@@ -12800,11 +12722,11 @@ function getPackageManagerChoiceIndex(detected) {
|
|
|
12800
12722
|
const managers = ["npm", "yarn", "pnpm", "bun"];
|
|
12801
12723
|
return Math.max(0, managers.indexOf(detected));
|
|
12802
12724
|
}
|
|
12803
|
-
async function confirmOverwrite(
|
|
12725
|
+
async function confirmOverwrite(message) {
|
|
12804
12726
|
const { overwrite } = await prompts({
|
|
12805
12727
|
type: "confirm",
|
|
12806
12728
|
name: "overwrite",
|
|
12807
|
-
message
|
|
12729
|
+
message,
|
|
12808
12730
|
initial: false
|
|
12809
12731
|
});
|
|
12810
12732
|
return overwrite;
|
|
@@ -12824,7 +12746,7 @@ var IGNITER_FEATURES = {
|
|
|
12824
12746
|
name: "Redis Store",
|
|
12825
12747
|
description: "Caching, sessions, and pub/sub messaging",
|
|
12826
12748
|
dependencies: [
|
|
12827
|
-
{ name: "@igniter-js/adapter-redis", version: "
|
|
12749
|
+
{ name: "@igniter-js/adapter-redis", version: "alpha" },
|
|
12828
12750
|
{ name: "ioredis", version: "^5.6.1" }
|
|
12829
12751
|
],
|
|
12830
12752
|
devDependencies: [
|
|
@@ -12852,8 +12774,9 @@ var IGNITER_FEATURES = {
|
|
|
12852
12774
|
name: "BullMQ Jobs",
|
|
12853
12775
|
description: "Background task processing and job queues",
|
|
12854
12776
|
dependencies: [
|
|
12855
|
-
{ name: "@igniter-js/adapter-
|
|
12856
|
-
{ name: "bullmq", version: "
|
|
12777
|
+
{ name: "@igniter-js/adapter-redis", version: "alpha" },
|
|
12778
|
+
{ name: "@igniter-js/adapter-bullmq", version: "alpha" },
|
|
12779
|
+
{ name: "bullmq", version: "^4.0.0" },
|
|
12857
12780
|
{ name: "ioredis", version: "^5.6.1" }
|
|
12858
12781
|
],
|
|
12859
12782
|
devDependencies: [
|
|
@@ -12872,21 +12795,39 @@ var IGNITER_FEATURES = {
|
|
|
12872
12795
|
],
|
|
12873
12796
|
envVars: [
|
|
12874
12797
|
{ key: "REDIS_URL", value: "redis://localhost:6379", description: "Redis connection URL for jobs" },
|
|
12875
|
-
{ key: "
|
|
12798
|
+
{ key: "IGNITER_JOBS_QUEUE_PREFIX", value: "igniter", description: "Job queue prefix" }
|
|
12876
12799
|
]
|
|
12877
12800
|
},
|
|
12878
12801
|
mcp: {
|
|
12879
12802
|
key: "mcp",
|
|
12880
12803
|
name: "MCP Server",
|
|
12881
|
-
description: "AI
|
|
12804
|
+
description: "Easy expose your API as a MCP server for AI assistants like Cursor, Claude, etc.",
|
|
12882
12805
|
dependencies: [
|
|
12883
|
-
{ name: "@igniter-js/adapter-mcp", version: "
|
|
12806
|
+
{ name: "@igniter-js/adapter-mcp", version: "alpha" },
|
|
12884
12807
|
{ name: "@vercel/mcp-adapter", version: "^0.2.0" },
|
|
12885
|
-
{ name: "@modelcontextprotocol/sdk", version: "^1.10.2" }
|
|
12808
|
+
{ name: "@modelcontextprotocol/sdk", version: "^1.10.2" },
|
|
12809
|
+
{ name: "ioredis", version: "^5.6.1" }
|
|
12810
|
+
],
|
|
12811
|
+
devDependencies: [
|
|
12812
|
+
{ name: "@types/ioredis", version: "^4.28.10" }
|
|
12813
|
+
],
|
|
12814
|
+
dockerServices: [
|
|
12815
|
+
{
|
|
12816
|
+
name: "redis",
|
|
12817
|
+
image: "redis:7-alpine",
|
|
12818
|
+
ports: ["6379:6379"],
|
|
12819
|
+
environment: {
|
|
12820
|
+
REDIS_PASSWORD: ""
|
|
12821
|
+
},
|
|
12822
|
+
volumes: ["redis_data:/data"]
|
|
12823
|
+
}
|
|
12886
12824
|
],
|
|
12887
12825
|
envVars: [
|
|
12888
|
-
{ key: "
|
|
12889
|
-
{ key: "
|
|
12826
|
+
{ key: "IGNITER_MCP_SERVER_BASE_PATH", value: "/api/mcp", description: "MCP server base path" },
|
|
12827
|
+
{ key: "IGNITER_MCP_SERVER_TIMEOUT", value: "3600000", description: "MCP session timeout in ms" },
|
|
12828
|
+
{ key: "REDIS_URL", value: "redis://localhost:6379", description: "Redis connection URL" },
|
|
12829
|
+
{ key: "REDIS_HOST", value: "localhost", description: "Redis host" },
|
|
12830
|
+
{ key: "REDIS_PORT", value: "6379", description: "Redis port" }
|
|
12890
12831
|
]
|
|
12891
12832
|
},
|
|
12892
12833
|
logging: {
|
|
@@ -12894,11 +12835,24 @@ var IGNITER_FEATURES = {
|
|
|
12894
12835
|
name: "Enhanced Logging",
|
|
12895
12836
|
description: "Advanced console logging with structured output",
|
|
12896
12837
|
dependencies: [
|
|
12897
|
-
{ name: "@igniter-js/core", version: "
|
|
12838
|
+
{ name: "@igniter-js/core", version: "alpha" }
|
|
12898
12839
|
],
|
|
12899
12840
|
envVars: [
|
|
12900
|
-
{ key: "
|
|
12901
|
-
|
|
12841
|
+
{ key: "IGNITER_LOG_LEVEL", value: "info", description: "Logging level (debug, info, warn, error)" }
|
|
12842
|
+
]
|
|
12843
|
+
},
|
|
12844
|
+
telemetry: {
|
|
12845
|
+
key: "telemetry",
|
|
12846
|
+
name: "Telemetry",
|
|
12847
|
+
description: "Telemetry for tracking requests and errors",
|
|
12848
|
+
dependencies: [
|
|
12849
|
+
{ name: "@igniter-js/core", version: "alpha" }
|
|
12850
|
+
],
|
|
12851
|
+
envVars: [
|
|
12852
|
+
{ key: "IGNITER_TELEMETRY_ENABLE_TRACING", value: "true", description: "Enable telemetry tracing" },
|
|
12853
|
+
{ key: "IGNITER_TELEMETRY_ENABLE_METRICS", value: "true", description: "Enable telemetry metrics" },
|
|
12854
|
+
{ key: "IGNITER_TELEMETRY_ENABLE_EVENTS", value: "true", description: "Enable telemetry metrics" },
|
|
12855
|
+
{ key: "IGNITER_TELEMETRY_ENABLE_CLI_INTEGRATION", value: "true", description: "Enable telemetry metrics" }
|
|
12902
12856
|
]
|
|
12903
12857
|
}
|
|
12904
12858
|
};
|
|
@@ -13021,7 +12975,7 @@ function getEnvironmentVariables(enabledFeatures, databaseProvider, projectName)
|
|
|
13021
12975
|
if (dbConfig?.envVars) {
|
|
13022
12976
|
const dbEnvVars = dbConfig.envVars.map((envVar) => ({
|
|
13023
12977
|
...envVar,
|
|
13024
|
-
value: envVar.value
|
|
12978
|
+
value: envVar.value
|
|
13025
12979
|
}));
|
|
13026
12980
|
envVars.push(...dbEnvVars);
|
|
13027
12981
|
}
|
|
@@ -13036,29 +12990,30 @@ function generateIgniterRouter(config) {
|
|
|
13036
12990
|
const { features } = config;
|
|
13037
12991
|
let imports = [`import { Igniter } from '@igniter-js/core'`];
|
|
13038
12992
|
let serviceImports = [];
|
|
13039
|
-
imports.push('import
|
|
12993
|
+
imports.push('import { createIgniterAppContext } from "./igniter.context"');
|
|
13040
12994
|
if (features.store) {
|
|
13041
12995
|
serviceImports.push('import { store } from "@/services/store"');
|
|
13042
12996
|
}
|
|
13043
12997
|
if (features.jobs) {
|
|
13044
|
-
serviceImports.push('import {
|
|
12998
|
+
serviceImports.push('import { REGISTERED_JOBS } from "@/services/jobs"');
|
|
13045
12999
|
}
|
|
13046
13000
|
if (features.logging) {
|
|
13047
13001
|
serviceImports.push('import { logger } from "@/services/logger"');
|
|
13048
13002
|
}
|
|
13049
|
-
if (
|
|
13050
|
-
serviceImports.push('import {
|
|
13003
|
+
if (features.telemetry) {
|
|
13004
|
+
serviceImports.push('import { telemetry } from "@/services/telemetry"');
|
|
13051
13005
|
}
|
|
13052
13006
|
const allImports = [...imports, ...serviceImports].join("\n");
|
|
13053
|
-
let configChain = ["export const igniter = Igniter", " .context
|
|
13007
|
+
let configChain = ["export const igniter = Igniter", " .context(createIgniterAppContext)"];
|
|
13054
13008
|
if (features.store) configChain.push(" .store(store)");
|
|
13055
|
-
if (features.jobs) configChain.push(" .jobs(
|
|
13009
|
+
if (features.jobs) configChain.push(" .jobs(REGISTERED_JOBS)");
|
|
13056
13010
|
if (features.logging) configChain.push(" .logger(logger)");
|
|
13011
|
+
if (features.telemetry) configChain.push(" .telemetry(telemetry)");
|
|
13057
13012
|
configChain.push(" .create()");
|
|
13058
13013
|
const content = `${allImports}
|
|
13059
13014
|
|
|
13060
13015
|
/**
|
|
13061
|
-
* @description Initialize the Igniter.js
|
|
13016
|
+
* @description Initialize the Igniter.js
|
|
13062
13017
|
* @see https://github.com/felipebarcelospro/igniter-js
|
|
13063
13018
|
*/
|
|
13064
13019
|
${configChain.join("\n")}
|
|
@@ -13069,30 +13024,18 @@ ${configChain.join("\n")}
|
|
|
13069
13024
|
};
|
|
13070
13025
|
}
|
|
13071
13026
|
function generateIgniterContext(config) {
|
|
13072
|
-
const {
|
|
13027
|
+
const { database } = config;
|
|
13073
13028
|
let serviceImports = [];
|
|
13074
13029
|
let contextProperties = [];
|
|
13075
|
-
if (features.store) {
|
|
13076
|
-
serviceImports.push('import { store } from "@/services/store"');
|
|
13077
|
-
contextProperties.push(" store,");
|
|
13078
|
-
}
|
|
13079
|
-
if (features.jobs) {
|
|
13080
|
-
serviceImports.push('import { jobs } from "@/services/jobs"');
|
|
13081
|
-
contextProperties.push(" jobs,");
|
|
13082
|
-
}
|
|
13083
|
-
if (features.logging) {
|
|
13084
|
-
serviceImports.push('import { logger } from "@/services/logger"');
|
|
13085
|
-
contextProperties.push(" logger,");
|
|
13086
|
-
}
|
|
13087
13030
|
if (database.provider !== "none") {
|
|
13088
13031
|
serviceImports.push('import { database } from "@/services/database"');
|
|
13089
|
-
contextProperties.push(" database,");
|
|
13032
|
+
contextProperties.push(" // database,");
|
|
13090
13033
|
}
|
|
13091
13034
|
const allImports = serviceImports.join("\n");
|
|
13092
13035
|
const content = `${allImports}
|
|
13093
13036
|
|
|
13094
13037
|
/**
|
|
13095
|
-
* @description Create the context of the application
|
|
13038
|
+
* @description Create the context of the Igniter.js application
|
|
13096
13039
|
* @see https://github.com/felipebarcelospro/igniter-js
|
|
13097
13040
|
*/
|
|
13098
13041
|
export const createIgniterAppContext = () => {
|
|
@@ -13102,8 +13045,7 @@ ${contextProperties.join("\n")}
|
|
|
13102
13045
|
}
|
|
13103
13046
|
|
|
13104
13047
|
/**
|
|
13105
|
-
* @description The context of the application
|
|
13106
|
-
* Enhanced with store, jobs, and logger from Igniter.js builder
|
|
13048
|
+
* @description The context of the Igniter.js application
|
|
13107
13049
|
* @see https://github.com/felipebarcelospro/igniter-js
|
|
13108
13050
|
*/
|
|
13109
13051
|
export type IgniterAppContext = Awaited<ReturnType<typeof createIgniterAppContext>>
|
|
@@ -13119,11 +13061,13 @@ function generateExampleController(config) {
|
|
|
13119
13061
|
import { z } from 'zod'`;
|
|
13120
13062
|
let exampleActions = ` // Health check action
|
|
13121
13063
|
health: igniter.query({
|
|
13064
|
+
name: 'health',
|
|
13065
|
+
description: 'Health check',
|
|
13122
13066
|
path: '/',
|
|
13123
13067
|
handler: async ({ request, response, context }) => {
|
|
13124
13068
|
${features.logging ? "context.logger.info('Health check requested')" : ""}
|
|
13125
|
-
return response.success({
|
|
13126
|
-
status: 'ok',
|
|
13069
|
+
return response.success({
|
|
13070
|
+
status: 'ok',
|
|
13127
13071
|
timestamp: new Date().toISOString(),
|
|
13128
13072
|
features: {
|
|
13129
13073
|
store: ${features.store},
|
|
@@ -13139,33 +13083,32 @@ import { z } from 'zod'`;
|
|
|
13139
13083
|
|
|
13140
13084
|
// Cache demonstration action
|
|
13141
13085
|
cacheDemo: igniter.query({
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
}),
|
|
13086
|
+
name: 'cacheDemo',
|
|
13087
|
+
description: 'Demonstrate caching',
|
|
13088
|
+
path: '/cache/:key' as const,
|
|
13146
13089
|
handler: async ({ request, response, context }) => {
|
|
13147
13090
|
const { key } = request.params
|
|
13148
13091
|
const cached = await context.store.get(key)
|
|
13149
|
-
|
|
13092
|
+
|
|
13150
13093
|
if (cached) {
|
|
13151
|
-
return response.success({
|
|
13152
|
-
data: cached,
|
|
13153
|
-
source: 'cache'
|
|
13094
|
+
return response.success({
|
|
13095
|
+
data: cached,
|
|
13096
|
+
source: 'cache'
|
|
13154
13097
|
})
|
|
13155
13098
|
}
|
|
13156
|
-
|
|
13099
|
+
|
|
13157
13100
|
// Generate sample data
|
|
13158
|
-
const data = {
|
|
13101
|
+
const data = {
|
|
13159
13102
|
message: \`Hello from \${key}\`,
|
|
13160
13103
|
timestamp: new Date().toISOString()
|
|
13161
13104
|
}
|
|
13162
|
-
|
|
13105
|
+
|
|
13163
13106
|
// Cache for 1 hour
|
|
13164
13107
|
await context.store.set(key, data, { ttl: 3600 })
|
|
13165
|
-
|
|
13166
|
-
return response.success({
|
|
13167
|
-
data,
|
|
13168
|
-
source: 'generated'
|
|
13108
|
+
|
|
13109
|
+
return response.success({
|
|
13110
|
+
data,
|
|
13111
|
+
source: 'generated'
|
|
13169
13112
|
})
|
|
13170
13113
|
}
|
|
13171
13114
|
})`;
|
|
@@ -13175,25 +13118,28 @@ import { z } from 'zod'`;
|
|
|
13175
13118
|
|
|
13176
13119
|
// Background job scheduling action
|
|
13177
13120
|
scheduleJob: igniter.mutation({
|
|
13121
|
+
name: 'scheduleJob',
|
|
13122
|
+
description: 'Schedule a background job',
|
|
13178
13123
|
path: '/schedule-job',
|
|
13124
|
+
method: 'POST',
|
|
13179
13125
|
body: z.object({
|
|
13180
13126
|
message: z.string(),
|
|
13181
13127
|
delay: z.number().optional()
|
|
13182
13128
|
}),
|
|
13183
13129
|
handler: async ({ request, response, context }) => {
|
|
13184
13130
|
const { message, delay = 0 } = request.body
|
|
13185
|
-
|
|
13131
|
+
|
|
13186
13132
|
const jobId = await context.jobs.add('processMessage', {
|
|
13187
13133
|
message,
|
|
13188
13134
|
timestamp: new Date().toISOString()
|
|
13189
13135
|
}, { delay })
|
|
13190
|
-
|
|
13136
|
+
|
|
13191
13137
|
${features.logging ? "context.logger.info('Job scheduled', { jobId, message })" : ""}
|
|
13192
|
-
|
|
13193
|
-
return response.success({
|
|
13138
|
+
|
|
13139
|
+
return response.success({
|
|
13194
13140
|
jobId,
|
|
13195
13141
|
message: 'Job scheduled successfully',
|
|
13196
|
-
delay
|
|
13142
|
+
delay
|
|
13197
13143
|
})
|
|
13198
13144
|
}
|
|
13199
13145
|
})`;
|
|
@@ -13205,6 +13151,7 @@ import { z } from 'zod'`;
|
|
|
13205
13151
|
* @see https://github.com/felipebarcelospro/igniter-js
|
|
13206
13152
|
*/
|
|
13207
13153
|
export const exampleController = igniter.controller({
|
|
13154
|
+
name: 'example',
|
|
13208
13155
|
path: '/example',
|
|
13209
13156
|
actions: {
|
|
13210
13157
|
${exampleActions}
|
|
@@ -13225,14 +13172,12 @@ import { exampleController } from '@/features/example'
|
|
|
13225
13172
|
* @see https://github.com/felipebarcelospro/igniter-js
|
|
13226
13173
|
*/
|
|
13227
13174
|
export const AppRouter = igniter.router({
|
|
13228
|
-
baseURL: process.env.NEXT_PUBLIC_IGNITER_APP_URL, // Default is http://localhost:3000
|
|
13229
|
-
basePATH: process.env.NEXT_PUBLIC_IGNITER_APP_BASE_PATH, // Default is /api/v1
|
|
13230
13175
|
controllers: {
|
|
13231
13176
|
example: exampleController
|
|
13232
13177
|
}
|
|
13233
13178
|
})
|
|
13234
13179
|
|
|
13235
|
-
export type
|
|
13180
|
+
export type AppRouterType = typeof AppRouter
|
|
13236
13181
|
`;
|
|
13237
13182
|
return {
|
|
13238
13183
|
path: "src/igniter.router.ts",
|
|
@@ -13251,103 +13196,228 @@ export * from './example.interfaces'
|
|
|
13251
13196
|
function generateServiceFiles(config) {
|
|
13252
13197
|
const { features, database } = config;
|
|
13253
13198
|
const files = [];
|
|
13199
|
+
files.push({
|
|
13200
|
+
path: "src/app/api/v1/[[...all]]/route.ts",
|
|
13201
|
+
content: `import { AppRouter } from '@/igniter.router'
|
|
13202
|
+
import { nextRouteHandlerAdapter } from '@igniter-js/core/adapters'
|
|
13203
|
+
|
|
13204
|
+
export const { GET, POST, PUT, DELETE } = nextRouteHandlerAdapter(AppRouter)
|
|
13205
|
+
`
|
|
13206
|
+
});
|
|
13207
|
+
if (features.store || features.jobs) {
|
|
13208
|
+
files.push({
|
|
13209
|
+
path: "src/services/redis.ts",
|
|
13210
|
+
content: `import { Redis } from 'ioredis'
|
|
13211
|
+
|
|
13212
|
+
/**
|
|
13213
|
+
* Redis client instance for caching, session storage, and pub/sub.
|
|
13214
|
+
*
|
|
13215
|
+
* @remarks
|
|
13216
|
+
* Used for caching, session management, and real-time messaging.
|
|
13217
|
+
*
|
|
13218
|
+
* @see https://github.com/luin/ioredis
|
|
13219
|
+
*/
|
|
13220
|
+
export const redis = new Redis(process.env.REDIS_URL!, {
|
|
13221
|
+
maxRetriesPerRequest: null,
|
|
13222
|
+
})
|
|
13223
|
+
`
|
|
13224
|
+
});
|
|
13225
|
+
}
|
|
13254
13226
|
if (features.store) {
|
|
13255
13227
|
files.push({
|
|
13256
13228
|
path: "src/services/store.ts",
|
|
13257
13229
|
content: `import { createRedisStoreAdapter } from '@igniter-js/adapter-redis'
|
|
13258
|
-
import {
|
|
13230
|
+
import { redis } from './redis'
|
|
13259
13231
|
|
|
13260
13232
|
/**
|
|
13261
|
-
|
|
13262
|
-
|
|
13263
|
-
|
|
13264
|
-
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
|
|
13268
|
-
|
|
13269
|
-
*/
|
|
13270
|
-
export const store = createRedisStoreAdapter({ redis })
|
|
13233
|
+
* Store adapter for data persistence.
|
|
13234
|
+
*
|
|
13235
|
+
* @remarks
|
|
13236
|
+
* Provides a unified interface for data storage operations using Redis.
|
|
13237
|
+
*
|
|
13238
|
+
* @see https://github.com/felipebarcelospro/igniter-js/tree/main/packages/adapter-redis
|
|
13239
|
+
*/
|
|
13240
|
+
export const store = createRedisStoreAdapter(redis)
|
|
13271
13241
|
`
|
|
13272
13242
|
});
|
|
13273
13243
|
}
|
|
13274
13244
|
if (features.jobs) {
|
|
13275
13245
|
files.push({
|
|
13276
13246
|
path: "src/services/jobs.ts",
|
|
13277
|
-
content: `import {
|
|
13278
|
-
import {
|
|
13247
|
+
content: `import { store } from './store'
|
|
13248
|
+
import { createBullMQAdapter } from '@igniter-js/adapter-bullmq'
|
|
13249
|
+
import { z } from 'zod'
|
|
13279
13250
|
|
|
13280
13251
|
/**
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13252
|
+
* Job queue adapter for background processing.
|
|
13253
|
+
*
|
|
13254
|
+
* @remarks
|
|
13255
|
+
* Handles asynchronous job processing with BullMQ.
|
|
13256
|
+
*
|
|
13257
|
+
* @see https://github.com/felipebarcelospro/igniter-js/tree/main/packages/adapter-bullmq
|
|
13258
|
+
*/
|
|
13259
|
+
export const jobs = createBullMQAdapter({
|
|
13260
|
+
store,
|
|
13261
|
+
autoStartWorker: {
|
|
13262
|
+
concurrency: 1,
|
|
13263
|
+
queues: ['*']
|
|
13264
|
+
}
|
|
13265
|
+
})
|
|
13284
13266
|
|
|
13285
|
-
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
|
|
13267
|
+
export const REGISTERED_JOBS = jobs.merge({
|
|
13268
|
+
system: jobs.router({
|
|
13269
|
+
jobs: {
|
|
13270
|
+
sampleJob: jobs.register({
|
|
13271
|
+
name: 'sampleJob',
|
|
13272
|
+
input: z.object({
|
|
13273
|
+
message: z.string()
|
|
13274
|
+
}),
|
|
13275
|
+
handler: async ({ input }) => {
|
|
13276
|
+
console.log(input.message)
|
|
13277
|
+
}
|
|
13278
|
+
})
|
|
13279
|
+
}
|
|
13280
|
+
})
|
|
13281
|
+
})
|
|
13290
13282
|
`
|
|
13291
13283
|
});
|
|
13292
13284
|
}
|
|
13293
13285
|
if (features.logging) {
|
|
13294
13286
|
files.push({
|
|
13295
13287
|
path: "src/services/logger.ts",
|
|
13296
|
-
content: `import { createConsoleLogger } from '@igniter-js/core'
|
|
13288
|
+
content: `import { createConsoleLogger, IgniterLogLevel } from '@igniter-js/core'
|
|
13297
13289
|
|
|
13298
13290
|
/**
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
|
|
13303
|
-
|
|
13291
|
+
* Logger instance for application logging.
|
|
13292
|
+
*
|
|
13293
|
+
* @remarks
|
|
13294
|
+
* Provides structured logging with configurable log levels.
|
|
13295
|
+
*
|
|
13296
|
+
* @see https://github.com/felipebarcelospro/igniter-js/tree/main/packages/core
|
|
13297
|
+
*/
|
|
13298
|
+
export const logger = createConsoleLogger({
|
|
13299
|
+
level: IgniterLogLevel.INFO,
|
|
13300
|
+
showTimestamp: true,
|
|
13304
13301
|
})
|
|
13305
13302
|
`
|
|
13306
13303
|
});
|
|
13307
13304
|
}
|
|
13308
13305
|
if (database.provider !== "none") {
|
|
13309
|
-
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
content: `import mongoose from 'mongoose'
|
|
13306
|
+
files.push({
|
|
13307
|
+
path: "src/services/database.ts",
|
|
13308
|
+
content: `import { PrismaClient } from '@prisma/client'
|
|
13313
13309
|
|
|
13314
13310
|
/**
|
|
13315
|
-
*
|
|
13316
|
-
*
|
|
13311
|
+
* Prisma client instance for database operations.
|
|
13312
|
+
*
|
|
13313
|
+
* @remarks
|
|
13314
|
+
* Provides type-safe database access with Prisma ORM.
|
|
13315
|
+
*
|
|
13316
|
+
* @see https://www.prisma.io/docs/concepts/components/prisma-client
|
|
13317
13317
|
*/
|
|
13318
|
-
export const database =
|
|
13318
|
+
export const database = new PrismaClient()
|
|
13319
13319
|
`
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
|
|
13323
|
-
|
|
13324
|
-
|
|
13320
|
+
});
|
|
13321
|
+
}
|
|
13322
|
+
if (features.telemetry) {
|
|
13323
|
+
files.push({
|
|
13324
|
+
path: "src/services/telemetry.ts",
|
|
13325
|
+
content: `import { createConsoleTelemetryAdapter } from '@igniter-js/core/adapters'
|
|
13326
|
+
import { store } from './store'
|
|
13327
|
+
|
|
13328
|
+
/**
|
|
13329
|
+
* Telemetry service for tracking requests and errors.
|
|
13330
|
+
*
|
|
13331
|
+
* @remarks
|
|
13332
|
+
* Provides telemetry tracking with configurable options.
|
|
13333
|
+
*
|
|
13334
|
+
* @see https://github.com/felipebarcelospro/igniter-js/tree/main/packages/core
|
|
13335
|
+
*/
|
|
13336
|
+
export const telemetry = createConsoleTelemetryAdapter({
|
|
13337
|
+
serviceName: 'my-igniter-app',
|
|
13338
|
+
enableEvents: process.env.IGNITER_TELEMETRY_ENABLE_EVENTS === 'true',
|
|
13339
|
+
enableMetrics: process.env.IGNITER_TELEMETRY_ENABLE_METRICS === 'true',
|
|
13340
|
+
enableTracing: process.env.IGNITER_TELEMETRY_ENABLE_TRACING === 'true',
|
|
13341
|
+
}, {
|
|
13342
|
+
enableCliIntegration: process.env.IGNITER_TELEMETRY_ENABLE_CLI_INTEGRATION === 'true',
|
|
13343
|
+
store: store
|
|
13344
|
+
})
|
|
13345
|
+
`
|
|
13346
|
+
});
|
|
13347
|
+
}
|
|
13348
|
+
if (features.mcp) {
|
|
13349
|
+
files.push({
|
|
13350
|
+
path: "src/app/api/mcp/[transport].ts",
|
|
13351
|
+
content: `import { createMcpAdapter } from '@igniter-js/adapter-mcp'
|
|
13352
|
+
import { AppRouter } from '@/igniter.router'
|
|
13325
13353
|
|
|
13326
13354
|
/**
|
|
13327
|
-
*
|
|
13328
|
-
*
|
|
13355
|
+
* MCP server instance for exposing API as a MCP server.
|
|
13356
|
+
*
|
|
13357
|
+
* @see https://github.com/felipebarcelospro/igniter-js/tree/main/packages/adapter-mcp
|
|
13329
13358
|
*/
|
|
13330
|
-
export
|
|
13359
|
+
export default createMcpAdapter(AppRouter, {
|
|
13360
|
+
serverInfo: {
|
|
13361
|
+
name: 'Igniter.js MCP Server',
|
|
13362
|
+
version: '1.0.0',
|
|
13363
|
+
},
|
|
13364
|
+
adapter: {
|
|
13365
|
+
redis: {
|
|
13366
|
+
url: process.env.REDIS_URL!,
|
|
13367
|
+
maxRetriesPerRequest: null,
|
|
13368
|
+
},
|
|
13369
|
+
basePath: process.env.IGNITER_MCP_SERVER_BASE_PATH || '/api/mcp',
|
|
13370
|
+
maxDuration: process.env.IGNITER_MCP_SERVER_TIMEOUT || 60,
|
|
13371
|
+
},
|
|
13372
|
+
})
|
|
13331
13373
|
`
|
|
13332
|
-
|
|
13333
|
-
}
|
|
13374
|
+
});
|
|
13334
13375
|
}
|
|
13335
13376
|
return files;
|
|
13336
13377
|
}
|
|
13337
13378
|
function generateIgniterClient(config) {
|
|
13338
|
-
const content = `import { createIgniterClient } from '@igniter-js/core/client'
|
|
13339
|
-
import type {
|
|
13379
|
+
const content = `import { createIgniterClient, useIgniterQueryClient } from '@igniter-js/core/client'
|
|
13380
|
+
import type { AppRouterType } from './igniter.router'
|
|
13340
13381
|
|
|
13341
13382
|
/**
|
|
13342
|
-
|
|
13343
|
-
|
|
13344
|
-
|
|
13345
|
-
|
|
13346
|
-
|
|
13347
|
-
|
|
13383
|
+
* Type-safe API client generated from your Igniter router
|
|
13384
|
+
*
|
|
13385
|
+
* Usage in Server Components:
|
|
13386
|
+
* const users = await api.users.list.query()
|
|
13387
|
+
*
|
|
13388
|
+
* Usage in Client Components:
|
|
13389
|
+
* const { data } = api.users.list.useQuery()
|
|
13390
|
+
*/
|
|
13391
|
+
export const api = createIgniterClient<AppRouterType>({
|
|
13392
|
+
baseURL: 'http://localhost:3000',
|
|
13393
|
+
basePath: '/api/v1/',
|
|
13394
|
+
router: () => {
|
|
13395
|
+
if (typeof window === 'undefined') {
|
|
13396
|
+
return require('./igniter.router').AppRouter
|
|
13397
|
+
}
|
|
13398
|
+
|
|
13399
|
+
return require('./igniter.schema').AppRouterSchema
|
|
13400
|
+
},
|
|
13348
13401
|
})
|
|
13349
13402
|
|
|
13350
|
-
|
|
13403
|
+
/**
|
|
13404
|
+
* Type-safe API client generated from your Igniter router
|
|
13405
|
+
*
|
|
13406
|
+
* Usage in Server Components:
|
|
13407
|
+
* const users = await api.users.list.query()
|
|
13408
|
+
*
|
|
13409
|
+
* Usage in Client Components:
|
|
13410
|
+
* const { data } = api.users.list.useQuery()
|
|
13411
|
+
*/
|
|
13412
|
+
export type ApiClient = typeof api
|
|
13413
|
+
|
|
13414
|
+
/**
|
|
13415
|
+
* Type-safe query client generated from your Igniter router
|
|
13416
|
+
*
|
|
13417
|
+
* Usage in Client Components:
|
|
13418
|
+
* const { invalidate } = useQueryClient()
|
|
13419
|
+
*/
|
|
13420
|
+
export const useQueryClient = useIgniterQueryClient<AppRouterType>;
|
|
13351
13421
|
`;
|
|
13352
13422
|
return {
|
|
13353
13423
|
path: "src/igniter.client.ts",
|
|
@@ -13388,117 +13458,6 @@ export interface ExampleJobPayload {
|
|
|
13388
13458
|
content
|
|
13389
13459
|
};
|
|
13390
13460
|
}
|
|
13391
|
-
function generatePackageJson(config, dependencies, devDependencies) {
|
|
13392
|
-
const scripts = {
|
|
13393
|
-
"dev": "next dev",
|
|
13394
|
-
"build": "next build",
|
|
13395
|
-
"start": "next start",
|
|
13396
|
-
"lint": "next lint",
|
|
13397
|
-
"type-check": "tsc --noEmit"
|
|
13398
|
-
};
|
|
13399
|
-
if (config.database.provider !== "none" && config.database.provider !== "mongodb") {
|
|
13400
|
-
scripts["db:generate"] = "prisma generate";
|
|
13401
|
-
scripts["db:push"] = "prisma db push";
|
|
13402
|
-
scripts["db:studio"] = "prisma studio";
|
|
13403
|
-
scripts["db:migrate"] = "prisma migrate dev";
|
|
13404
|
-
}
|
|
13405
|
-
switch (config.framework) {
|
|
13406
|
-
case "vite":
|
|
13407
|
-
scripts.dev = "vite";
|
|
13408
|
-
scripts.build = "vite build";
|
|
13409
|
-
scripts.start = "vite preview";
|
|
13410
|
-
break;
|
|
13411
|
-
case "nuxt":
|
|
13412
|
-
scripts.dev = "nuxt dev";
|
|
13413
|
-
scripts.build = "nuxt build";
|
|
13414
|
-
scripts.start = "nuxt start";
|
|
13415
|
-
break;
|
|
13416
|
-
case "sveltekit":
|
|
13417
|
-
scripts.dev = "vite dev";
|
|
13418
|
-
scripts.build = "svelte-kit build";
|
|
13419
|
-
scripts.start = "node build";
|
|
13420
|
-
break;
|
|
13421
|
-
case "remix":
|
|
13422
|
-
scripts.dev = "remix dev";
|
|
13423
|
-
scripts.build = "remix build";
|
|
13424
|
-
scripts.start = "remix-serve build";
|
|
13425
|
-
break;
|
|
13426
|
-
case "astro":
|
|
13427
|
-
scripts.dev = "astro dev";
|
|
13428
|
-
scripts.build = "astro build";
|
|
13429
|
-
scripts.start = "astro preview";
|
|
13430
|
-
break;
|
|
13431
|
-
case "express":
|
|
13432
|
-
scripts.dev = "tsx watch src/server.ts";
|
|
13433
|
-
scripts.build = "tsc";
|
|
13434
|
-
scripts.start = "node dist/server.js";
|
|
13435
|
-
break;
|
|
13436
|
-
}
|
|
13437
|
-
const deps = dependencies.reduce((acc, dep) => {
|
|
13438
|
-
const [name, version] = dep.split("@");
|
|
13439
|
-
acc[name] = version;
|
|
13440
|
-
return acc;
|
|
13441
|
-
}, {});
|
|
13442
|
-
const devDeps = devDependencies.reduce((acc, dep) => {
|
|
13443
|
-
const [name, version] = dep.split("@");
|
|
13444
|
-
acc[name] = version;
|
|
13445
|
-
return acc;
|
|
13446
|
-
}, {});
|
|
13447
|
-
const packageJson = {
|
|
13448
|
-
name: config.projectName,
|
|
13449
|
-
version: "0.1.0",
|
|
13450
|
-
private: true,
|
|
13451
|
-
scripts,
|
|
13452
|
-
dependencies: deps,
|
|
13453
|
-
devDependencies: devDeps
|
|
13454
|
-
};
|
|
13455
|
-
return {
|
|
13456
|
-
path: "package.json",
|
|
13457
|
-
content: JSON.stringify(packageJson, null, 2)
|
|
13458
|
-
};
|
|
13459
|
-
}
|
|
13460
|
-
function generateTsConfig(framework) {
|
|
13461
|
-
let compilerOptions = {
|
|
13462
|
-
target: "ES2020",
|
|
13463
|
-
lib: ["ES2020", "DOM"],
|
|
13464
|
-
allowJs: true,
|
|
13465
|
-
skipLibCheck: true,
|
|
13466
|
-
strict: true,
|
|
13467
|
-
forceConsistentCasingInFileNames: true,
|
|
13468
|
-
noEmit: true,
|
|
13469
|
-
esModuleInterop: true,
|
|
13470
|
-
module: "esnext",
|
|
13471
|
-
moduleResolution: "node",
|
|
13472
|
-
resolveJsonModule: true,
|
|
13473
|
-
isolatedModules: true,
|
|
13474
|
-
jsx: "preserve",
|
|
13475
|
-
incremental: true,
|
|
13476
|
-
baseUrl: ".",
|
|
13477
|
-
paths: {
|
|
13478
|
-
"@/*": ["./src/*"]
|
|
13479
|
-
}
|
|
13480
|
-
};
|
|
13481
|
-
switch (framework) {
|
|
13482
|
-
case "nextjs":
|
|
13483
|
-
compilerOptions.plugins = [{ name: "next" }];
|
|
13484
|
-
break;
|
|
13485
|
-
case "vite":
|
|
13486
|
-
compilerOptions.types = ["vite/client"];
|
|
13487
|
-
break;
|
|
13488
|
-
case "nuxt":
|
|
13489
|
-
compilerOptions.paths["~/*"] = ["./src/*"];
|
|
13490
|
-
break;
|
|
13491
|
-
}
|
|
13492
|
-
const tsConfig = {
|
|
13493
|
-
compilerOptions,
|
|
13494
|
-
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
13495
|
-
exclude: ["node_modules"]
|
|
13496
|
-
};
|
|
13497
|
-
return {
|
|
13498
|
-
path: "tsconfig.json",
|
|
13499
|
-
content: JSON.stringify(tsConfig, null, 2)
|
|
13500
|
-
};
|
|
13501
|
-
}
|
|
13502
13461
|
function generateEnvFile(config) {
|
|
13503
13462
|
const envVars = getEnvironmentVariables(
|
|
13504
13463
|
Object.entries(config.features).filter(([_, enabled]) => enabled).map(([key, _]) => key),
|
|
@@ -13575,171 +13534,19 @@ ${Object.keys(dockerCompose.volumes).map((volume) => ` ${volume}:`).join("\n")}
|
|
|
13575
13534
|
`
|
|
13576
13535
|
};
|
|
13577
13536
|
}
|
|
13578
|
-
function
|
|
13579
|
-
const content = `# Dependencies
|
|
13580
|
-
node_modules/
|
|
13581
|
-
npm-debug.log*
|
|
13582
|
-
yarn-debug.log*
|
|
13583
|
-
yarn-error.log*
|
|
13584
|
-
.npm
|
|
13585
|
-
.yarn/
|
|
13586
|
-
|
|
13587
|
-
# Environment
|
|
13588
|
-
.env
|
|
13589
|
-
.env.local
|
|
13590
|
-
.env.development.local
|
|
13591
|
-
.env.test.local
|
|
13592
|
-
.env.production.local
|
|
13593
|
-
|
|
13594
|
-
# Build outputs
|
|
13595
|
-
dist/
|
|
13596
|
-
build/
|
|
13597
|
-
.next/
|
|
13598
|
-
.nuxt/
|
|
13599
|
-
.svelte-kit/
|
|
13600
|
-
|
|
13601
|
-
# Database
|
|
13602
|
-
*.db
|
|
13603
|
-
*.sqlite
|
|
13604
|
-
prisma/migrations/
|
|
13605
|
-
|
|
13606
|
-
# IDE
|
|
13607
|
-
.vscode/
|
|
13608
|
-
.idea/
|
|
13609
|
-
*.swp
|
|
13610
|
-
*.swo
|
|
13611
|
-
*~
|
|
13612
|
-
|
|
13613
|
-
# OS
|
|
13614
|
-
.DS_Store
|
|
13615
|
-
Thumbs.db
|
|
13616
|
-
|
|
13617
|
-
# Logs
|
|
13618
|
-
logs
|
|
13619
|
-
*.log
|
|
13620
|
-
|
|
13621
|
-
# Coverage
|
|
13622
|
-
coverage/
|
|
13623
|
-
.nyc_output/
|
|
13624
|
-
|
|
13625
|
-
# Cache
|
|
13626
|
-
.cache/
|
|
13627
|
-
.tmp/
|
|
13628
|
-
.temp/
|
|
13629
|
-
`;
|
|
13630
|
-
return {
|
|
13631
|
-
path: ".gitignore",
|
|
13632
|
-
content
|
|
13633
|
-
};
|
|
13634
|
-
}
|
|
13635
|
-
function generateReadme(config) {
|
|
13636
|
-
const enabledFeatures = Object.entries(config.features).filter(([_, enabled]) => enabled).map(([key, _]) => `- **${key}**: Enabled`);
|
|
13637
|
-
const content = `# ${config.projectName}
|
|
13638
|
-
|
|
13639
|
-
A modern, type-safe API built with [Igniter.js](https://github.com/felipebarcelospro/igniter-js) and ${config.framework}.
|
|
13640
|
-
|
|
13641
|
-
## Features
|
|
13642
|
-
|
|
13643
|
-
${enabledFeatures.join("\n")}
|
|
13644
|
-
${config.database.provider !== "none" ? `- **Database**: ${config.database.provider}` : ""}
|
|
13645
|
-
${config.dockerCompose ? "- **Docker**: Compose setup included" : ""}
|
|
13646
|
-
|
|
13647
|
-
## Development
|
|
13648
|
-
|
|
13649
|
-
### Prerequisites
|
|
13650
|
-
|
|
13651
|
-
- Node.js 18+
|
|
13652
|
-
- ${config.packageManager}
|
|
13653
|
-
${config.dockerCompose ? "- Docker and Docker Compose" : ""}
|
|
13654
|
-
|
|
13655
|
-
### Getting Started
|
|
13656
|
-
|
|
13657
|
-
1. **Install dependencies:**
|
|
13658
|
-
\`\`\`bash
|
|
13659
|
-
${config.packageManager} install
|
|
13660
|
-
\`\`\`
|
|
13661
|
-
|
|
13662
|
-
${config.dockerCompose ? `2. **Start services with Docker:**
|
|
13663
|
-
\`\`\`bash
|
|
13664
|
-
docker-compose up -d
|
|
13665
|
-
\`\`\`
|
|
13666
|
-
|
|
13667
|
-
` : ""}${config.database.provider !== "none" && config.database.provider !== "mongodb" ? `3. **Setup database:**
|
|
13668
|
-
\`\`\`bash
|
|
13669
|
-
${config.packageManager} run db:push
|
|
13670
|
-
\`\`\`
|
|
13671
|
-
|
|
13672
|
-
` : ""}4. **Start development server:**
|
|
13673
|
-
\`\`\`bash
|
|
13674
|
-
${config.packageManager} run dev
|
|
13675
|
-
\`\`\`
|
|
13676
|
-
|
|
13677
|
-
Visit [http://localhost:3000](http://localhost:3000) to see your app!
|
|
13678
|
-
|
|
13679
|
-
## Project Structure
|
|
13680
|
-
|
|
13681
|
-
\`\`\`
|
|
13682
|
-
src/
|
|
13683
|
-
\u251C\u2500\u2500 igniter.ts # Core initialization
|
|
13684
|
-
\u251C\u2500\u2500 igniter.client.ts # Client implementation
|
|
13685
|
-
\u251C\u2500\u2500 igniter.context.ts # Context management
|
|
13686
|
-
\u251C\u2500\u2500 igniter.router.ts # Router configuration
|
|
13687
|
-
\u251C\u2500\u2500 features/ # Application features
|
|
13688
|
-
\u2502 \u2514\u2500\u2500 example/
|
|
13689
|
-
\u2502 \u251C\u2500\u2500 controllers/ # Feature controllers
|
|
13690
|
-
\u2502 \u251C\u2500\u2500 procedures/ # Feature procedures/middleware
|
|
13691
|
-
\u2502 \u251C\u2500\u2500 example.interfaces.ts # Type definitions
|
|
13692
|
-
\u2502 \u2514\u2500\u2500 index.ts # Feature exports
|
|
13693
|
-
\u2514\u2500\u2500 services/ # Service layer
|
|
13694
|
-
\`\`\`
|
|
13695
|
-
|
|
13696
|
-
## API Endpoints
|
|
13697
|
-
|
|
13698
|
-
- \`GET /api/v1/example\` - Health check
|
|
13699
|
-
${config.features.store ? "- `GET /api/v1/example/cache/:key` - Cache example" : ""}
|
|
13700
|
-
${config.features.jobs ? "- `POST /api/v1/example/schedule-job` - Schedule background job" : ""}
|
|
13701
|
-
|
|
13702
|
-
## Learn More
|
|
13703
|
-
|
|
13704
|
-
- [Igniter.js Documentation](https://github.com/felipebarcelospro/igniter-js)
|
|
13705
|
-
- [${config.framework} Documentation](https://docs.${config.framework === "nextjs" ? "nextjs.org" : config.framework + ".dev"})
|
|
13706
|
-
${config.database.provider !== "none" && config.database.provider !== "mongodb" ? "- [Prisma Documentation](https://prisma.io/docs)" : ""}
|
|
13707
|
-
${config.database.provider === "mongodb" ? "- [Mongoose Documentation](https://mongoosejs.com/docs)" : ""}
|
|
13708
|
-
|
|
13709
|
-
## Contributing
|
|
13710
|
-
|
|
13711
|
-
1. Fork the repository
|
|
13712
|
-
2. Create your feature branch (\`git checkout -b feature/amazing-feature\`)
|
|
13713
|
-
3. Commit your changes (\`git commit -m 'Add some amazing feature'\`)
|
|
13714
|
-
4. Push to the branch (\`git push origin feature/amazing-feature\`)
|
|
13715
|
-
5. Open a Pull Request
|
|
13716
|
-
|
|
13717
|
-
## License
|
|
13718
|
-
|
|
13719
|
-
This project is licensed under the MIT License.
|
|
13720
|
-
`;
|
|
13721
|
-
return {
|
|
13722
|
-
path: "README.md",
|
|
13723
|
-
content
|
|
13724
|
-
};
|
|
13725
|
-
}
|
|
13726
|
-
function generateAllTemplates(config, dependencies, devDependencies) {
|
|
13537
|
+
function generateAllTemplates(config, isExistingProject) {
|
|
13727
13538
|
const templates = [
|
|
13728
|
-
// Core Igniter files
|
|
13539
|
+
// Core Igniter files - always generate
|
|
13729
13540
|
generateIgniterRouter(config),
|
|
13730
13541
|
generateIgniterContext(config),
|
|
13731
13542
|
generateMainRouter(config),
|
|
13732
13543
|
generateIgniterClient(config),
|
|
13733
|
-
// Feature files
|
|
13544
|
+
// Feature files - always generate
|
|
13734
13545
|
generateExampleController(config),
|
|
13735
13546
|
generateFeatureIndex(config),
|
|
13736
13547
|
generateExampleInterfaces(),
|
|
13737
|
-
//
|
|
13738
|
-
|
|
13739
|
-
generateTsConfig(config.framework),
|
|
13740
|
-
generateEnvFile(config),
|
|
13741
|
-
generateGitIgnore(),
|
|
13742
|
-
generateReadme(config)
|
|
13548
|
+
// .env.example is safe to generate as it won't overwrite a user's .env file
|
|
13549
|
+
generateEnvFile(config)
|
|
13743
13550
|
];
|
|
13744
13551
|
const serviceFiles = generateServiceFiles(config);
|
|
13745
13552
|
templates.push(...serviceFiles);
|
|
@@ -13754,10 +13561,11 @@ function generateAllTemplates(config, dependencies, devDependencies) {
|
|
|
13754
13561
|
init_logger();
|
|
13755
13562
|
var logger2 = createChildLogger({ component: "project-generator" });
|
|
13756
13563
|
var ProjectGenerator = class {
|
|
13757
|
-
constructor(config, targetDir) {
|
|
13564
|
+
constructor(config, targetDir, isExistingProject) {
|
|
13758
13565
|
this.spinner = ora();
|
|
13759
13566
|
this.config = config;
|
|
13760
13567
|
this.targetDir = path4.resolve(targetDir);
|
|
13568
|
+
this.isExistingProject = isExistingProject;
|
|
13761
13569
|
}
|
|
13762
13570
|
/**
|
|
13763
13571
|
* Generate the complete project
|
|
@@ -13766,14 +13574,19 @@ var ProjectGenerator = class {
|
|
|
13766
13574
|
try {
|
|
13767
13575
|
logger2.info("Starting project generation", {
|
|
13768
13576
|
project: this.config.projectName,
|
|
13769
|
-
targetDir: this.targetDir
|
|
13577
|
+
targetDir: this.targetDir,
|
|
13578
|
+
isExisting: this.isExistingProject
|
|
13770
13579
|
});
|
|
13771
|
-
|
|
13580
|
+
if (!this.isExistingProject) {
|
|
13581
|
+
await this.createProjectStructure();
|
|
13582
|
+
} else {
|
|
13583
|
+
this.spinner.succeed(chalk3.dim("\u2713 Existing project detected, skipping structure creation."));
|
|
13584
|
+
}
|
|
13772
13585
|
await this.generateFiles();
|
|
13773
13586
|
if (this.config.installDependencies) {
|
|
13774
13587
|
await this.installDependencies();
|
|
13775
13588
|
}
|
|
13776
|
-
if (this.config.initGit) {
|
|
13589
|
+
if (this.config.initGit && !this.isExistingProject) {
|
|
13777
13590
|
await this.initializeGit();
|
|
13778
13591
|
}
|
|
13779
13592
|
await this.runPostSetupTasks();
|
|
@@ -13784,12 +13597,75 @@ var ProjectGenerator = class {
|
|
|
13784
13597
|
throw error;
|
|
13785
13598
|
}
|
|
13786
13599
|
}
|
|
13600
|
+
async downloadTemplate() {
|
|
13601
|
+
const { framework } = this.config;
|
|
13602
|
+
const templateUrl = `https://github.com/felipebarcelospro/igniter-js.git`;
|
|
13603
|
+
const branch = "release/0.2.0-alpha.0";
|
|
13604
|
+
const tempDir = path4.join(this.targetDir, "__igniter_tmp__");
|
|
13605
|
+
const starterDir = path4.join(tempDir, "apps", `starter-${framework}`);
|
|
13606
|
+
const destDir = this.targetDir;
|
|
13607
|
+
let isValidStarter = false;
|
|
13608
|
+
this.spinner.start(`Baixando apenas o conte\xFAdo da pasta starter (${framework}) do branch ${branch}...`);
|
|
13609
|
+
try {
|
|
13610
|
+
await fs3.rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
13611
|
+
});
|
|
13612
|
+
await execa("git", [
|
|
13613
|
+
"clone",
|
|
13614
|
+
"--depth",
|
|
13615
|
+
"1",
|
|
13616
|
+
"--branch",
|
|
13617
|
+
branch,
|
|
13618
|
+
"--single-branch",
|
|
13619
|
+
templateUrl,
|
|
13620
|
+
tempDir
|
|
13621
|
+
]);
|
|
13622
|
+
const stat = await fs3.stat(starterDir).catch(() => null);
|
|
13623
|
+
if (!stat || !stat.isDirectory()) {
|
|
13624
|
+
throw new Error("Diret\xF3rio starter n\xE3o encontrado no template clonado.");
|
|
13625
|
+
}
|
|
13626
|
+
isValidStarter = true;
|
|
13627
|
+
const copyRecursive = async (src, dest) => {
|
|
13628
|
+
const entries = await fs3.readdir(src, { withFileTypes: true });
|
|
13629
|
+
for (const entry of entries) {
|
|
13630
|
+
const srcPath = path4.join(src, entry.name);
|
|
13631
|
+
const destPath = path4.join(dest, entry.name);
|
|
13632
|
+
if (entry.isDirectory()) {
|
|
13633
|
+
await fs3.mkdir(destPath, { recursive: true });
|
|
13634
|
+
await copyRecursive(srcPath, destPath);
|
|
13635
|
+
} else if (entry.isFile()) {
|
|
13636
|
+
await fs3.copyFile(srcPath, destPath);
|
|
13637
|
+
}
|
|
13638
|
+
}
|
|
13639
|
+
};
|
|
13640
|
+
await copyRecursive(starterDir, destDir);
|
|
13641
|
+
await fs3.rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
13642
|
+
});
|
|
13643
|
+
this.spinner.succeed(chalk3.dim("\u2713 Conte\xFAdo da pasta starter copiado com sucesso"));
|
|
13644
|
+
return { isStarter: isValidStarter, success: true };
|
|
13645
|
+
} catch (error) {
|
|
13646
|
+
console.error("Template download/copy failed", error);
|
|
13647
|
+
this.spinner.fail(chalk3.red("Falha ao baixar/copiar o template starter"));
|
|
13648
|
+
logger2.error("Template download/copy failed", { error });
|
|
13649
|
+
await fs3.rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
13650
|
+
});
|
|
13651
|
+
return { isStarter: isValidStarter, success: false };
|
|
13652
|
+
}
|
|
13653
|
+
}
|
|
13787
13654
|
/**
|
|
13788
13655
|
* Create project directory structure based on README.md structure
|
|
13789
13656
|
*/
|
|
13790
13657
|
async createProjectStructure() {
|
|
13791
13658
|
this.spinner.start("Creating project structure...");
|
|
13792
13659
|
try {
|
|
13660
|
+
const result = await this.downloadTemplate();
|
|
13661
|
+
if (result.isStarter) {
|
|
13662
|
+
if (result.success !== true) {
|
|
13663
|
+
throw new Error("Template download/copy failed");
|
|
13664
|
+
}
|
|
13665
|
+
if (result.success === true) {
|
|
13666
|
+
return;
|
|
13667
|
+
}
|
|
13668
|
+
}
|
|
13793
13669
|
await fs3.mkdir(this.targetDir, { recursive: true });
|
|
13794
13670
|
const dirs = [
|
|
13795
13671
|
"src",
|
|
@@ -13808,7 +13684,7 @@ var ProjectGenerator = class {
|
|
|
13808
13684
|
"src/features/example/presentation/utils"
|
|
13809
13685
|
);
|
|
13810
13686
|
}
|
|
13811
|
-
if (this.config.database.provider !== "none"
|
|
13687
|
+
if (this.config.database.provider !== "none") {
|
|
13812
13688
|
dirs.push("prisma");
|
|
13813
13689
|
}
|
|
13814
13690
|
for (const dir of dirs) {
|
|
@@ -13828,45 +13704,101 @@ var ProjectGenerator = class {
|
|
|
13828
13704
|
this.spinner.start("Generating project files...");
|
|
13829
13705
|
try {
|
|
13830
13706
|
const featureDeps = getFeatureDependencies(
|
|
13831
|
-
Object.entries(this.config.features).filter(([_, enabled]) => enabled).map(([key
|
|
13707
|
+
Object.entries(this.config.features).filter(([_, enabled]) => enabled).map(([key]) => key)
|
|
13832
13708
|
);
|
|
13833
13709
|
const dbConfig = DATABASE_CONFIGS[this.config.database.provider];
|
|
13834
|
-
const
|
|
13835
|
-
"@igniter-js/core
|
|
13836
|
-
"zod
|
|
13837
|
-
...featureDeps.dependencies.map((dep) => `${dep.name}@${dep.version}`),
|
|
13838
|
-
...dbConfig.dependencies.map((dep) => `${dep.name}@${dep.version}`)
|
|
13710
|
+
const coreDependencies = [
|
|
13711
|
+
{ name: "@igniter-js/core", version: "*" },
|
|
13712
|
+
{ name: "zod", version: "3.25.48" }
|
|
13839
13713
|
];
|
|
13840
|
-
const
|
|
13841
|
-
"typescript
|
|
13842
|
-
"@types/node
|
|
13843
|
-
"tsx
|
|
13844
|
-
...featureDeps.devDependencies.map((dep) => `${dep.name}@${dep.version}`),
|
|
13845
|
-
...(dbConfig.devDependencies || []).map((dep) => `${dep.name}@${dep.version}`)
|
|
13714
|
+
const coreDevDependencies = [
|
|
13715
|
+
{ name: "typescript", version: "^5.6.3" },
|
|
13716
|
+
{ name: "@types/node", version: "^22.9.0" },
|
|
13717
|
+
{ name: "tsx", version: "^4.7.0" }
|
|
13846
13718
|
];
|
|
13847
|
-
|
|
13719
|
+
if (this.isExistingProject) {
|
|
13720
|
+
const deps = [...coreDependencies, ...featureDeps.dependencies, ...dbConfig.dependencies];
|
|
13721
|
+
const devDeps = [...coreDevDependencies, ...featureDeps.devDependencies || [], ...dbConfig.devDependencies || []];
|
|
13722
|
+
await this.updatePackageJson(deps, devDeps);
|
|
13723
|
+
}
|
|
13724
|
+
const allTemplates = generateAllTemplates(
|
|
13725
|
+
this.config,
|
|
13726
|
+
this.isExistingProject,
|
|
13727
|
+
[...coreDependencies, ...featureDeps.dependencies, ...dbConfig.dependencies].map(
|
|
13728
|
+
(d) => `${d.name}@${d.version}`
|
|
13729
|
+
),
|
|
13730
|
+
[
|
|
13731
|
+
...coreDevDependencies,
|
|
13732
|
+
...featureDeps.devDependencies || [],
|
|
13733
|
+
...dbConfig.devDependencies || []
|
|
13734
|
+
].map((d) => `${d.name}@${d.version}`)
|
|
13735
|
+
);
|
|
13848
13736
|
let writtenCount = 0;
|
|
13849
|
-
for (const template of
|
|
13737
|
+
for (const template of allTemplates) {
|
|
13850
13738
|
const filePath = path4.join(this.targetDir, template.path);
|
|
13851
|
-
|
|
13852
|
-
|
|
13853
|
-
|
|
13854
|
-
|
|
13855
|
-
|
|
13739
|
+
if (this.isExistingProject) {
|
|
13740
|
+
if (template.path === "package.json") continue;
|
|
13741
|
+
const fileExists = await fs3.stat(filePath).catch(() => null);
|
|
13742
|
+
if (fileExists && [".gitignore", "README.md", "tsconfig.json"].includes(path4.basename(filePath))) {
|
|
13743
|
+
this.spinner.info(chalk3.dim(`Skipping existing file: ${template.path}`));
|
|
13744
|
+
continue;
|
|
13745
|
+
}
|
|
13856
13746
|
}
|
|
13857
13747
|
writtenCount++;
|
|
13858
|
-
this.spinner.text = `Generating files... (${writtenCount}/${
|
|
13748
|
+
this.spinner.text = `Generating files... (${writtenCount}/${allTemplates.length})`;
|
|
13859
13749
|
}
|
|
13860
|
-
if (this.config.database.provider !== "none"
|
|
13750
|
+
if (this.config.database.provider !== "none") {
|
|
13861
13751
|
await this.generatePrismaSchema();
|
|
13862
13752
|
}
|
|
13863
|
-
this.spinner.succeed(chalk3.green(`\u2713 Generated ${
|
|
13864
|
-
logger2.info("Project files generated successfully", { fileCount:
|
|
13753
|
+
this.spinner.succeed(chalk3.green(`\u2713 Generated ${writtenCount} files`));
|
|
13754
|
+
logger2.info("Project files generated successfully", { fileCount: writtenCount });
|
|
13865
13755
|
} catch (error) {
|
|
13866
13756
|
this.spinner.fail(chalk3.red("\u2717 Failed to generate files"));
|
|
13867
13757
|
throw error;
|
|
13868
13758
|
}
|
|
13869
13759
|
}
|
|
13760
|
+
/**
|
|
13761
|
+
* Updates an existing package.json file with new dependencies and scripts.
|
|
13762
|
+
*/
|
|
13763
|
+
async updatePackageJson(dependencies, devDependencies) {
|
|
13764
|
+
const pkgJsonPath = path4.join(this.targetDir, "package.json");
|
|
13765
|
+
try {
|
|
13766
|
+
const pkgJsonContent = await fs3.readFile(pkgJsonPath, "utf-8");
|
|
13767
|
+
const pkgJson = JSON.parse(pkgJsonContent);
|
|
13768
|
+
this.spinner.text = "Updating package.json...";
|
|
13769
|
+
pkgJson.dependencies = pkgJson.dependencies || {};
|
|
13770
|
+
for (const dep of dependencies) {
|
|
13771
|
+
if (!pkgJson.dependencies[dep.name]) {
|
|
13772
|
+
pkgJson.dependencies[dep.name] = dep.version;
|
|
13773
|
+
}
|
|
13774
|
+
}
|
|
13775
|
+
pkgJson.devDependencies = pkgJson.devDependencies || {};
|
|
13776
|
+
for (const dep of devDependencies) {
|
|
13777
|
+
if (!pkgJson.devDependencies[dep.name]) {
|
|
13778
|
+
pkgJson.devDependencies[dep.name] = dep.version;
|
|
13779
|
+
}
|
|
13780
|
+
}
|
|
13781
|
+
if (this.config.database.provider !== "none") {
|
|
13782
|
+
pkgJson.scripts = pkgJson.scripts || {};
|
|
13783
|
+
const newScripts = {
|
|
13784
|
+
"db:generate": "prisma generate",
|
|
13785
|
+
"db:push": "prisma db push",
|
|
13786
|
+
"db:studio": "prisma studio",
|
|
13787
|
+
"db:migrate": "prisma migrate dev"
|
|
13788
|
+
};
|
|
13789
|
+
for (const [name, command] of Object.entries(newScripts)) {
|
|
13790
|
+
if (!pkgJson.scripts[name]) {
|
|
13791
|
+
pkgJson.scripts[name] = command;
|
|
13792
|
+
}
|
|
13793
|
+
}
|
|
13794
|
+
}
|
|
13795
|
+
await fs3.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
13796
|
+
this.spinner.succeed(chalk3.green("\u2713 package.json updated"));
|
|
13797
|
+
} catch (error) {
|
|
13798
|
+
this.spinner.warn(chalk3.yellow("Could not update package.json. Please add dependencies manually."));
|
|
13799
|
+
logger2.warn("Failed to update package.json", { error });
|
|
13800
|
+
}
|
|
13801
|
+
}
|
|
13870
13802
|
/**
|
|
13871
13803
|
* Generate Prisma schema file
|
|
13872
13804
|
*/
|
|
@@ -13885,32 +13817,9 @@ datasource db {
|
|
|
13885
13817
|
provider = "${providerName}"
|
|
13886
13818
|
url = ${datasourceUrl}
|
|
13887
13819
|
}
|
|
13888
|
-
|
|
13889
|
-
// Example model - customize as needed
|
|
13890
|
-
model User {
|
|
13891
|
-
id String @id @default(cuid())
|
|
13892
|
-
email String @unique
|
|
13893
|
-
name String?
|
|
13894
|
-
createdAt DateTime @default(now())
|
|
13895
|
-
updatedAt DateTime @updatedAt
|
|
13896
|
-
|
|
13897
|
-
@@map("users")
|
|
13898
|
-
}
|
|
13899
|
-
|
|
13900
|
-
model Post {
|
|
13901
|
-
id String @id @default(cuid())
|
|
13902
|
-
title String
|
|
13903
|
-
content String?
|
|
13904
|
-
published Boolean @default(false)
|
|
13905
|
-
authorId String
|
|
13906
|
-
author User @relation(fields: [authorId], references: [id])
|
|
13907
|
-
createdAt DateTime @default(now())
|
|
13908
|
-
updatedAt DateTime @updatedAt
|
|
13909
|
-
|
|
13910
|
-
@@map("posts")
|
|
13911
|
-
}
|
|
13912
13820
|
`;
|
|
13913
13821
|
const schemaPath = path4.join(this.targetDir, "prisma", "schema.prisma");
|
|
13822
|
+
await fs3.mkdir(path4.dirname(schemaPath), { recursive: true });
|
|
13914
13823
|
await fs3.writeFile(schemaPath, schema, "utf8");
|
|
13915
13824
|
}
|
|
13916
13825
|
/**
|
|
@@ -13973,7 +13882,7 @@ model Post {
|
|
|
13973
13882
|
* Run post-setup tasks like Prisma generation
|
|
13974
13883
|
*/
|
|
13975
13884
|
async runPostSetupTasks() {
|
|
13976
|
-
if (this.config.database.provider !== "none"
|
|
13885
|
+
if (this.config.database.provider !== "none") {
|
|
13977
13886
|
this.spinner.start("Generating Prisma client...");
|
|
13978
13887
|
try {
|
|
13979
13888
|
const { command, args } = this.getRunCommand("db:generate");
|
|
@@ -14008,17 +13917,23 @@ model Post {
|
|
|
14008
13917
|
*/
|
|
14009
13918
|
showSuccessMessage() {
|
|
14010
13919
|
console.log();
|
|
14011
|
-
|
|
13920
|
+
if (this.isExistingProject) {
|
|
13921
|
+
console.log(chalk3.green("\u2713 Success! Igniter.js has been added to your project!"));
|
|
13922
|
+
} else {
|
|
13923
|
+
console.log(chalk3.green("\u2713 Success! Your Igniter.js project is ready!"));
|
|
13924
|
+
}
|
|
14012
13925
|
console.log();
|
|
14013
13926
|
console.log(chalk3.bold("Next steps:"));
|
|
14014
|
-
|
|
13927
|
+
if (!this.isExistingProject) {
|
|
13928
|
+
console.log(` ${chalk3.cyan("cd")} ${this.config.projectName}`);
|
|
13929
|
+
}
|
|
14015
13930
|
if (!this.config.installDependencies) {
|
|
14016
13931
|
console.log(` ${chalk3.cyan(this.config.packageManager)} install`);
|
|
14017
13932
|
}
|
|
14018
13933
|
if (this.config.dockerCompose) {
|
|
14019
13934
|
console.log(` ${chalk3.cyan("docker-compose")} up -d`);
|
|
14020
13935
|
}
|
|
14021
|
-
if (this.config.database.provider !== "none"
|
|
13936
|
+
if (this.config.database.provider !== "none") {
|
|
14022
13937
|
console.log(` ${chalk3.cyan(this.config.packageManager)} run db:push`);
|
|
14023
13938
|
}
|
|
14024
13939
|
console.log(` ${chalk3.cyan(this.config.packageManager)} run dev`);
|
|
@@ -14026,15 +13941,19 @@ model Post {
|
|
|
14026
13941
|
console.log(chalk3.bold("Helpful commands:"));
|
|
14027
13942
|
console.log(` ${chalk3.dim("Start development:")} ${chalk3.cyan(`${this.config.packageManager} run dev`)}`);
|
|
14028
13943
|
console.log(` ${chalk3.dim("Build for production:")} ${chalk3.cyan(`${this.config.packageManager} run build`)}`);
|
|
14029
|
-
if (this.config.database.provider !== "none"
|
|
13944
|
+
if (this.config.database.provider !== "none") {
|
|
14030
13945
|
console.log(` ${chalk3.dim("Database operations:")} ${chalk3.cyan(`${this.config.packageManager} run db:studio`)}`);
|
|
14031
13946
|
}
|
|
13947
|
+
if (this.isExistingProject) {
|
|
13948
|
+
console.log();
|
|
13949
|
+
console.log(chalk3.yellow("Remember to integrate the Igniter router into your existing server setup!"));
|
|
13950
|
+
}
|
|
14032
13951
|
console.log();
|
|
14033
13952
|
console.log(chalk3.dim("Happy coding!"));
|
|
14034
13953
|
}
|
|
14035
13954
|
};
|
|
14036
|
-
async function generateProject(config, targetDir) {
|
|
14037
|
-
const generator = new ProjectGenerator(config, targetDir);
|
|
13955
|
+
async function generateProject(config, targetDir, isExistingProject) {
|
|
13956
|
+
const generator = new ProjectGenerator(config, targetDir, isExistingProject);
|
|
14038
13957
|
await generator.generate();
|
|
14039
13958
|
}
|
|
14040
13959
|
|
|
@@ -14108,6 +14027,7 @@ function showInitHelp() {
|
|
|
14108
14027
|
console.log(" \u{1F504} BullMQ Jobs Background task processing");
|
|
14109
14028
|
console.log(" \u{1F916} MCP Server AI assistant integration");
|
|
14110
14029
|
console.log(" \u{1F4DD} Enhanced Logging Structured console output");
|
|
14030
|
+
console.log(" \u{1F4CA} Telemetry Tracking requests and errors");
|
|
14111
14031
|
console.log();
|
|
14112
14032
|
console.log(chalk4.bold("Supported Frameworks:"));
|
|
14113
14033
|
console.log(" \u2022 Next.js \u2022 Express");
|
|
@@ -14127,192 +14047,520 @@ function showInitHelp() {
|
|
|
14127
14047
|
|
|
14128
14048
|
// src/index.ts
|
|
14129
14049
|
init_spinner();
|
|
14050
|
+
|
|
14051
|
+
// src/adapters/scaffold.ts
|
|
14052
|
+
init_logger();
|
|
14053
|
+
import * as fs5 from "fs/promises";
|
|
14054
|
+
import * as path6 from "path";
|
|
14055
|
+
import chalk5 from "chalk";
|
|
14056
|
+
|
|
14057
|
+
// src/adapters/scaffold/providers/prisma.ts
|
|
14058
|
+
init_logger();
|
|
14059
|
+
import { getSchema } from "@mrleebo/prisma-ast";
|
|
14060
|
+
import * as fs4 from "fs/promises";
|
|
14061
|
+
import * as path5 from "path";
|
|
14062
|
+
var logger4 = createChildLogger({ component: "prisma-provider" });
|
|
14063
|
+
function mapPrismaTypeToTsType(prismaType) {
|
|
14064
|
+
const cleanType = prismaType.replace("?", "");
|
|
14065
|
+
switch (cleanType) {
|
|
14066
|
+
case "String":
|
|
14067
|
+
case "Json":
|
|
14068
|
+
case "Bytes":
|
|
14069
|
+
case "Unsupported":
|
|
14070
|
+
return "string";
|
|
14071
|
+
case "BigInt":
|
|
14072
|
+
return "bigint";
|
|
14073
|
+
case "Int":
|
|
14074
|
+
case "Float":
|
|
14075
|
+
case "Decimal":
|
|
14076
|
+
return "number";
|
|
14077
|
+
case "Boolean":
|
|
14078
|
+
return "boolean";
|
|
14079
|
+
case "DateTime":
|
|
14080
|
+
return "Date";
|
|
14081
|
+
default:
|
|
14082
|
+
return "string";
|
|
14083
|
+
}
|
|
14084
|
+
}
|
|
14085
|
+
function hasAttribute(prop, attributeName) {
|
|
14086
|
+
if (prop.type !== "field" || !Array.isArray(prop.attributes)) {
|
|
14087
|
+
return false;
|
|
14088
|
+
}
|
|
14089
|
+
return prop.attributes.some((attr) => attr.name === attributeName);
|
|
14090
|
+
}
|
|
14091
|
+
var PrismaProvider = class {
|
|
14092
|
+
constructor(customPath) {
|
|
14093
|
+
this.schemaPath = customPath || path5.join(process.cwd(), "prisma", "schema.prisma");
|
|
14094
|
+
logger4.debug(`Prisma schema path set to: ${this.schemaPath}`);
|
|
14095
|
+
}
|
|
14096
|
+
/**
|
|
14097
|
+
* Reads and parses the Prisma schema file to extract the details of a specific model.
|
|
14098
|
+
*
|
|
14099
|
+
* @param modelName - The name of the model to retrieve (e.g., 'User').
|
|
14100
|
+
* @returns A promise that resolves to the standardized `ModelSchema` or null if not found.
|
|
14101
|
+
*/
|
|
14102
|
+
async getModel(modelName) {
|
|
14103
|
+
try {
|
|
14104
|
+
const schemaContent = await fs4.readFile(this.schemaPath, "utf-8");
|
|
14105
|
+
const ast = getSchema(schemaContent);
|
|
14106
|
+
const model = ast.list.find(
|
|
14107
|
+
(node) => node.type === "model" && node.name === modelName
|
|
14108
|
+
);
|
|
14109
|
+
if (!model) {
|
|
14110
|
+
logger4.warn(`Model '${modelName}' not found in schema.`);
|
|
14111
|
+
return null;
|
|
14112
|
+
}
|
|
14113
|
+
const fields = model.properties.filter((prop) => prop.type === "field").map((prop) => {
|
|
14114
|
+
const isRelation = !/^[A-Z]/.test(prop.fieldType.toString()) && typeof prop.fieldType !== "string";
|
|
14115
|
+
return {
|
|
14116
|
+
name: prop.name,
|
|
14117
|
+
type: mapPrismaTypeToTsType(prop.fieldType),
|
|
14118
|
+
isId: hasAttribute(prop, "id"),
|
|
14119
|
+
isRequired: !(prop.optional || hasAttribute(prop, "default")),
|
|
14120
|
+
isUnique: hasAttribute(prop, "unique"),
|
|
14121
|
+
isRelation,
|
|
14122
|
+
hasDefault: hasAttribute(prop, "default"),
|
|
14123
|
+
isAutoGenerated: this.isFieldAutoGenerated(prop)
|
|
14124
|
+
};
|
|
14125
|
+
});
|
|
14126
|
+
return {
|
|
14127
|
+
name: model.name,
|
|
14128
|
+
fields
|
|
14129
|
+
};
|
|
14130
|
+
} catch (error) {
|
|
14131
|
+
if (error.code === "ENOENT") {
|
|
14132
|
+
logger4.error(`Prisma schema file not found at: ${this.schemaPath}`);
|
|
14133
|
+
} else {
|
|
14134
|
+
logger4.error("Failed to parse Prisma schema", { error });
|
|
14135
|
+
}
|
|
14136
|
+
throw new Error(`Could not process Prisma schema. Make sure '${this.schemaPath}' exists and is valid.`);
|
|
14137
|
+
}
|
|
14138
|
+
}
|
|
14139
|
+
/**
|
|
14140
|
+
* Determines if a field's value is automatically managed by the database.
|
|
14141
|
+
*
|
|
14142
|
+
* @param prop - The schema property to inspect.
|
|
14143
|
+
* @returns True if the field is auto-generated.
|
|
14144
|
+
*/
|
|
14145
|
+
isFieldAutoGenerated(prop) {
|
|
14146
|
+
if (hasAttribute(prop, "updatedAt")) {
|
|
14147
|
+
return true;
|
|
14148
|
+
}
|
|
14149
|
+
if (prop.type === "field" && Array.isArray(prop.attributes)) {
|
|
14150
|
+
const defaultAttr = prop.attributes.find((attr) => attr.name === "default");
|
|
14151
|
+
if (defaultAttr && Array.isArray(defaultAttr.args)) {
|
|
14152
|
+
const defaultValue = defaultAttr.args[0]?.value;
|
|
14153
|
+
if (typeof defaultValue === "string" && ["autoincrement()", "now()", "cuid()", "uuid()"].includes(defaultValue)) {
|
|
14154
|
+
return true;
|
|
14155
|
+
}
|
|
14156
|
+
}
|
|
14157
|
+
}
|
|
14158
|
+
return false;
|
|
14159
|
+
}
|
|
14160
|
+
};
|
|
14161
|
+
|
|
14162
|
+
// src/adapters/scaffold.ts
|
|
14163
|
+
var logger5 = createChildLogger({ component: "scaffold" });
|
|
14164
|
+
function toPascalCase(str) {
|
|
14165
|
+
return str.replace(/(^\w|-\w)/g, (g) => g.replace(/-/, "").toUpperCase());
|
|
14166
|
+
}
|
|
14167
|
+
function toCamelCase(str) {
|
|
14168
|
+
const pascal = toPascalCase(str);
|
|
14169
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
14170
|
+
}
|
|
14171
|
+
async function writeFile2(filePath, content) {
|
|
14172
|
+
const dir = path6.dirname(filePath);
|
|
14173
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
14174
|
+
await fs5.writeFile(filePath, content, "utf-8");
|
|
14175
|
+
}
|
|
14176
|
+
function getSchemaProvider(providerName) {
|
|
14177
|
+
if (providerName.toLowerCase() === "prisma") {
|
|
14178
|
+
return new PrismaProvider();
|
|
14179
|
+
}
|
|
14180
|
+
throw new Error(`Unsupported schema provider: ${providerName}`);
|
|
14181
|
+
}
|
|
14182
|
+
function generateCrudInterfacesTemplate(model, featureName) {
|
|
14183
|
+
const modelNamePascal = toPascalCase(model.name);
|
|
14184
|
+
const zodFields = model.fields.filter((field) => !field.isRelation).map((field) => {
|
|
14185
|
+
let zodType;
|
|
14186
|
+
switch (field.type) {
|
|
14187
|
+
case "string":
|
|
14188
|
+
case "bigint":
|
|
14189
|
+
zodType = "z.string()";
|
|
14190
|
+
break;
|
|
14191
|
+
case "number":
|
|
14192
|
+
zodType = "z.number()";
|
|
14193
|
+
break;
|
|
14194
|
+
case "boolean":
|
|
14195
|
+
zodType = "z.boolean()";
|
|
14196
|
+
break;
|
|
14197
|
+
case "Date":
|
|
14198
|
+
zodType = "z.date()";
|
|
14199
|
+
break;
|
|
14200
|
+
default:
|
|
14201
|
+
zodType = `z.any() // Type '${field.type}' not directly supported`;
|
|
14202
|
+
}
|
|
14203
|
+
if (!field.isRequired) {
|
|
14204
|
+
zodType += ".nullable()";
|
|
14205
|
+
}
|
|
14206
|
+
return ` ${field.name}: ${zodType},`;
|
|
14207
|
+
}).join("\n");
|
|
14208
|
+
const createInputOmissions = model.fields.filter((f) => f.isId || f.isAutoGenerated).map((f) => ` ${f.name}: true,`).join("\n");
|
|
14209
|
+
return `import { z } from 'zod';
|
|
14210
|
+
|
|
14211
|
+
// Generated from your '${model.name}' Prisma model
|
|
14212
|
+
export const ${modelNamePascal}Schema = z.object({
|
|
14213
|
+
${zodFields}
|
|
14214
|
+
});
|
|
14215
|
+
|
|
14216
|
+
// Schema for creating a new ${model.name}.
|
|
14217
|
+
// Fields managed by the database (id, createdAt, etc.) are omitted.
|
|
14218
|
+
export const Create${modelNamePascal}InputSchema = ${modelNamePascal}Schema.omit({
|
|
14219
|
+
${createInputOmissions}
|
|
14220
|
+
});
|
|
14221
|
+
|
|
14222
|
+
// Schema for updating a ${model.name}. All fields are optional.
|
|
14223
|
+
export const Update${modelNamePascal}InputSchema = Create${modelNamePascal}InputSchema.partial();
|
|
14224
|
+
|
|
14225
|
+
// Exporting types for convenience
|
|
14226
|
+
export type ${modelNamePascal} = z.infer<typeof ${modelNamePascal}Schema>;
|
|
14227
|
+
export type Create${modelNamePascal}Input = z.infer<typeof Create${modelNamePascal}InputSchema>;
|
|
14228
|
+
export type Update${modelNamePascal}Input = z.infer<typeof Update${modelNamePascal}InputSchema>;
|
|
14229
|
+
`;
|
|
14230
|
+
}
|
|
14231
|
+
function generateCrudProcedureTemplate(model, featureName) {
|
|
14232
|
+
const modelNameCamel = toCamelCase(model.name);
|
|
14233
|
+
const modelNamePascal = toPascalCase(model.name);
|
|
14234
|
+
const idField = model.fields.find((f) => f.isId);
|
|
14235
|
+
if (!idField) throw new Error(`Model ${model.name} has no ID field.`);
|
|
14236
|
+
return `import { igniter } from '@/igniter';
|
|
14237
|
+
import type { Create${modelNamePascal}Input, Update${modelNamePascal}Input } from '../${featureName}.interfaces';
|
|
14238
|
+
|
|
14239
|
+
export const ${modelNameCamel}Procedure = igniter.procedure({
|
|
14240
|
+
name: '${modelNameCamel}',
|
|
14241
|
+
handler: async (_, { context }) => {
|
|
14242
|
+
// This procedure acts as a repository, centralizing database access logic.
|
|
14243
|
+
return {
|
|
14244
|
+
${modelNameCamel}Repository: {
|
|
14245
|
+
findAll: () => context.database.${modelNameCamel}.findMany(),
|
|
14246
|
+
findById: (id: ${idField.type}) => context.database.${modelNameCamel}.findUnique({ where: { id } }),
|
|
14247
|
+
create: (data: Create${modelNamePascal}Input) => context.database.${modelNameCamel}.create({ data }),
|
|
14248
|
+
update: (id: ${idField.type}, data: Update${modelNamePascal}Input) => context.database.${modelNameCamel}.update({ where: { id }, data }),
|
|
14249
|
+
delete: (id: ${idField.type}) => context.database.${modelNameCamel}.delete({ where: { id } }),
|
|
14250
|
+
}
|
|
14251
|
+
};
|
|
14252
|
+
}
|
|
14253
|
+
});
|
|
14254
|
+
`;
|
|
14255
|
+
}
|
|
14256
|
+
function generateCrudControllerTemplate(model, featureName) {
|
|
14257
|
+
const modelNameCamel = toCamelCase(model.name);
|
|
14258
|
+
const modelNamePascal = toPascalCase(model.name);
|
|
14259
|
+
const idField = model.fields.find((f) => f.isId);
|
|
14260
|
+
if (!idField) throw new Error(`Model ${model.name} has no ID field.`);
|
|
14261
|
+
let idZodType = "z.string()";
|
|
14262
|
+
if (idField.type === "number") idZodType = "z.coerce.number()";
|
|
14263
|
+
return `import { igniter } from '@/igniter';
|
|
14264
|
+
import { z } from 'zod';
|
|
14265
|
+
import { ${modelNameCamel}Procedure } from '../procedures/${featureName}.procedure'
|
|
14266
|
+
import { Create${modelNamePascal}InputSchema, Update${modelNamePascal}InputSchema } from '../${featureName}.interfaces'
|
|
14267
|
+
|
|
14268
|
+
export const ${modelNameCamel}Controller = igniter.controller({
|
|
14269
|
+
name: '${modelNamePascal}',
|
|
14270
|
+
description: 'Endpoints for ${modelNamePascal}s',
|
|
14271
|
+
path: '/${modelNameCamel}s', // e.g., /users
|
|
14272
|
+
actions: {
|
|
14273
|
+
list: igniter.query({
|
|
14274
|
+
name: 'list',
|
|
14275
|
+
description: 'List all ${modelNamePascal}s',
|
|
14276
|
+
path: '/',
|
|
14277
|
+
use: [${modelNameCamel}Procedure()],
|
|
14278
|
+
handler: async ({ context, response }) => {
|
|
14279
|
+
const records = await context.${modelNameCamel}Repository.findAll()
|
|
14280
|
+
return response.success(records)
|
|
14281
|
+
},
|
|
14282
|
+
}),
|
|
14283
|
+
|
|
14284
|
+
getById: igniter.query({
|
|
14285
|
+
name: 'getById',
|
|
14286
|
+
description: 'Get a ${modelNamePascal} by ID',
|
|
14287
|
+
path: '/:id' as const,
|
|
14288
|
+
use: [${modelNameCamel}Procedure()],
|
|
14289
|
+
handler: async ({ request, context, response }) => {
|
|
14290
|
+
const record = await context.${modelNameCamel}Repository.findById(request.params.id)
|
|
14291
|
+
if (!record) {
|
|
14292
|
+
return response.notFound('${modelNamePascal} not found')
|
|
14293
|
+
}
|
|
14294
|
+
return response.success(record)
|
|
14295
|
+
},
|
|
14296
|
+
}),
|
|
14297
|
+
|
|
14298
|
+
create: igniter.mutation({
|
|
14299
|
+
name: 'create',
|
|
14300
|
+
description: 'Create a new ${modelNamePascal}',
|
|
14301
|
+
path: '/',
|
|
14302
|
+
method: 'POST',
|
|
14303
|
+
body: Create${modelNamePascal}InputSchema,
|
|
14304
|
+
use: [${modelNameCamel}Procedure()],
|
|
14305
|
+
handler: async ({ request, context, response }) => {
|
|
14306
|
+
const newRecord = await context.${modelNameCamel}Repository.create(request.body)
|
|
14307
|
+
return response.created(newRecord)
|
|
14308
|
+
},
|
|
14309
|
+
}),
|
|
14310
|
+
|
|
14311
|
+
update: igniter.mutation({
|
|
14312
|
+
name: 'update',
|
|
14313
|
+
description: 'Update a ${modelNamePascal} by ID',
|
|
14314
|
+
path: '/:id' as const,
|
|
14315
|
+
method: 'PUT',
|
|
14316
|
+
body: Update${modelNamePascal}InputSchema,
|
|
14317
|
+
use: [${modelNameCamel}Procedure()],
|
|
14318
|
+
handler: async ({ request, context, response }) => {
|
|
14319
|
+
const updatedRecord = await context.${modelNameCamel}Repository.update(request.params.id, request.body)
|
|
14320
|
+
return response.success(updatedRecord)
|
|
14321
|
+
},
|
|
14322
|
+
}),
|
|
14323
|
+
|
|
14324
|
+
delete: igniter.mutation({
|
|
14325
|
+
name: 'delete',
|
|
14326
|
+
description: 'Delete a ${modelNamePascal} by ID',
|
|
14327
|
+
path: '/:id' as const,
|
|
14328
|
+
method: 'DELETE',
|
|
14329
|
+
use: [${modelNameCamel}Procedure()],
|
|
14330
|
+
handler: async ({ request, context, response }) => {
|
|
14331
|
+
await context.${modelNameCamel}Repository.delete(request.params.id)
|
|
14332
|
+
return response.noContent()
|
|
14333
|
+
},
|
|
14334
|
+
}),
|
|
14335
|
+
},
|
|
14336
|
+
})
|
|
14337
|
+
`;
|
|
14338
|
+
}
|
|
14339
|
+
function generateCrudIndexTemplate(featureName) {
|
|
14340
|
+
const procedureFileName = `${featureName}.procedure`;
|
|
14341
|
+
const controllerFileName = `${featureName}.controller`;
|
|
14342
|
+
const interfacesFileName = `${featureName}.interfaces`;
|
|
14343
|
+
return `export * from './controllers/${controllerFileName}'
|
|
14344
|
+
export * from './procedures/${procedureFileName}'
|
|
14345
|
+
export * from './${interfacesFileName}'
|
|
14346
|
+
`;
|
|
14347
|
+
}
|
|
14348
|
+
function generateEmptyControllerTemplate(featureName) {
|
|
14349
|
+
const controllerName = `${featureName.toLowerCase()}Controller`;
|
|
14350
|
+
return `import { igniter } from '@/igniter'
|
|
14351
|
+
import { z } from 'zod'
|
|
14352
|
+
|
|
14353
|
+
export const ${controllerName} = igniter.controller({
|
|
14354
|
+
name: '${featureName}',
|
|
14355
|
+
path: '/${featureName}',
|
|
14356
|
+
actions: {
|
|
14357
|
+
hello: igniter.query({
|
|
14358
|
+
path: '/hello',
|
|
14359
|
+
handler: async ({ response }) => {
|
|
14360
|
+
return response.success({ message: 'Hello from ${featureName}!' })
|
|
14361
|
+
},
|
|
14362
|
+
}),
|
|
14363
|
+
},
|
|
14364
|
+
})
|
|
14365
|
+
`;
|
|
14366
|
+
}
|
|
14367
|
+
function generateEmptyInterfacesTemplate(featureName) {
|
|
14368
|
+
return `// Zod schemas and TypeScript types for the ${featureName} feature.
|
|
14369
|
+
`;
|
|
14370
|
+
}
|
|
14371
|
+
function generateEmptyIndexTemplate(featureName) {
|
|
14372
|
+
return `export * from './controllers/${featureName}.controller'
|
|
14373
|
+
`;
|
|
14374
|
+
}
|
|
14375
|
+
async function scaffoldEmptyFeature(featureName, featureDir) {
|
|
14376
|
+
const spinner = logger5.spinner(`Creating empty feature '${featureName}'...`);
|
|
14377
|
+
spinner.start();
|
|
14378
|
+
try {
|
|
14379
|
+
await fs5.mkdir(path6.join(featureDir, "controllers"), { recursive: true });
|
|
14380
|
+
await fs5.mkdir(path6.join(featureDir, "procedures"), { recursive: true });
|
|
14381
|
+
await writeFile2(
|
|
14382
|
+
path6.join(featureDir, "controllers", `${featureName}.controller.ts`),
|
|
14383
|
+
generateEmptyControllerTemplate(featureName)
|
|
14384
|
+
);
|
|
14385
|
+
await writeFile2(
|
|
14386
|
+
path6.join(featureDir, `${featureName}.interfaces.ts`),
|
|
14387
|
+
generateEmptyInterfacesTemplate(featureName)
|
|
14388
|
+
);
|
|
14389
|
+
await writeFile2(
|
|
14390
|
+
path6.join(featureDir, "index.ts"),
|
|
14391
|
+
generateEmptyIndexTemplate(featureName)
|
|
14392
|
+
);
|
|
14393
|
+
spinner.success(`Scaffolded empty feature '${featureName}'`);
|
|
14394
|
+
} catch (error) {
|
|
14395
|
+
spinner.error(`Failed to create empty feature '${featureName}'`);
|
|
14396
|
+
throw error;
|
|
14397
|
+
}
|
|
14398
|
+
}
|
|
14399
|
+
async function scaffoldFeatureFromSchema(featureName, schemaString, featureDir) {
|
|
14400
|
+
const spinner = logger5.spinner(`Scaffolding feature '${featureName}' from schema...`);
|
|
14401
|
+
spinner.start();
|
|
14402
|
+
try {
|
|
14403
|
+
const [providerName, modelName] = schemaString.split(":");
|
|
14404
|
+
if (!providerName || !modelName) {
|
|
14405
|
+
throw new Error("Invalid schema format. Expected `provider:ModelName` (e.g., `prisma:User`).");
|
|
14406
|
+
}
|
|
14407
|
+
const provider = getSchemaProvider(providerName);
|
|
14408
|
+
const model = await provider.getModel(modelName);
|
|
14409
|
+
if (!model) {
|
|
14410
|
+
throw new Error(`Model '${modelName}' not found using provider '${providerName}'.`);
|
|
14411
|
+
}
|
|
14412
|
+
spinner.update("Generating files from model schema...");
|
|
14413
|
+
await fs5.mkdir(path6.join(featureDir, "controllers"), { recursive: true });
|
|
14414
|
+
await fs5.mkdir(path6.join(featureDir, "procedures"), { recursive: true });
|
|
14415
|
+
await writeFile2(
|
|
14416
|
+
path6.join(featureDir, `${featureName}.interfaces.ts`),
|
|
14417
|
+
generateCrudInterfacesTemplate(model, featureName)
|
|
14418
|
+
);
|
|
14419
|
+
await writeFile2(
|
|
14420
|
+
path6.join(featureDir, "procedures", `${featureName}.procedure.ts`),
|
|
14421
|
+
generateCrudProcedureTemplate(model, featureName)
|
|
14422
|
+
);
|
|
14423
|
+
await writeFile2(
|
|
14424
|
+
path6.join(featureDir, "controllers", `${featureName}.controller.ts`),
|
|
14425
|
+
generateCrudControllerTemplate(model, featureName)
|
|
14426
|
+
);
|
|
14427
|
+
await writeFile2(
|
|
14428
|
+
path6.join(featureDir, "index.ts"),
|
|
14429
|
+
generateCrudIndexTemplate(featureName)
|
|
14430
|
+
);
|
|
14431
|
+
spinner.success(`Successfully scaffolded feature '${featureName}' from '${modelName}' model.`);
|
|
14432
|
+
console.log(chalk5.cyan(`
|
|
14433
|
+
\u2705 Next step: Register the '${toCamelCase(featureName)}Controller' in 'src/igniter.router.ts'`));
|
|
14434
|
+
} catch (error) {
|
|
14435
|
+
spinner.error(`Failed to scaffold feature from schema`);
|
|
14436
|
+
throw error;
|
|
14437
|
+
}
|
|
14438
|
+
}
|
|
14439
|
+
async function handleGenerateFeature(featureName, options) {
|
|
14440
|
+
const normalizedName = featureName.toLowerCase();
|
|
14441
|
+
const featureDir = path6.join(process.cwd(), "src", "features", normalizedName);
|
|
14442
|
+
logger5.info(`Scaffolding feature: ${chalk5.cyan(normalizedName)}`);
|
|
14443
|
+
try {
|
|
14444
|
+
await fs5.access(featureDir);
|
|
14445
|
+
logger5.error(`Feature '${normalizedName}' already exists.`);
|
|
14446
|
+
console.error(chalk5.red(`\u2717 Feature '${normalizedName}' already exists at ${path6.relative(process.cwd(), featureDir)}`));
|
|
14447
|
+
return;
|
|
14448
|
+
} catch (error) {
|
|
14449
|
+
}
|
|
14450
|
+
if (options.schema) {
|
|
14451
|
+
await scaffoldFeatureFromSchema(normalizedName, options.schema, featureDir);
|
|
14452
|
+
} else {
|
|
14453
|
+
await scaffoldEmptyFeature(normalizedName, featureDir);
|
|
14454
|
+
}
|
|
14455
|
+
}
|
|
14456
|
+
async function handleGenerateController(name, feature) {
|
|
14457
|
+
logger5.warn(`'generate controller' is not yet fully implemented. Use 'generate feature --schema' instead.`);
|
|
14458
|
+
}
|
|
14459
|
+
async function handleGenerateProcedure(name, feature) {
|
|
14460
|
+
logger5.warn(`'generate procedure' is not yet fully implemented. Use 'generate feature --schema' instead.`);
|
|
14461
|
+
}
|
|
14462
|
+
|
|
14463
|
+
// src/index.ts
|
|
14130
14464
|
var program = new Command();
|
|
14131
14465
|
program.name("igniter").description("CLI for Igniter.js type-safe client generation").version("1.0.0");
|
|
14132
14466
|
program.command("init").description("Create a new Igniter.js project with interactive setup").argument("[project-name]", "Name of the project directory").option("--force", "Skip confirmation prompts and overwrite existing files").option("--pm, --package-manager <manager>", "Package manager to use (npm, yarn, pnpm, bun)").option("--template <template>", "Use a specific template (coming soon)").option("-f, --framework <framework>", "Target framework (nextjs, vite, etc.)").option("--no-git", "Skip git repository initialization").option("--no-install", "Skip automatic dependency installation").action(async (projectName, options) => {
|
|
14133
14467
|
const initLogger = createChildLogger({ component: "init-command" });
|
|
14134
14468
|
try {
|
|
14135
|
-
if (options.help) {
|
|
14136
|
-
showInitHelp();
|
|
14137
|
-
return;
|
|
14138
|
-
}
|
|
14139
14469
|
if (!projectName) {
|
|
14140
|
-
|
|
14141
|
-
console.log();
|
|
14142
|
-
console.log("To create a new project:");
|
|
14143
|
-
console.log(" igniter init my-awesome-api");
|
|
14144
|
-
console.log();
|
|
14145
|
-
console.log("To initialize in current directory:");
|
|
14146
|
-
console.log(" igniter init .");
|
|
14147
|
-
console.log();
|
|
14148
|
-
console.log("For more options:");
|
|
14149
|
-
console.log(" igniter init --help");
|
|
14470
|
+
showInitHelp();
|
|
14150
14471
|
return;
|
|
14151
14472
|
}
|
|
14152
14473
|
if (projectName !== ".") {
|
|
14153
14474
|
const validation2 = validateProjectName(projectName);
|
|
14154
14475
|
if (!validation2.valid) {
|
|
14155
|
-
initLogger.error("Invalid project name", {
|
|
14156
|
-
projectName,
|
|
14157
|
-
reason: validation2.message
|
|
14158
|
-
});
|
|
14476
|
+
initLogger.error("Invalid project name", { projectName, reason: validation2.message });
|
|
14159
14477
|
console.error(`\u2717 ${validation2.message}`);
|
|
14160
14478
|
process.exit(1);
|
|
14161
14479
|
}
|
|
14162
14480
|
}
|
|
14163
|
-
|
|
14164
|
-
|
|
14165
|
-
options
|
|
14166
|
-
});
|
|
14167
|
-
const targetDir = projectName ? path7.resolve(projectName) : process.cwd();
|
|
14481
|
+
const targetDir = projectName === "." ? process.cwd() : path9.resolve(projectName);
|
|
14482
|
+
const isExistingProject = await fs8.promises.stat(path9.join(targetDir, "package.json")).catch(() => null) !== null;
|
|
14168
14483
|
if (!options.force) {
|
|
14169
14484
|
try {
|
|
14170
|
-
const stats = await
|
|
14485
|
+
const stats = await fs8.promises.stat(targetDir);
|
|
14171
14486
|
if (stats.isDirectory()) {
|
|
14172
|
-
const files = await
|
|
14173
|
-
const nonEmptyFiles = files.filter(
|
|
14174
|
-
|
|
14175
|
-
|
|
14176
|
-
if (nonEmptyFiles.length > 0) {
|
|
14177
|
-
const shouldOverwrite = await confirmOverwrite(targetDir);
|
|
14487
|
+
const files = await fs8.promises.readdir(targetDir);
|
|
14488
|
+
const nonEmptyFiles = files.filter((file) => !file.startsWith("."));
|
|
14489
|
+
if (nonEmptyFiles.length > 0 && !isExistingProject) {
|
|
14490
|
+
const shouldOverwrite = await confirmOverwrite(`Directory '${projectName}' is not empty. Continue?`);
|
|
14178
14491
|
if (!shouldOverwrite) {
|
|
14179
|
-
console.log("Setup cancelled");
|
|
14492
|
+
console.log("Setup cancelled.");
|
|
14180
14493
|
process.exit(0);
|
|
14181
14494
|
}
|
|
14182
14495
|
}
|
|
14183
14496
|
}
|
|
14184
14497
|
} catch (error) {
|
|
14498
|
+
if (error.code !== "ENOENT") {
|
|
14499
|
+
throw error;
|
|
14500
|
+
}
|
|
14185
14501
|
}
|
|
14186
14502
|
}
|
|
14187
|
-
const config = await runSetupPrompts(
|
|
14503
|
+
const config = await runSetupPrompts(targetDir, isExistingProject);
|
|
14188
14504
|
const validation = validateConfig(config);
|
|
14189
14505
|
if (!validation.isValid) {
|
|
14190
14506
|
console.error(`\u2717 ${validation.message}`);
|
|
14191
14507
|
process.exit(1);
|
|
14192
14508
|
}
|
|
14193
|
-
await generateProject(config, targetDir);
|
|
14194
|
-
initLogger.info("Project generated successfully", {
|
|
14195
|
-
project: config.projectName,
|
|
14196
|
-
targetDir
|
|
14197
|
-
});
|
|
14509
|
+
await generateProject(config, targetDir, isExistingProject);
|
|
14198
14510
|
} catch (error) {
|
|
14199
14511
|
initLogger.error("Init command failed", { error });
|
|
14200
14512
|
console.error("\u2717 Failed to initialize project:", error instanceof Error ? error.message : String(error));
|
|
14201
14513
|
process.exit(1);
|
|
14202
14514
|
}
|
|
14203
14515
|
});
|
|
14204
|
-
program.command("dev").description("Start development mode with framework and Igniter (interactive dashboard by default)").option(
|
|
14205
|
-
"--framework <type>",
|
|
14206
|
-
`Framework type (${getFrameworkList()}, generic)`
|
|
14207
|
-
).option("--output <dir>", "Output directory", "src").option("--debug", "Enable debug mode").option("--port <number>", "Port for the dev server", "3000").option("--cmd <command>", "Custom command to start dev server").option("--no-framework", "Disable framework dev server (Igniter only)").option("--no-interactive", "Disable interactive mode (use regular concurrent mode)").action(async (options) => {
|
|
14516
|
+
program.command("dev").description("Start development mode with framework and Igniter (interactive dashboard by default)").option("--framework <type>", `Framework type (${getFrameworkList()}, generic)`).option("--output <dir>", "Output directory for generated client files", "src/").option("--debug", "Enable debug mode").option("--port <number>", "Port for the dev server", "3000").option("--cmd <command>", "Custom command to start dev server").option("--no-framework", "Disable framework dev server (Igniter only)").option("--no-interactive", "Disable interactive mode (use regular concurrent mode)").action(async (options) => {
|
|
14208
14517
|
const detectedFramework = detectFramework();
|
|
14209
14518
|
const framework = options.framework ? isFrameworkSupported(options.framework) ? options.framework : "generic" : detectedFramework;
|
|
14210
|
-
const cmdLogger = createChildLogger({ command: "dev" });
|
|
14211
|
-
logger.group("Igniter.js");
|
|
14212
14519
|
const useInteractive = options.interactive !== false;
|
|
14213
|
-
|
|
14214
|
-
|
|
14215
|
-
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
14226
|
-
|
|
14227
|
-
|
|
14228
|
-
|
|
14229
|
-
|
|
14230
|
-
|
|
14231
|
-
|
|
14232
|
-
|
|
14233
|
-
|
|
14234
|
-
|
|
14235
|
-
|
|
14236
|
-
name: framework === "nextjs" ? "Next.js" : framework === "sveltekit" ? "SvelteKit" : framework === "tanstack-start" ? "TanStack Start" : framework.charAt(0).toUpperCase() + framework.slice(1),
|
|
14237
|
-
command: frameworkCommand,
|
|
14238
|
-
color: framework === "nextjs" ? "green" : framework === "vite" ? "yellow" : framework === "nuxt" ? "cyan" : framework === "sveltekit" ? "magenta" : framework === "remix" ? "red" : framework === "astro" ? "white" : framework === "express" ? "blue" : framework === "tanstack-start" ? "purple" : "gray",
|
|
14239
|
-
cwd: process.cwd(),
|
|
14240
|
-
env: {
|
|
14241
|
-
PORT: options.port.toString(),
|
|
14242
|
-
NODE_ENV: "development"
|
|
14243
|
-
}
|
|
14244
|
-
});
|
|
14245
|
-
}
|
|
14520
|
+
logger.info(`Starting ${useInteractive ? "interactive" : "concurrent"} development mode`, { framework });
|
|
14521
|
+
const { runInteractiveProcesses: runInteractiveProcesses2, runConcurrentProcesses: runConcurrentProcesses2 } = await Promise.resolve().then(() => (init_concurrent_processes(), concurrent_processes_exports));
|
|
14522
|
+
const processes = [];
|
|
14523
|
+
if (!options.noFramework && framework !== "generic") {
|
|
14524
|
+
const frameworkCommands = {
|
|
14525
|
+
nextjs: "npm run dev",
|
|
14526
|
+
vite: "npm run dev",
|
|
14527
|
+
nuxt: "npm run dev",
|
|
14528
|
+
sveltekit: "npm run dev",
|
|
14529
|
+
remix: "npm run dev",
|
|
14530
|
+
astro: "npm run dev",
|
|
14531
|
+
express: "npm run dev",
|
|
14532
|
+
"tanstack-start": "npm run dev"
|
|
14533
|
+
};
|
|
14534
|
+
const frameworkCommand = options.cmd || frameworkCommands[framework];
|
|
14535
|
+
if (frameworkCommand) {
|
|
14536
|
+
processes.push({
|
|
14537
|
+
name: framework.charAt(0).toUpperCase() + framework.slice(1),
|
|
14538
|
+
command: frameworkCommand,
|
|
14539
|
+
color: "green",
|
|
14540
|
+
cwd: process.cwd(),
|
|
14541
|
+
env: { PORT: options.port.toString(), NODE_ENV: "development" }
|
|
14542
|
+
});
|
|
14246
14543
|
}
|
|
14247
|
-
|
|
14248
|
-
|
|
14249
|
-
|
|
14250
|
-
|
|
14251
|
-
|
|
14252
|
-
|
|
14544
|
+
}
|
|
14545
|
+
processes.push({
|
|
14546
|
+
name: "Igniter",
|
|
14547
|
+
command: `igniter generate schema --watch --framework ${framework} --output ${options.output}${options.debug ? " --debug" : ""}`,
|
|
14548
|
+
color: "blue",
|
|
14549
|
+
cwd: process.cwd()
|
|
14550
|
+
});
|
|
14551
|
+
if (useInteractive) {
|
|
14253
14552
|
await runInteractiveProcesses2(processes);
|
|
14254
14553
|
} else {
|
|
14255
|
-
|
|
14256
|
-
framework,
|
|
14257
|
-
output: options.output,
|
|
14258
|
-
port: options.port,
|
|
14259
|
-
withFramework: !options.noFramework
|
|
14260
|
-
});
|
|
14261
|
-
const { runConcurrentProcesses: runConcurrentProcesses2 } = await Promise.resolve().then(() => (init_concurrent_processes(), concurrent_processes_exports));
|
|
14262
|
-
const processes = [];
|
|
14263
|
-
if (!options.noFramework && framework !== "generic") {
|
|
14264
|
-
const frameworkCommands = {
|
|
14265
|
-
nextjs: "npm run dev",
|
|
14266
|
-
vite: "npm run dev",
|
|
14267
|
-
nuxt: "npm run dev",
|
|
14268
|
-
sveltekit: "npm run dev",
|
|
14269
|
-
remix: "npm run dev",
|
|
14270
|
-
astro: "npm run dev",
|
|
14271
|
-
express: "npm run dev",
|
|
14272
|
-
"tanstack-start": "npm run dev"
|
|
14273
|
-
};
|
|
14274
|
-
const frameworkCommand = options.cmd || frameworkCommands[framework];
|
|
14275
|
-
if (frameworkCommand) {
|
|
14276
|
-
processes.push({
|
|
14277
|
-
name: framework === "nextjs" ? "Next.js" : framework === "sveltekit" ? "SvelteKit" : framework === "tanstack-start" ? "TanStack Start" : framework.charAt(0).toUpperCase() + framework.slice(1),
|
|
14278
|
-
command: frameworkCommand,
|
|
14279
|
-
color: framework === "nextjs" ? "green" : framework === "vite" ? "yellow" : framework === "nuxt" ? "cyan" : framework === "sveltekit" ? "magenta" : framework === "remix" ? "red" : framework === "astro" ? "white" : framework === "express" ? "blue" : framework === "tanstack-start" ? "purple" : "gray",
|
|
14280
|
-
cwd: process.cwd(),
|
|
14281
|
-
env: {
|
|
14282
|
-
PORT: options.port.toString(),
|
|
14283
|
-
NODE_ENV: "development"
|
|
14284
|
-
}
|
|
14285
|
-
});
|
|
14286
|
-
}
|
|
14287
|
-
}
|
|
14288
|
-
processes.push({
|
|
14289
|
-
name: "Igniter.js",
|
|
14290
|
-
command: `igniter generate --framework ${framework} --output ${options.output}${options.debug ? " --debug" : ""}`,
|
|
14291
|
-
color: "blue",
|
|
14292
|
-
cwd: process.cwd()
|
|
14293
|
-
});
|
|
14294
|
-
await runConcurrentProcesses2({
|
|
14295
|
-
processes,
|
|
14296
|
-
killOthers: true,
|
|
14297
|
-
prefixFormat: "name",
|
|
14298
|
-
prefixColors: true,
|
|
14299
|
-
prefixLength: 10
|
|
14300
|
-
});
|
|
14554
|
+
await runConcurrentProcesses2({ processes, killOthers: true });
|
|
14301
14555
|
}
|
|
14302
14556
|
});
|
|
14303
|
-
program.command("generate").description("
|
|
14304
|
-
|
|
14305
|
-
`Framework type (${getFrameworkList()}, generic)`
|
|
14306
|
-
).option("--output <dir>", "Output directory", "lib").option("--debug", "Enable debug mode").action(async (options) => {
|
|
14557
|
+
var generate = program.command("generate").description("Scaffold new features or generate client schema");
|
|
14558
|
+
generate.command("schema").description("Generate client schema from your Igniter router (for CI/CD or manual builds)").option("--framework <type>", `Framework type (${getFrameworkList()}, generic)`).option("--output <dir>", "Output directory", "src/").option("--debug", "Enable debug mode").option("--watch", "Watch for changes and regenerate automatically").action(async (options) => {
|
|
14307
14559
|
const startTime = performance.now();
|
|
14308
14560
|
const detectedFramework = detectFramework();
|
|
14309
14561
|
const framework = options.framework ? isFrameworkSupported(options.framework) ? options.framework : "generic" : detectedFramework;
|
|
14310
|
-
const cmdLogger = createChildLogger({ command: "generate" });
|
|
14311
14562
|
logger.group("Igniter.js CLI");
|
|
14312
|
-
logger.info(
|
|
14313
|
-
framework,
|
|
14314
|
-
output: options.output
|
|
14315
|
-
});
|
|
14563
|
+
logger.info(`Starting client schema ${options.watch ? "watching" : "generation"}`, { framework, output: options.output });
|
|
14316
14564
|
const watcherSpinner = createDetachedSpinner("Loading generator...");
|
|
14317
14565
|
watcherSpinner.start();
|
|
14318
14566
|
const { IgniterWatcher: IgniterWatcher2 } = await Promise.resolve().then(() => (init_watcher(), watcher_exports));
|
|
@@ -14320,99 +14568,27 @@ program.command("generate").description("Generate client once (useful for CI/CD)
|
|
|
14320
14568
|
framework,
|
|
14321
14569
|
outputDir: options.output,
|
|
14322
14570
|
debug: options.debug,
|
|
14323
|
-
controllerPatterns: ["**/*.controller.{ts,js}"]
|
|
14324
|
-
extractTypes: true,
|
|
14325
|
-
optimizeClientBundle: true,
|
|
14326
|
-
hotReload: false
|
|
14571
|
+
controllerPatterns: ["**/*.controller.{ts,js}"]
|
|
14327
14572
|
});
|
|
14328
14573
|
watcherSpinner.success("Generator loaded");
|
|
14329
|
-
|
|
14330
|
-
|
|
14331
|
-
|
|
14332
|
-
|
|
14333
|
-
|
|
14334
|
-
|
|
14335
|
-
output: options.output,
|
|
14336
|
-
framework,
|
|
14337
|
-
duration: `${duration}s`,
|
|
14338
|
-
timestamp
|
|
14339
|
-
});
|
|
14340
|
-
logger.separator();
|
|
14341
|
-
logger.info("Useful links");
|
|
14342
|
-
logger.info(
|
|
14343
|
-
"Documentation: https://felipebarcelospro.github.io/igniter-js"
|
|
14344
|
-
);
|
|
14345
|
-
logger.info(
|
|
14346
|
-
"Issues: https://github.com/felipebarcelospro/igniter-js/issues"
|
|
14347
|
-
);
|
|
14348
|
-
logger.info(
|
|
14349
|
-
"Contributing: https://github.com/felipebarcelospro/igniter-js/blob/main/CONTRIBUTING.md"
|
|
14350
|
-
);
|
|
14351
|
-
logger.groupEnd();
|
|
14352
|
-
logger.separator();
|
|
14353
|
-
process.exit(0);
|
|
14354
|
-
});
|
|
14355
|
-
program.command("server").description("Start framework dev server only (no Igniter file watching)").option(
|
|
14356
|
-
"--framework <type>",
|
|
14357
|
-
`Framework type (${getFrameworkList()}, generic)`
|
|
14358
|
-
).option("--port <number>", "Port for the dev server", "3000").option("--cmd <command>", "Custom command to start dev server").option("--debug", "Enable debug mode").action(async (options) => {
|
|
14359
|
-
const detectedFramework = detectFramework();
|
|
14360
|
-
const framework = options.framework ? isFrameworkSupported(options.framework) ? options.framework : "generic" : detectedFramework;
|
|
14361
|
-
const cmdLogger = createChildLogger({ command: "server" });
|
|
14362
|
-
logger.group("Igniter.js CLI");
|
|
14363
|
-
logger.info("Server mode", {
|
|
14364
|
-
framework,
|
|
14365
|
-
port: options.port
|
|
14366
|
-
});
|
|
14367
|
-
try {
|
|
14368
|
-
const devServerProcess = await startDevServer({
|
|
14369
|
-
framework,
|
|
14370
|
-
command: options.cmd,
|
|
14371
|
-
port: parseInt(options.port),
|
|
14372
|
-
debug: options.debug
|
|
14373
|
-
});
|
|
14374
|
-
logger.success("Dev server started");
|
|
14375
|
-
logger.info("Press Ctrl+C to stop");
|
|
14376
|
-
process.on("SIGINT", () => {
|
|
14377
|
-
console.log("\n");
|
|
14378
|
-
const shutdownSpinner = createDetachedSpinner("Stopping dev server...");
|
|
14379
|
-
shutdownSpinner.start();
|
|
14380
|
-
devServerProcess?.kill("SIGTERM");
|
|
14381
|
-
setTimeout(() => {
|
|
14382
|
-
if (devServerProcess && !devServerProcess.killed) {
|
|
14383
|
-
devServerProcess.kill("SIGKILL");
|
|
14384
|
-
}
|
|
14385
|
-
shutdownSpinner.success("Dev server stopped");
|
|
14386
|
-
logger.groupEnd();
|
|
14387
|
-
process.exit(0);
|
|
14388
|
-
}, 3e3);
|
|
14389
|
-
});
|
|
14390
|
-
} catch (error) {
|
|
14391
|
-
if (error instanceof Error) {
|
|
14392
|
-
logger.error("Failed to start dev server", { error: error.message });
|
|
14393
|
-
} else {
|
|
14394
|
-
logger.error("Failed to start dev server", { error });
|
|
14395
|
-
}
|
|
14574
|
+
if (options.watch) {
|
|
14575
|
+
await watcher.start();
|
|
14576
|
+
} else {
|
|
14577
|
+
await watcher.generate();
|
|
14578
|
+
const duration = ((performance.now() - startTime) / 1e3).toFixed(2);
|
|
14579
|
+
logger.success(`Generation complete in ${duration}s`);
|
|
14396
14580
|
logger.groupEnd();
|
|
14397
|
-
process.exit(
|
|
14581
|
+
process.exit(0);
|
|
14398
14582
|
}
|
|
14399
14583
|
});
|
|
14400
|
-
|
|
14401
|
-
|
|
14402
|
-
|
|
14403
|
-
|
|
14404
|
-
|
|
14405
|
-
|
|
14406
|
-
|
|
14407
|
-
|
|
14408
|
-
console.log(" igniter dev --framework nextjs Start with specific framework");
|
|
14409
|
-
console.log(" igniter generate --watch Generate and watch for changes");
|
|
14410
|
-
console.log();
|
|
14411
|
-
console.log("For more help with a specific command:");
|
|
14412
|
-
console.log(" igniter init --help");
|
|
14413
|
-
console.log(" igniter dev --help");
|
|
14414
|
-
console.log(" igniter generate --help");
|
|
14415
|
-
console.log();
|
|
14584
|
+
generate.command("feature").description("Scaffold a new feature module").argument("<name>", "The name of the feature (e.g., 'user', 'products')").option("--schema <value>", "Generate from a schema provider (e.g., 'prisma:User')").action(async (name, options) => {
|
|
14585
|
+
await handleGenerateFeature(name, options);
|
|
14586
|
+
});
|
|
14587
|
+
generate.command("controller").description("Scaffold a new controller within a feature").argument("<name>", "The name of the controller (e.g., 'profile')").option("-f, --feature <feature>", "The parent feature name", "").action(async (name, options) => {
|
|
14588
|
+
await handleGenerateController(name, options.feature);
|
|
14589
|
+
});
|
|
14590
|
+
generate.command("procedure").description("Scaffold a new procedure within a feature").argument("<name>", "The name of the procedure (e.g., 'auth', 'logging')").option("-f, --feature <feature>", "The parent feature name", "").action(async (name, options) => {
|
|
14591
|
+
await handleGenerateProcedure(name, options.feature);
|
|
14416
14592
|
});
|
|
14417
14593
|
program.parse();
|
|
14418
14594
|
function validateConfig(config) {
|