@igniter-js/cli 0.2.62 → 0.2.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +184 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +184 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
package/dist/index.mjs
CHANGED
|
@@ -19553,12 +19553,18 @@ var PrismaProvider = class {
|
|
|
19553
19553
|
return null;
|
|
19554
19554
|
}
|
|
19555
19555
|
const fields = model.properties.filter((prop) => prop.type === "field").map((prop) => {
|
|
19556
|
-
|
|
19556
|
+
if (prop.type !== "field" || !("fieldType" in prop) || !("name" in prop)) {
|
|
19557
|
+
throw new Error(`Invalid field property structure for ${prop}`);
|
|
19558
|
+
}
|
|
19559
|
+
const fieldType = prop.fieldType;
|
|
19560
|
+
const fieldTypeStr = typeof fieldType === "string" ? fieldType : fieldType?.toString() || "string";
|
|
19561
|
+
const isOptional = "optional" in prop ? prop.optional : false;
|
|
19562
|
+
const isRelation = !/^[A-Z]/.test(fieldTypeStr) && typeof fieldType !== "string";
|
|
19557
19563
|
return {
|
|
19558
19564
|
name: prop.name,
|
|
19559
|
-
type: mapPrismaTypeToTsType(
|
|
19565
|
+
type: mapPrismaTypeToTsType(fieldTypeStr),
|
|
19560
19566
|
isId: hasAttribute(prop, "id"),
|
|
19561
|
-
isRequired: !(
|
|
19567
|
+
isRequired: !(isOptional || hasAttribute(prop, "default")),
|
|
19562
19568
|
isUnique: hasAttribute(prop, "unique"),
|
|
19563
19569
|
isRelation,
|
|
19564
19570
|
hasDefault: hasAttribute(prop, "default"),
|
|
@@ -19578,6 +19584,28 @@ var PrismaProvider = class {
|
|
|
19578
19584
|
throw new Error(`Could not process Prisma schema. Make sure '${this.schemaPath}' exists and is valid.`);
|
|
19579
19585
|
}
|
|
19580
19586
|
}
|
|
19587
|
+
/**
|
|
19588
|
+
* Lists all available model names in the Prisma schema.
|
|
19589
|
+
*
|
|
19590
|
+
* @returns A promise that resolves to an array of model names.
|
|
19591
|
+
*/
|
|
19592
|
+
async listModels() {
|
|
19593
|
+
try {
|
|
19594
|
+
const schemaContent = await fs4.readFile(this.schemaPath, "utf-8");
|
|
19595
|
+
const ast = getSchema(schemaContent);
|
|
19596
|
+
const models = ast.list.filter((node) => node.type === "model").map((model) => model.name);
|
|
19597
|
+
logger4.debug(`Found ${models.length} models in schema: ${models.join(", ")}`);
|
|
19598
|
+
return models;
|
|
19599
|
+
} catch (error) {
|
|
19600
|
+
if (error.code === "ENOENT") {
|
|
19601
|
+
logger4.error(`Prisma schema file not found at: ${this.schemaPath}`);
|
|
19602
|
+
return [];
|
|
19603
|
+
} else {
|
|
19604
|
+
logger4.error("Failed to parse Prisma schema for model listing", { error });
|
|
19605
|
+
throw new Error(`Could not process Prisma schema. Make sure '${this.schemaPath}' exists and is valid.`);
|
|
19606
|
+
}
|
|
19607
|
+
}
|
|
19608
|
+
}
|
|
19581
19609
|
/**
|
|
19582
19610
|
* Determines if a field's value is automatically managed by the database.
|
|
19583
19611
|
*
|
|
@@ -19603,6 +19631,29 @@ var PrismaProvider = class {
|
|
|
19603
19631
|
|
|
19604
19632
|
// src/adapters/scaffold.ts
|
|
19605
19633
|
var logger5 = createChildLogger({ component: "scaffold" });
|
|
19634
|
+
async function hasPrismaSchema() {
|
|
19635
|
+
const possiblePaths = [
|
|
19636
|
+
path6.join(process.cwd(), "prisma", "schema.prisma"),
|
|
19637
|
+
path6.join(process.cwd(), "schema.prisma")
|
|
19638
|
+
];
|
|
19639
|
+
for (const schemaPath of possiblePaths) {
|
|
19640
|
+
try {
|
|
19641
|
+
await fs5.access(schemaPath);
|
|
19642
|
+
return true;
|
|
19643
|
+
} catch {
|
|
19644
|
+
}
|
|
19645
|
+
}
|
|
19646
|
+
return false;
|
|
19647
|
+
}
|
|
19648
|
+
async function getPrismaModels() {
|
|
19649
|
+
try {
|
|
19650
|
+
const provider = new PrismaProvider();
|
|
19651
|
+
return await provider.listModels();
|
|
19652
|
+
} catch (error) {
|
|
19653
|
+
logger5.debug("Failed to get Prisma models", { error });
|
|
19654
|
+
return [];
|
|
19655
|
+
}
|
|
19656
|
+
}
|
|
19606
19657
|
function toPascalCase(str) {
|
|
19607
19658
|
return str.replace(/(^\w|-\w)/g, (g) => g.replace(/-/, "").toUpperCase());
|
|
19608
19659
|
}
|
|
@@ -19815,8 +19866,7 @@ function generateEmptyIndexTemplate(featureName) {
|
|
|
19815
19866
|
`;
|
|
19816
19867
|
}
|
|
19817
19868
|
async function scaffoldEmptyFeature(featureName, featureDir) {
|
|
19818
|
-
|
|
19819
|
-
spinner.start();
|
|
19869
|
+
logger5.info(`Creating empty feature '${featureName}'...`);
|
|
19820
19870
|
try {
|
|
19821
19871
|
await fs5.mkdir(path6.join(featureDir, "controllers"), { recursive: true });
|
|
19822
19872
|
await fs5.mkdir(path6.join(featureDir, "procedures"), { recursive: true });
|
|
@@ -19832,15 +19882,14 @@ async function scaffoldEmptyFeature(featureName, featureDir) {
|
|
|
19832
19882
|
path6.join(featureDir, "index.ts"),
|
|
19833
19883
|
generateEmptyIndexTemplate(featureName)
|
|
19834
19884
|
);
|
|
19835
|
-
|
|
19885
|
+
logger5.success(`Scaffolded empty feature '${featureName}'`);
|
|
19836
19886
|
} catch (error) {
|
|
19837
|
-
|
|
19887
|
+
logger5.error(`Failed to create empty feature '${featureName}'`);
|
|
19838
19888
|
throw error;
|
|
19839
19889
|
}
|
|
19840
19890
|
}
|
|
19841
19891
|
async function scaffoldFeatureFromSchema(featureName, schemaString, featureDir) {
|
|
19842
|
-
|
|
19843
|
-
spinner.start();
|
|
19892
|
+
logger5.info(`Scaffolding feature '${featureName}' from schema...`);
|
|
19844
19893
|
try {
|
|
19845
19894
|
const [providerName, modelName] = schemaString.split(":");
|
|
19846
19895
|
if (!providerName || !modelName) {
|
|
@@ -19851,7 +19900,7 @@ async function scaffoldFeatureFromSchema(featureName, schemaString, featureDir)
|
|
|
19851
19900
|
if (!model) {
|
|
19852
19901
|
throw new Error(`Model '${modelName}' not found using provider '${providerName}'.`);
|
|
19853
19902
|
}
|
|
19854
|
-
|
|
19903
|
+
logger5.info("Generating files from model schema...");
|
|
19855
19904
|
await fs5.mkdir(path6.join(featureDir, "controllers"), { recursive: true });
|
|
19856
19905
|
await fs5.mkdir(path6.join(featureDir, "procedures"), { recursive: true });
|
|
19857
19906
|
await writeFile2(
|
|
@@ -19870,15 +19919,64 @@ async function scaffoldFeatureFromSchema(featureName, schemaString, featureDir)
|
|
|
19870
19919
|
path6.join(featureDir, "index.ts"),
|
|
19871
19920
|
generateCrudIndexTemplate(featureName)
|
|
19872
19921
|
);
|
|
19873
|
-
|
|
19922
|
+
logger5.success(`Successfully scaffolded feature '${featureName}' from '${modelName}' model.`);
|
|
19874
19923
|
console.log(chalk5.cyan(`
|
|
19875
19924
|
\u2705 Next step: Register the '${toCamelCase(featureName)}Controller' in 'src/igniter.router.ts'`));
|
|
19876
19925
|
} catch (error) {
|
|
19877
|
-
|
|
19926
|
+
logger5.error(`Failed to scaffold feature from schema`);
|
|
19878
19927
|
throw error;
|
|
19879
19928
|
}
|
|
19880
19929
|
}
|
|
19881
|
-
async function handleGenerateFeature(
|
|
19930
|
+
async function handleGenerateFeature(name, options = {}) {
|
|
19931
|
+
let featureName = name;
|
|
19932
|
+
if (!featureName) {
|
|
19933
|
+
const prompts2 = await import("prompts");
|
|
19934
|
+
const hasPrisma = await hasPrismaSchema();
|
|
19935
|
+
const prismaModels = hasPrisma ? await getPrismaModels() : [];
|
|
19936
|
+
const questions = [
|
|
19937
|
+
{
|
|
19938
|
+
type: "text",
|
|
19939
|
+
name: "featureName",
|
|
19940
|
+
message: "What is the name of your feature?",
|
|
19941
|
+
validate: (input) => {
|
|
19942
|
+
if (!input.trim()) {
|
|
19943
|
+
return "Feature name is required";
|
|
19944
|
+
}
|
|
19945
|
+
if (!/^[a-zA-Z][a-zA-Z0-9-_]*$/.test(input)) {
|
|
19946
|
+
return "Feature name must start with a letter and contain only letters, numbers, hyphens, and underscores";
|
|
19947
|
+
}
|
|
19948
|
+
return true;
|
|
19949
|
+
}
|
|
19950
|
+
}
|
|
19951
|
+
];
|
|
19952
|
+
if (hasPrisma && prismaModels.length > 0) {
|
|
19953
|
+
questions.push({
|
|
19954
|
+
type: "select",
|
|
19955
|
+
name: "useModel",
|
|
19956
|
+
message: "Would you like to generate CRUD operations from a Prisma model?",
|
|
19957
|
+
choices: [
|
|
19958
|
+
{ title: "No, create an empty feature", value: "none" },
|
|
19959
|
+
{ title: "Yes, select a Prisma model", value: "select" }
|
|
19960
|
+
],
|
|
19961
|
+
initial: 0
|
|
19962
|
+
});
|
|
19963
|
+
questions.push({
|
|
19964
|
+
type: (prev) => prev === "select" ? "select" : null,
|
|
19965
|
+
name: "selectedModel",
|
|
19966
|
+
message: "Which Prisma model would you like to use?",
|
|
19967
|
+
choices: prismaModels.map((model) => ({ title: model, value: model }))
|
|
19968
|
+
});
|
|
19969
|
+
}
|
|
19970
|
+
const response = await prompts2.default(questions);
|
|
19971
|
+
if (!response.featureName) {
|
|
19972
|
+
logger5.error("Feature name is required");
|
|
19973
|
+
process.exit(1);
|
|
19974
|
+
}
|
|
19975
|
+
featureName = response.featureName;
|
|
19976
|
+
if (response.useModel === "select" && response.selectedModel) {
|
|
19977
|
+
options.schema = `prisma:${response.selectedModel}`;
|
|
19978
|
+
}
|
|
19979
|
+
}
|
|
19882
19980
|
const normalizedName = featureName.toLowerCase();
|
|
19883
19981
|
const featureDir = path6.join(process.cwd(), "src", "features", normalizedName);
|
|
19884
19982
|
logger5.info(`Scaffolding feature: ${chalk5.cyan(normalizedName)}`);
|
|
@@ -19902,6 +20000,74 @@ async function handleGenerateProcedure(name, feature) {
|
|
|
19902
20000
|
logger5.warn(`'generate procedure' is not yet fully implemented. Use 'generate feature --schema' instead.`);
|
|
19903
20001
|
}
|
|
19904
20002
|
|
|
20003
|
+
// src/lib/port-manager.ts
|
|
20004
|
+
init_logger();
|
|
20005
|
+
import { spawn as spawn3 } from "child_process";
|
|
20006
|
+
async function killProcessOnPort(port) {
|
|
20007
|
+
const isWindows = process.platform === "win32";
|
|
20008
|
+
const isMac = process.platform === "darwin";
|
|
20009
|
+
const isLinux = process.platform === "linux";
|
|
20010
|
+
try {
|
|
20011
|
+
let command;
|
|
20012
|
+
let args;
|
|
20013
|
+
if (isWindows) {
|
|
20014
|
+
command = "cmd";
|
|
20015
|
+
args = ["/c", `netstat -ano | findstr :${port} | findstr LISTENING`];
|
|
20016
|
+
} else if (isMac || isLinux) {
|
|
20017
|
+
command = "lsof";
|
|
20018
|
+
args = ["-ti", `:${port}`];
|
|
20019
|
+
} else {
|
|
20020
|
+
logger.warn(`Unsupported platform: ${process.platform}. Skipping port cleanup.`);
|
|
20021
|
+
return;
|
|
20022
|
+
}
|
|
20023
|
+
const child = spawn3(command, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
20024
|
+
let stdout = "";
|
|
20025
|
+
let stderr = "";
|
|
20026
|
+
child.stdout.on("data", (data) => {
|
|
20027
|
+
stdout += data.toString();
|
|
20028
|
+
});
|
|
20029
|
+
child.stderr.on("data", (data) => {
|
|
20030
|
+
stderr += data.toString();
|
|
20031
|
+
});
|
|
20032
|
+
await new Promise((resolve5) => {
|
|
20033
|
+
child.on("close", async (code) => {
|
|
20034
|
+
if (code === 0 && stdout.trim()) {
|
|
20035
|
+
const pids = stdout.trim().split("\n").filter((line) => line.trim());
|
|
20036
|
+
if (pids.length > 0) {
|
|
20037
|
+
logger.info(`Found ${pids.length} process(es) using port ${port}. Killing...`);
|
|
20038
|
+
for (const pid of pids) {
|
|
20039
|
+
try {
|
|
20040
|
+
if (isWindows) {
|
|
20041
|
+
const parts = pid.trim().split(/\s+/);
|
|
20042
|
+
const actualPid = parts[parts.length - 1];
|
|
20043
|
+
spawn3("taskkill", ["/PID", actualPid, "/F"], { stdio: "inherit" });
|
|
20044
|
+
} else {
|
|
20045
|
+
process.kill(parseInt(pid.trim()), "SIGTERM");
|
|
20046
|
+
setTimeout(() => {
|
|
20047
|
+
try {
|
|
20048
|
+
process.kill(parseInt(pid.trim()), "SIGKILL");
|
|
20049
|
+
} catch (e) {
|
|
20050
|
+
}
|
|
20051
|
+
}, 2e3);
|
|
20052
|
+
}
|
|
20053
|
+
} catch (error) {
|
|
20054
|
+
logger.warn(`Failed to kill process ${pid}:`, error);
|
|
20055
|
+
}
|
|
20056
|
+
}
|
|
20057
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1e3));
|
|
20058
|
+
logger.info(`Port ${port} freed successfully.`);
|
|
20059
|
+
}
|
|
20060
|
+
} else if (code !== 0 && stderr) {
|
|
20061
|
+
logger.debug(`Port check command failed (this is normal if no processes are using the port): ${stderr}`);
|
|
20062
|
+
}
|
|
20063
|
+
resolve5();
|
|
20064
|
+
});
|
|
20065
|
+
});
|
|
20066
|
+
} catch (error) {
|
|
20067
|
+
logger.warn(`Failed to check/kill processes on port ${port}:`, error);
|
|
20068
|
+
}
|
|
20069
|
+
}
|
|
20070
|
+
|
|
19905
20071
|
// src/index.ts
|
|
19906
20072
|
var program = new Command();
|
|
19907
20073
|
program.name("igniter").description("CLI for Igniter.js type-safe client generation").version("1.0.0").option("--debug", "Enable debug mode for detailed logging", false).hook("preAction", (thisCommand) => {
|
|
@@ -19960,6 +20126,9 @@ program.command("init").description("Create a new Igniter.js project with intera
|
|
|
19960
20126
|
}
|
|
19961
20127
|
});
|
|
19962
20128
|
program.command("dev").description("Start development mode with framework and Igniter (interactive dashboard and OpenAPI docs by default)").option("--framework <type>", `Framework type (${getFrameworkList()}, generic)`).option("--output <dir>", "Output directory for generated client files", "src/").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)").option("--docs-output <dir>", "Output directory for OpenAPI docs", "./src/docs").action(async (options) => {
|
|
20129
|
+
const port = parseInt(options.port) || 3e3;
|
|
20130
|
+
logger.info(`Checking and freeing port ${port}...`);
|
|
20131
|
+
await killProcessOnPort(port);
|
|
19963
20132
|
const detectedFramework = detectFramework();
|
|
19964
20133
|
const framework = options.framework ? isFrameworkSupported(options.framework) ? options.framework : "generic" : detectedFramework;
|
|
19965
20134
|
const useInteractive = options.interactive !== false;
|
|
@@ -19984,7 +20153,7 @@ program.command("dev").description("Start development mode with framework and Ig
|
|
|
19984
20153
|
command: frameworkCommand,
|
|
19985
20154
|
color: "green",
|
|
19986
20155
|
cwd: process.cwd(),
|
|
19987
|
-
env: { PORT:
|
|
20156
|
+
env: { PORT: port.toString(), NODE_ENV: "development" }
|
|
19988
20157
|
});
|
|
19989
20158
|
}
|
|
19990
20159
|
}
|
|
@@ -20115,7 +20284,7 @@ generate.command("docs").description("Generate OpenAPI specification and/or inte
|
|
|
20115
20284
|
process.exit(1);
|
|
20116
20285
|
}
|
|
20117
20286
|
});
|
|
20118
|
-
generate.command("feature").description("Scaffold a new feature module").argument("
|
|
20287
|
+
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) => {
|
|
20119
20288
|
await handleGenerateFeature(name, options);
|
|
20120
20289
|
});
|
|
20121
20290
|
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) => {
|