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