@postxl/cli 1.7.1 → 1.8.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/dist/generate-types.command.js +25 -15
- package/dist/generate.command.js +22 -21
- package/dist/helpers/load-project-schema.d.ts +14 -0
- package/dist/helpers/load-project-schema.js +117 -0
- package/dist/helpers/load-split-schema-files.d.ts +19 -0
- package/dist/helpers/load-split-schema-files.js +114 -0
- package/package.json +4 -4
|
@@ -40,6 +40,7 @@ const generator_1 = require("@postxl/generator");
|
|
|
40
40
|
const generators_1 = require("@postxl/generators");
|
|
41
41
|
const schema_1 = require("@postxl/schema");
|
|
42
42
|
const utils_1 = require("@postxl/utils");
|
|
43
|
+
const load_project_schema_1 = require("./helpers/load-project-schema");
|
|
43
44
|
const log_schema_error_1 = require("./helpers/log-schema-error");
|
|
44
45
|
function register(program) {
|
|
45
46
|
program
|
|
@@ -56,12 +57,14 @@ This reads the schema and runs only the types generator. The generated type file
|
|
|
56
57
|
if (!path.isAbsolute(inputPath)) {
|
|
57
58
|
inputPath = path.join(process.cwd(), inputPath);
|
|
58
59
|
}
|
|
59
|
-
// Resolve schema path
|
|
60
|
+
// Resolve schema path / project path
|
|
60
61
|
let schemaPath = '';
|
|
62
|
+
let projectPath;
|
|
61
63
|
try {
|
|
62
64
|
const stats = await fs.stat(inputPath);
|
|
63
65
|
if (stats.isDirectory()) {
|
|
64
66
|
schemaPath = path.join(inputPath, 'postxl-schema.json');
|
|
67
|
+
projectPath = inputPath;
|
|
65
68
|
}
|
|
66
69
|
else if (stats.isFile()) {
|
|
67
70
|
schemaPath = inputPath;
|
|
@@ -79,22 +82,29 @@ This reads the schema and runs only the types generator. The generated type file
|
|
|
79
82
|
catch {
|
|
80
83
|
program.error(`Cannot find schema file at ${schemaPath}`);
|
|
81
84
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
jsonSchema = JSON.parse(content);
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
program.error(`Error reading or parsing JSON from ${schemaPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
85
|
+
let schema;
|
|
86
|
+
if (projectPath !== undefined) {
|
|
87
|
+
// Directory input: use the shared loader (picks up schema/*.model.json etc.)
|
|
88
|
+
schema = await (0, load_project_schema_1.loadProjectSchema)({ program, projectPath });
|
|
90
89
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
else {
|
|
91
|
+
// Single schema file input: parse as-is, no split-file discovery.
|
|
92
|
+
let jsonSchema;
|
|
93
|
+
try {
|
|
94
|
+
const content = await fs.readFile(schemaPath, 'utf-8');
|
|
95
|
+
jsonSchema = JSON.parse(content);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
program.error(`Error reading or parsing JSON from ${schemaPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
99
|
+
}
|
|
100
|
+
const result = schema_1.zProjectSchema.safeParse(jsonSchema);
|
|
101
|
+
if (!result.success) {
|
|
102
|
+
console.log('\nError parsing schema:\n');
|
|
103
|
+
(0, log_schema_error_1.logSchemaValidationError)(result.error);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
schema = result.data;
|
|
96
107
|
}
|
|
97
|
-
const schema = result.data;
|
|
98
108
|
// Resolve output path
|
|
99
109
|
let outputPath = options.output;
|
|
100
110
|
if (!path.isAbsolute(outputPath)) {
|
package/dist/generate.command.js
CHANGED
|
@@ -43,9 +43,8 @@ const dotenv_1 = __importDefault(require("dotenv"));
|
|
|
43
43
|
const fs = __importStar(require("node:fs/promises"));
|
|
44
44
|
const path = __importStar(require("node:path"));
|
|
45
45
|
const generator_1 = require("@postxl/generator");
|
|
46
|
-
const schema_1 = require("@postxl/schema");
|
|
47
46
|
const utils_1 = require("@postxl/utils");
|
|
48
|
-
const
|
|
47
|
+
const load_project_schema_1 = require("./helpers/load-project-schema");
|
|
49
48
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
50
49
|
function register(program) {
|
|
51
50
|
program
|
|
@@ -60,6 +59,7 @@ This reads the schema from ${(0, utils_1.yellow)('schema.json')} and runs the ge
|
|
|
60
59
|
.option('-e, --ejected', 'lists ejected files')
|
|
61
60
|
.option('-f, --force', 'overwrites ejected files')
|
|
62
61
|
.option('-i, --ignore-errors', 'ignores errors during generation')
|
|
62
|
+
.option('-m, --model <names...>', 'regenerate only for the given models (accepts multiple, e.g. -m Country City)')
|
|
63
63
|
.option('-n, --no-fix', 'does not attempt to fix linting errors')
|
|
64
64
|
.option('-p, --pattern <glob>', 'filters files by glob pattern (e.g., "**/*.ts" or "backend/libs/**")')
|
|
65
65
|
.option('-q, --quiet', 'does not print errors')
|
|
@@ -78,7 +78,7 @@ This reads the schema from ${(0, utils_1.yellow)('schema.json')} and runs the ge
|
|
|
78
78
|
}
|
|
79
79
|
const envConfig = dotenv_1.default.config({ path: path.join(projectPath, '.env') });
|
|
80
80
|
const localGenerators = await getLocalGenerators({ program, projectPath });
|
|
81
|
-
const schema = await
|
|
81
|
+
const schema = await (0, load_project_schema_1.loadProjectSchema)({ program, projectPath });
|
|
82
82
|
targetPath = await resolveTargetPath({
|
|
83
83
|
projectPath,
|
|
84
84
|
targetPath,
|
|
@@ -94,10 +94,15 @@ This reads the schema from ${(0, utils_1.yellow)('schema.json')} and runs the ge
|
|
|
94
94
|
if (options.pattern) {
|
|
95
95
|
console.log(`Filtering files by pattern: ${options.pattern}`);
|
|
96
96
|
}
|
|
97
|
+
const targetModelNames = resolveTargetModelNames({ program, schema, names: options.model });
|
|
98
|
+
if (targetModelNames) {
|
|
99
|
+
console.log(`Restricting generation to models: ${[...targetModelNames].join(', ')}`);
|
|
100
|
+
}
|
|
97
101
|
const manager = new generator_1.GeneratorManager(localGenerators);
|
|
98
102
|
const baseContext = (0, generator_1.prepareBaseContext)(schema, {
|
|
99
103
|
...options,
|
|
100
104
|
...(options.pattern ? { filePattern: options.pattern } : {}),
|
|
105
|
+
...(targetModelNames ? { targetModelNames } : {}),
|
|
101
106
|
});
|
|
102
107
|
const generatedContext = await manager.generate(baseContext);
|
|
103
108
|
return (new generator_1.Generator(generatedContext)
|
|
@@ -126,6 +131,7 @@ This reads the schema from ${(0, utils_1.yellow)('schema.json')} and runs the ge
|
|
|
126
131
|
lockFilePath: path.join(targetPath, 'postxl-lock.json'),
|
|
127
132
|
diskFilePath: targetPath,
|
|
128
133
|
force: options.force ?? false,
|
|
134
|
+
...(targetModelNames ? { selectiveGeneration: true } : {}),
|
|
129
135
|
});
|
|
130
136
|
(0, generator_1.logSyncResult)(result, {
|
|
131
137
|
showEjectedStats: options.ejected ?? false,
|
|
@@ -223,24 +229,6 @@ async function getLocalGenerators(params) {
|
|
|
223
229
|
}
|
|
224
230
|
return localGenerate();
|
|
225
231
|
}
|
|
226
|
-
async function getSchema(params) {
|
|
227
|
-
const { projectPath } = params;
|
|
228
|
-
const schemaPath = path.join(projectPath, 'postxl-schema.json');
|
|
229
|
-
try {
|
|
230
|
-
await fs.access(schemaPath);
|
|
231
|
-
}
|
|
232
|
-
catch {
|
|
233
|
-
params.program.error(`Cannot find file "postxl-schema.json" found at ${schemaPath}`);
|
|
234
|
-
}
|
|
235
|
-
const jsonSchema = JSON.parse((await fs.readFile(schemaPath)).toString());
|
|
236
|
-
const schema = schema_1.zProjectSchema.safeParse(jsonSchema);
|
|
237
|
-
if (!schema.success) {
|
|
238
|
-
console.log('\nError parsing postxl-schema.json:\n');
|
|
239
|
-
(0, log_schema_error_1.logSchemaValidationError)(schema.error);
|
|
240
|
-
process.exit(1);
|
|
241
|
-
}
|
|
242
|
-
return schema.data;
|
|
243
|
-
}
|
|
244
232
|
/**
|
|
245
233
|
* Checks if the current projectPath is within the PostXL monorepo workspace
|
|
246
234
|
* by verifying if '../../packages/cli' exists relative to the project path.
|
|
@@ -255,6 +243,19 @@ async function isInPostxlWorkspace(projectPath) {
|
|
|
255
243
|
return false;
|
|
256
244
|
}
|
|
257
245
|
}
|
|
246
|
+
function resolveTargetModelNames(params) {
|
|
247
|
+
const { program, schema, names } = params;
|
|
248
|
+
if (!names || names.length === 0) {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
const available = [...schema.models.keys()];
|
|
252
|
+
const availableSet = new Set(available);
|
|
253
|
+
const unknown = names.filter((n) => !availableSet.has(n));
|
|
254
|
+
if (unknown.length > 0) {
|
|
255
|
+
program.error(`Model${unknown.length > 1 ? 's' : ''} ${unknown.map((n) => `"${n}"`).join(', ')} ${unknown.length > 1 ? 'are' : 'is'} not defined in this schema. Available models: ${available.join(', ')}`);
|
|
256
|
+
}
|
|
257
|
+
return new Set(names);
|
|
258
|
+
}
|
|
258
259
|
async function resolveTargetPath({ projectPath, targetPath, envTargetPath, schemaSlug, projectType, }) {
|
|
259
260
|
let resolved = targetPath ?? envTargetPath;
|
|
260
261
|
if (!resolved) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { ProjectSchema } from '@postxl/schema';
|
|
3
|
+
/**
|
|
4
|
+
* Loads `postxl-schema.json` from the given project path, merges in any
|
|
5
|
+
* per-model / per-enum files under `schema/`, and validates the result
|
|
6
|
+
* against `zProjectSchema`.
|
|
7
|
+
*
|
|
8
|
+
* On validation error, logs the formatted Zod error and exits the process —
|
|
9
|
+
* preserves the behavior of the previous in-command schema loader.
|
|
10
|
+
*/
|
|
11
|
+
export declare function loadProjectSchema(params: {
|
|
12
|
+
projectPath: string;
|
|
13
|
+
program: Command;
|
|
14
|
+
}): Promise<ProjectSchema>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadProjectSchema = loadProjectSchema;
|
|
37
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const schema_1 = require("@postxl/schema");
|
|
40
|
+
const load_split_schema_files_1 = require("./load-split-schema-files");
|
|
41
|
+
const log_schema_error_1 = require("./log-schema-error");
|
|
42
|
+
/**
|
|
43
|
+
* Loads `postxl-schema.json` from the given project path, merges in any
|
|
44
|
+
* per-model / per-enum files under `schema/`, and validates the result
|
|
45
|
+
* against `zProjectSchema`.
|
|
46
|
+
*
|
|
47
|
+
* On validation error, logs the formatted Zod error and exits the process —
|
|
48
|
+
* preserves the behavior of the previous in-command schema loader.
|
|
49
|
+
*/
|
|
50
|
+
async function loadProjectSchema(params) {
|
|
51
|
+
const { projectPath, program } = params;
|
|
52
|
+
const schemaPath = path.join(projectPath, 'postxl-schema.json');
|
|
53
|
+
try {
|
|
54
|
+
await fs.access(schemaPath);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
program.error(`Cannot find file "postxl-schema.json" at ${schemaPath}`);
|
|
58
|
+
}
|
|
59
|
+
let rawJson;
|
|
60
|
+
try {
|
|
61
|
+
const content = await fs.readFile(schemaPath, 'utf-8');
|
|
62
|
+
rawJson = JSON.parse(content);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
66
|
+
return program.error(`Error reading or parsing JSON from ${schemaPath}: ${message}`);
|
|
67
|
+
}
|
|
68
|
+
const modelFilesGlob = asStringArray(rawJson.modelFiles);
|
|
69
|
+
const enumFilesGlob = asStringArray(rawJson.enumFiles);
|
|
70
|
+
const split = await (0, load_split_schema_files_1.loadSplitSchemaFiles)({
|
|
71
|
+
projectPath,
|
|
72
|
+
...(modelFilesGlob ? { modelFilesGlob } : {}),
|
|
73
|
+
...(enumFilesGlob ? { enumFilesGlob } : {}),
|
|
74
|
+
});
|
|
75
|
+
const mergedInput = {
|
|
76
|
+
...rawJson,
|
|
77
|
+
models: (0, schema_1.mergeSplitSchemaEntries)({
|
|
78
|
+
inline: rawJson.models,
|
|
79
|
+
fromFiles: toDefinitionMap(split.models),
|
|
80
|
+
kind: 'model',
|
|
81
|
+
fromFilesPaths: toPathMap(split.models),
|
|
82
|
+
}),
|
|
83
|
+
enums: (0, schema_1.mergeSplitSchemaEntries)({
|
|
84
|
+
inline: rawJson.enums,
|
|
85
|
+
fromFiles: toDefinitionMap(split.enums),
|
|
86
|
+
kind: 'enum',
|
|
87
|
+
fromFilesPaths: toPathMap(split.enums),
|
|
88
|
+
}),
|
|
89
|
+
};
|
|
90
|
+
const result = schema_1.zProjectSchema.safeParse(mergedInput);
|
|
91
|
+
if (!result.success) {
|
|
92
|
+
console.log('\nError parsing postxl-schema.json:\n');
|
|
93
|
+
(0, log_schema_error_1.logSchemaValidationError)(result.error);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
return result.data;
|
|
97
|
+
}
|
|
98
|
+
function toDefinitionMap(entries) {
|
|
99
|
+
const out = {};
|
|
100
|
+
for (const [name, { definition }] of Object.entries(entries)) {
|
|
101
|
+
out[name] = definition;
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
function toPathMap(entries) {
|
|
106
|
+
const out = {};
|
|
107
|
+
for (const [name, { filePath }] of Object.entries(entries)) {
|
|
108
|
+
out[name] = filePath;
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
function asStringArray(value) {
|
|
113
|
+
if (!Array.isArray(value)) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
return value.every((item) => typeof item === 'string') ? value : undefined;
|
|
117
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type LoadedEntry = {
|
|
2
|
+
definition: unknown;
|
|
3
|
+
filePath: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Globs the project directory for per-model / per-enum JSON files and returns
|
|
7
|
+
* their parsed contents keyed by name (derived from the filename basename).
|
|
8
|
+
*
|
|
9
|
+
* Returns empty maps when no `schema/` directory exists or nothing matches —
|
|
10
|
+
* existing single-file schemas are unaffected.
|
|
11
|
+
*/
|
|
12
|
+
export declare function loadSplitSchemaFiles(params: {
|
|
13
|
+
projectPath: string;
|
|
14
|
+
modelFilesGlob?: string[];
|
|
15
|
+
enumFilesGlob?: string[];
|
|
16
|
+
}): Promise<{
|
|
17
|
+
models: Record<string, LoadedEntry>;
|
|
18
|
+
enums: Record<string, LoadedEntry>;
|
|
19
|
+
}>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadSplitSchemaFiles = loadSplitSchemaFiles;
|
|
37
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const DEFAULT_MODEL_GLOBS = ['schema/*.model.json'];
|
|
40
|
+
const DEFAULT_ENUM_GLOBS = ['schema/*.enum.json'];
|
|
41
|
+
const MODEL_SUFFIX = '.model.json';
|
|
42
|
+
const ENUM_SUFFIX = '.enum.json';
|
|
43
|
+
const FILE_BASENAME_REGEX = /^[a-z][A-Za-z0-9_]*$/;
|
|
44
|
+
/**
|
|
45
|
+
* Globs the project directory for per-model / per-enum JSON files and returns
|
|
46
|
+
* their parsed contents keyed by name (derived from the filename basename).
|
|
47
|
+
*
|
|
48
|
+
* Returns empty maps when no `schema/` directory exists or nothing matches —
|
|
49
|
+
* existing single-file schemas are unaffected.
|
|
50
|
+
*/
|
|
51
|
+
async function loadSplitSchemaFiles(params) {
|
|
52
|
+
const { projectPath } = params;
|
|
53
|
+
const modelGlobs = params.modelFilesGlob ?? DEFAULT_MODEL_GLOBS;
|
|
54
|
+
const enumGlobs = params.enumFilesGlob ?? DEFAULT_ENUM_GLOBS;
|
|
55
|
+
const [models, enums] = await Promise.all([
|
|
56
|
+
loadEntries(projectPath, modelGlobs, MODEL_SUFFIX, 'model'),
|
|
57
|
+
loadEntries(projectPath, enumGlobs, ENUM_SUFFIX, 'enum'),
|
|
58
|
+
]);
|
|
59
|
+
return { models, enums };
|
|
60
|
+
}
|
|
61
|
+
async function loadEntries(projectPath, globs, suffix, kind) {
|
|
62
|
+
const matched = new Set();
|
|
63
|
+
for (const pattern of globs) {
|
|
64
|
+
const iterator = fs.glob(pattern, { cwd: projectPath });
|
|
65
|
+
for await (const relPath of iterator) {
|
|
66
|
+
matched.add(relPath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const sorted = [...matched].sort((a, b) => a.localeCompare(b));
|
|
70
|
+
const result = {};
|
|
71
|
+
for (const relPath of sorted) {
|
|
72
|
+
const basename = path.basename(relPath);
|
|
73
|
+
if (!basename.endsWith(suffix)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const fileBasename = basename.slice(0, -suffix.length);
|
|
77
|
+
if (!FILE_BASENAME_REGEX.test(fileBasename)) {
|
|
78
|
+
throw new Error(`Invalid ${kind} filename "${basename}" in "${relPath}". ${kind === 'model' ? 'Model' : 'Enum'} files must use camelCase basenames matching [a-z][A-Za-z0-9_]* (e.g. ${kind === 'model' ? 'country.model.json' : 'role.enum.json'}).`);
|
|
79
|
+
}
|
|
80
|
+
const name = fileBasename.charAt(0).toUpperCase() + fileBasename.slice(1);
|
|
81
|
+
const absPath = path.join(projectPath, relPath);
|
|
82
|
+
let raw;
|
|
83
|
+
try {
|
|
84
|
+
raw = await fs.readFile(absPath, 'utf-8');
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
88
|
+
throw new Error(`Failed to read ${kind} file "${relPath}": ${message}`);
|
|
89
|
+
}
|
|
90
|
+
let definition;
|
|
91
|
+
try {
|
|
92
|
+
definition = JSON.parse(raw);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
96
|
+
throw new Error(`Failed to parse JSON in ${kind} file "${relPath}": ${message}`);
|
|
97
|
+
}
|
|
98
|
+
if (definition && typeof definition === 'object' && !Array.isArray(definition)) {
|
|
99
|
+
const obj = definition;
|
|
100
|
+
if ('name' in obj) {
|
|
101
|
+
if (typeof obj.name === 'string' && obj.name !== name) {
|
|
102
|
+
throw new Error(`${kind === 'model' ? 'Model' : 'Enum'} file "${relPath}" has "name": "${obj.name}" but the filename implies "${name}". Remove the "name" field or rename the file.`);
|
|
103
|
+
}
|
|
104
|
+
const { name: _stripped, ...rest } = obj;
|
|
105
|
+
definition = rest;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (Object.prototype.hasOwnProperty.call(result, name)) {
|
|
109
|
+
throw new Error(`Duplicate ${kind} name "${name}" — matched by more than one file (last seen: "${relPath}"). Rename one of the files.`);
|
|
110
|
+
}
|
|
111
|
+
result[name] = { definition, filePath: relPath };
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postxl/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Command-line interface for PostXL code generation framework",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"commander": "14.0.3",
|
|
45
45
|
"dotenv": "17.3.1",
|
|
46
46
|
"zod-validation-error": "5.0.0",
|
|
47
|
-
"@postxl/generator": "^1.
|
|
48
|
-
"@postxl/generators": "^
|
|
49
|
-
"@postxl/schema": "^
|
|
47
|
+
"@postxl/generator": "^1.6.0",
|
|
48
|
+
"@postxl/generators": "^2.0.0",
|
|
49
|
+
"@postxl/schema": "^2.0.0",
|
|
50
50
|
"@postxl/utils": "^1.4.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {},
|