@appweaver/cli 1.0.0
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/LICENSE +1 -0
- package/README.md +7 -0
- package/build/build-command.d.ts +2 -0
- package/build/build-command.js +15 -0
- package/build/build-project.d.ts +8 -0
- package/build/build-project.js +19 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +18 -0
- package/generate/generate-command.d.ts +2 -0
- package/generate/generate-command.js +38 -0
- package/generate/generate-schema.d.ts +12 -0
- package/generate/generate-schema.js +475 -0
- package/generate/generate-types.d.ts +10 -0
- package/generate/generate-types.js +86 -0
- package/generate/index.d.ts +3 -0
- package/generate/index.js +19 -0
- package/migrate/index.d.ts +1 -0
- package/migrate/index.js +17 -0
- package/migrate/migrate-command.d.ts +2 -0
- package/migrate/migrate-command.js +13 -0
- package/migration/index.d.ts +1 -0
- package/migration/index.js +17 -0
- package/migration/migration-command.d.ts +2 -0
- package/migration/migration-command.js +34 -0
- package/openapi/index.d.ts +1 -0
- package/openapi/index.js +17 -0
- package/openapi/openapi-command.d.ts +2 -0
- package/openapi/openapi-command.js +46 -0
- package/package.json +56 -0
- package/seed/index.d.ts +1 -0
- package/seed/index.js +17 -0
- package/seed/seed-command.d.ts +2 -0
- package/seed/seed-command.js +33 -0
- package/skill/GUIDELINES.md +298 -0
- package/skill/SKILL.md +593 -0
- package/skill/references/cache.md +207 -0
- package/skill/references/cli.md +213 -0
- package/skill/references/client.md +507 -0
- package/skill/references/configuration.md +402 -0
- package/skill/references/database.md +134 -0
- package/skill/references/dependency-injection.md +214 -0
- package/skill/references/events.md +152 -0
- package/skill/references/mailer.md +235 -0
- package/skill/references/queue.md +196 -0
- package/skill/references/resources.md +961 -0
- package/skill/references/scheduler.md +184 -0
- package/skill/references/security.md +694 -0
- package/skill/references/storage.md +251 -0
- package/start/index.d.ts +2 -0
- package/start/index.js +18 -0
- package/start/start-command.d.ts +2 -0
- package/start/start-command.js +17 -0
- package/start/start-project.d.ts +8 -0
- package/start/start-project.js +147 -0
- package/testing/index.d.ts +1 -0
- package/testing/index.js +17 -0
- package/testing/testing-command.d.ts +2 -0
- package/testing/testing-command.js +96 -0
- package/update/index.d.ts +2 -0
- package/update/index.js +18 -0
- package/update/update-command.d.ts +2 -0
- package/update/update-command.js +84 -0
- package/update/update-packages.d.ts +10 -0
- package/update/update-packages.js +45 -0
- package/update/update-skill.d.ts +8 -0
- package/update/update-skill.js +93 -0
- package/utils/index.d.ts +3 -0
- package/utils/index.js +19 -0
- package/utils/loader-util.d.ts +29 -0
- package/utils/loader-util.js +132 -0
- package/utils/path-util.d.ts +41 -0
- package/utils/path-util.js +98 -0
- package/utils/process-util.d.ts +39 -0
- package/utils/process-util.js +92 -0
- package/weaver.d.ts +2 -0
- package/weaver.js +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
UNLICENSED
|
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildCommand = buildCommand;
|
|
4
|
+
const build_project_1 = require("./build-project");
|
|
5
|
+
function buildCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('build')
|
|
8
|
+
.alias('b')
|
|
9
|
+
.description('Build the application.')
|
|
10
|
+
.option('-p, --project [path]', 'TypeScript project config file.', 'tsconfig.build.json')
|
|
11
|
+
.action(async (_, command) => {
|
|
12
|
+
const projectFile = command.getOptionValue('project');
|
|
13
|
+
process.exit(await (0, build_project_1.buildProject)(projectFile));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the project by compiling TypeScript files and replacing alias paths.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} projectFile - The path to the TypeScript project configuration file.
|
|
5
|
+
* @return {Promise<number>} A promise that resolves to the status code of the TypeScript compilation process.
|
|
6
|
+
* Returns 0 if the compilation process is successful.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildProject(projectFile: string): Promise<number>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildProject = buildProject;
|
|
4
|
+
const tsc_alias_1 = require("tsc-alias");
|
|
5
|
+
const common_1 = require("@appweaver/common");
|
|
6
|
+
const utils_1 = require("../utils");
|
|
7
|
+
/**
|
|
8
|
+
* Builds the project by compiling TypeScript files and replacing alias paths.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} projectFile - The path to the TypeScript project configuration file.
|
|
11
|
+
* @return {Promise<number>} A promise that resolves to the status code of the TypeScript compilation process.
|
|
12
|
+
* Returns 0 if the compilation process is successful.
|
|
13
|
+
*/
|
|
14
|
+
async function buildProject(projectFile) {
|
|
15
|
+
await (0, utils_1.rimrafPath)(common_1.config.APP_BUILD_PATH, true);
|
|
16
|
+
const tscStatus = await (0, utils_1.runProcess)('tsc', ['-p', projectFile]);
|
|
17
|
+
await (0, tsc_alias_1.replaceTscAliasPaths)({ configFile: projectFile });
|
|
18
|
+
return tscStatus || 0;
|
|
19
|
+
}
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./build-command"), exports);
|
|
18
|
+
__exportStar(require("./build-project"), exports);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateCommand = generateCommand;
|
|
4
|
+
const common_1 = require("@appweaver/common");
|
|
5
|
+
const generate_types_1 = require("./generate-types");
|
|
6
|
+
const generate_schema_1 = require("./generate-schema");
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
function generateCommand(program) {
|
|
9
|
+
program
|
|
10
|
+
.command('generate')
|
|
11
|
+
.alias('g')
|
|
12
|
+
.description('Generate application types and/or schemas.')
|
|
13
|
+
.option('-t, --types', 'Generate TypeScript types.')
|
|
14
|
+
.option('-s, --schema', 'Generate Prisma schema.')
|
|
15
|
+
.option('--modelPattern [pattern]', 'Glob pattern for finding model files. (default: from config or env).')
|
|
16
|
+
.option('--typesPath [path]', 'Output path for generated types. (default: from config or env).')
|
|
17
|
+
.option('--schemaPath [path]', 'Output path for generated Prisma schema. (default: from config or env).')
|
|
18
|
+
.option('--clientPath [path]', 'Output path for generated Prisma client (default: from config or env).')
|
|
19
|
+
.option('--verbose', 'Print verbose output.')
|
|
20
|
+
.action(async (_, command) => {
|
|
21
|
+
const quiet = !command.getOptionValue('verbose');
|
|
22
|
+
const generateAll = !command.getOptionValue('types') && !command.getOptionValue('schema');
|
|
23
|
+
const models = await (0, utils_1.loadModels)(command.getOptionValue('modelPattern') ?? common_1.config.RESOURCE_MODEL_PATTERN);
|
|
24
|
+
let typeGenerateResult = 0;
|
|
25
|
+
if (command.getOptionValue('types') || generateAll) {
|
|
26
|
+
typeGenerateResult = await (0, generate_types_1.generateTypes)(models, command.getOptionValue('typesPath') ??
|
|
27
|
+
common_1.config.RESOURCE_GENERATED_TYPES_PATH, quiet);
|
|
28
|
+
}
|
|
29
|
+
let schemaGenerateResult = 0;
|
|
30
|
+
if (command.getOptionValue('schema') || generateAll) {
|
|
31
|
+
schemaGenerateResult = await (0, generate_schema_1.generateSchema)(models, command.getOptionValue('schemaPath') ?? common_1.config.DATABASE_SCHEMA_PATH, command.getOptionValue('clientPath') ??
|
|
32
|
+
common_1.config.DATABASE_CLIENT_OUTPUT_DIR_PATH, quiet);
|
|
33
|
+
}
|
|
34
|
+
if (typeGenerateResult !== 0 || schemaGenerateResult !== 0) {
|
|
35
|
+
process.exit(typeGenerateResult || schemaGenerateResult);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ResourceModel } from '@appweaver/common';
|
|
2
|
+
/**
|
|
3
|
+
* Generates a Prisma schema file based on the provided resource models and saves it to the specified file path.
|
|
4
|
+
*
|
|
5
|
+
* @param {Record<string, ResourceModel>} models - An object containing resource model configurations, where each key is
|
|
6
|
+
* the model name.
|
|
7
|
+
* @param {string} schemaPath - The file path where the generated Prisma schema should be stored.
|
|
8
|
+
* @param {string} clientPath - The file path where the Prisma client output should be generated.
|
|
9
|
+
* @param {boolean} [quiet=false] - Optional flag to suppress logs. Default is false.
|
|
10
|
+
* @return {Promise<number>} A promise that resolves to the number of models processed for the schema generation.
|
|
11
|
+
*/
|
|
12
|
+
export declare function generateSchema(models: Record<string, ResourceModel>, schemaPath: string, clientPath: string, quiet?: boolean): Promise<number>;
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateSchema = generateSchema;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const common_1 = require("@appweaver/common");
|
|
10
|
+
const utils_1 = require("../utils");
|
|
11
|
+
/**
|
|
12
|
+
* Generates a Prisma schema file based on the provided resource models and saves it to the specified file path.
|
|
13
|
+
*
|
|
14
|
+
* @param {Record<string, ResourceModel>} models - An object containing resource model configurations, where each key is
|
|
15
|
+
* the model name.
|
|
16
|
+
* @param {string} schemaPath - The file path where the generated Prisma schema should be stored.
|
|
17
|
+
* @param {string} clientPath - The file path where the Prisma client output should be generated.
|
|
18
|
+
* @param {boolean} [quiet=false] - Optional flag to suppress logs. Default is false.
|
|
19
|
+
* @return {Promise<number>} A promise that resolves to the number of models processed for the schema generation.
|
|
20
|
+
*/
|
|
21
|
+
async function generateSchema(models, schemaPath, clientPath, quiet = false) {
|
|
22
|
+
const cwd = process.cwd();
|
|
23
|
+
try {
|
|
24
|
+
await (0, utils_1.ensureDirExists)(node_path_1.default.join(cwd, schemaPath));
|
|
25
|
+
const prismaModels = {};
|
|
26
|
+
const prismaEnums = {};
|
|
27
|
+
const dbType = databaseType();
|
|
28
|
+
const relativeOutputPath = (0, utils_1.relativePathFrom)(schemaPath, clientPath);
|
|
29
|
+
const schemaContent = [
|
|
30
|
+
`// This is your Prisma schema file,`,
|
|
31
|
+
`// learn more about it in the docs: https://pris.ly/d/prisma-schema`,
|
|
32
|
+
``,
|
|
33
|
+
`// Generated by Appweaver. Please do not edit this file manually.`,
|
|
34
|
+
``,
|
|
35
|
+
`datasource db {`,
|
|
36
|
+
` provider = "${dbType}"`,
|
|
37
|
+
`}`,
|
|
38
|
+
``,
|
|
39
|
+
`generator client {`,
|
|
40
|
+
` provider = "prisma-client"`,
|
|
41
|
+
` output = "${relativeOutputPath}"`,
|
|
42
|
+
common_1.config.APP_RUNTIME === common_1.Runtime.Bun ? ' runtime = "bun"' : undefined,
|
|
43
|
+
`}`,
|
|
44
|
+
``
|
|
45
|
+
].filter((l) => l !== undefined);
|
|
46
|
+
let authModel;
|
|
47
|
+
for (const model of Object.values(models)) {
|
|
48
|
+
if ((0, common_1.isResourceAuthModel)(model)) {
|
|
49
|
+
authModel = model;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Create Prisma models and enums from resource model config
|
|
54
|
+
for (const [name, schema] of Object.entries(models)) {
|
|
55
|
+
if (schema.config.generateSchema === false) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
prismaModels[name] = {
|
|
59
|
+
id: createIdSchema(schema.config.id),
|
|
60
|
+
scalars: createScalarsSchema(name, schema.config.scalars),
|
|
61
|
+
relations: createRelationsSchema(name, schema.config.relations),
|
|
62
|
+
files: createFilesSchema(name, schema.config.files),
|
|
63
|
+
audit: createAuditSchema(name, authModel, schema.config.audit),
|
|
64
|
+
index: createIndexSchema(schema.config.index),
|
|
65
|
+
tableName: schema.config.tableName
|
|
66
|
+
};
|
|
67
|
+
for (const [fieldName, scalar] of Object.entries(schema.config.scalars ?? {})) {
|
|
68
|
+
if (scalar.type === 'enum' && scalar.values?.length) {
|
|
69
|
+
prismaEnums[`${name}${(0, common_1.capitalize)(fieldName)}`] = scalar.values;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Resolve model relations to match Prisma schema relationship convention
|
|
74
|
+
const createdByAuthUser = [];
|
|
75
|
+
const fileFields = [];
|
|
76
|
+
for (const [name, model] of Object.entries(prismaModels)) {
|
|
77
|
+
const modelSchema = Object.entries(models).find(([key]) => key === name)?.[1];
|
|
78
|
+
for (const relation of model.relations) {
|
|
79
|
+
if (relation.type.startsWith('Int')) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const relationConfig = modelSchema?.config.relations?.[relation.name];
|
|
83
|
+
const referencedName = relation.type
|
|
84
|
+
.replaceAll('[]', '')
|
|
85
|
+
.replaceAll('?', '');
|
|
86
|
+
const referencedModel = prismaModels[referencedName];
|
|
87
|
+
const mappedField = referencedModel.relations.find((r) => r.name === relationConfig?.mappedBy);
|
|
88
|
+
if (mappedField?.type.startsWith('Int')) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (mappedField && mappedField.attributes?.length) {
|
|
92
|
+
// Ensure that mapped fields have the same reference name
|
|
93
|
+
const relationAttribute = mappedField.attributes[0];
|
|
94
|
+
const relationParts = relationAttribute.split('"');
|
|
95
|
+
const referenceName = relation.attributes?.[0].split('"')[1];
|
|
96
|
+
relationParts.splice(1, 1, `${referenceName}`);
|
|
97
|
+
if (relationConfig?.unique) {
|
|
98
|
+
mappedField.attributes[0] = `@relation("${referenceName}")`;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
mappedField.attributes[0] = relationParts.join('"');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// For unmapped relations add a default relation field using the same
|
|
106
|
+
// reference name
|
|
107
|
+
const refName = (0, common_1.uncapitalize)((0, common_1.plural)(name));
|
|
108
|
+
if (!referencedModel.relations.some((r) => r.name === refName)) {
|
|
109
|
+
referencedModel.relations.push({
|
|
110
|
+
name: (0, common_1.uncapitalize)((0, common_1.plural)(name)),
|
|
111
|
+
type: `${name}[]`,
|
|
112
|
+
attributes: relation.attributes
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const auditField of model.audit) {
|
|
118
|
+
if (auditField.name === 'createdBy') {
|
|
119
|
+
const referenceName = auditField.attributes?.[0].split('"')[1];
|
|
120
|
+
createdByAuthUser.push({
|
|
121
|
+
name: `created${(0, common_1.plural)(name)}`,
|
|
122
|
+
type: `${name}[]`,
|
|
123
|
+
attributes: [`@relation("${referenceName}")`]
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
for (const fileField of model.files) {
|
|
128
|
+
const referenceName = fileField.attributes?.[0].split('"')[1];
|
|
129
|
+
if (referenceName) {
|
|
130
|
+
fileFields.push({
|
|
131
|
+
name: `${fileField.name}${(0, common_1.plural)(name)}`,
|
|
132
|
+
type: `${name}[]`,
|
|
133
|
+
attributes: [`@relation("${referenceName}")`]
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Add auth model ownership relations
|
|
139
|
+
const authUserModel = prismaModels[authModel?.name ?? ''];
|
|
140
|
+
if (authUserModel) {
|
|
141
|
+
authUserModel.extra = {
|
|
142
|
+
'Ownership models referenced with createdById column': createdByAuthUser
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// Add file fields relations
|
|
146
|
+
const fileModel = prismaModels['File'];
|
|
147
|
+
if (fileModel) {
|
|
148
|
+
fileModel.extra = {
|
|
149
|
+
'Related models with file columns': fileFields
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
// Add Prisma models to the schema content
|
|
153
|
+
for (const [name, model] of Object.entries(prismaModels)) {
|
|
154
|
+
schemaContent.push(`model ${name} {`);
|
|
155
|
+
// Group 1: id + scalars
|
|
156
|
+
const initialFields = [model.id, ...model.scalars];
|
|
157
|
+
const { nameLength, typeLength } = calculateMaxLengths(initialFields);
|
|
158
|
+
schemaContent.push(buildPrismaField(model.id, nameLength, typeLength));
|
|
159
|
+
for (const scalar of model.scalars) {
|
|
160
|
+
schemaContent.push(buildPrismaField(scalar, nameLength, typeLength));
|
|
161
|
+
}
|
|
162
|
+
// Group 2: relations
|
|
163
|
+
if (model.relations.length > 0) {
|
|
164
|
+
schemaContent.push(``);
|
|
165
|
+
schemaContent.push(` /// Related columns`);
|
|
166
|
+
const { nameLength, typeLength } = calculateMaxLengths(model.relations);
|
|
167
|
+
for (const relation of model.relations) {
|
|
168
|
+
schemaContent.push(buildPrismaField(relation, nameLength, typeLength));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Group 3: files
|
|
172
|
+
if (model.files.length > 0) {
|
|
173
|
+
schemaContent.push(``);
|
|
174
|
+
schemaContent.push(` /// File columns`);
|
|
175
|
+
const { nameLength, typeLength } = calculateMaxLengths(model.files);
|
|
176
|
+
for (const file of model.files) {
|
|
177
|
+
schemaContent.push(buildPrismaField(file, nameLength, typeLength));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Group 4: extra fields
|
|
181
|
+
for (const [title, extraFields] of Object.entries(model.extra ?? {})) {
|
|
182
|
+
if (extraFields.length === 0) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
schemaContent.push(``);
|
|
186
|
+
schemaContent.push(` /// ${title}`);
|
|
187
|
+
const { nameLength, typeLength } = calculateMaxLengths(extraFields);
|
|
188
|
+
for (const extraField of extraFields) {
|
|
189
|
+
schemaContent.push(buildPrismaField(extraField, nameLength, typeLength));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Group 5: audit
|
|
193
|
+
if (model.audit.length > 0) {
|
|
194
|
+
schemaContent.push(``);
|
|
195
|
+
schemaContent.push(` /// Audit columns`);
|
|
196
|
+
const { nameLength, typeLength } = calculateMaxLengths(model.audit);
|
|
197
|
+
for (const audit of model.audit) {
|
|
198
|
+
schemaContent.push(buildPrismaField(audit, nameLength, typeLength));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (model.index.length) {
|
|
202
|
+
schemaContent.push(``);
|
|
203
|
+
}
|
|
204
|
+
for (const index of model.index) {
|
|
205
|
+
schemaContent.push(` ${index}`);
|
|
206
|
+
}
|
|
207
|
+
if (model.tableName) {
|
|
208
|
+
schemaContent.push(``);
|
|
209
|
+
schemaContent.push(` @@map("${model.tableName}")`);
|
|
210
|
+
}
|
|
211
|
+
schemaContent.push(`}`, ``);
|
|
212
|
+
}
|
|
213
|
+
// Add Prisma enums to the schema content
|
|
214
|
+
for (const [name, enumValues] of Object.entries(prismaEnums)) {
|
|
215
|
+
schemaContent.push(`enum ${name} {`);
|
|
216
|
+
for (const value of enumValues) {
|
|
217
|
+
schemaContent.push(` ${value}`);
|
|
218
|
+
}
|
|
219
|
+
schemaContent.push(`}`, ``);
|
|
220
|
+
}
|
|
221
|
+
const outputPath = node_path_1.default.join(cwd, schemaPath);
|
|
222
|
+
let oldSchema;
|
|
223
|
+
try {
|
|
224
|
+
oldSchema = await promises_1.default.readFile(outputPath, 'utf8');
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
// Schema currently does not exist
|
|
228
|
+
}
|
|
229
|
+
await promises_1.default.writeFile(outputPath, schemaContent.join('\n'));
|
|
230
|
+
const code = await (0, utils_1.runProcess)('prisma', ['generate'], { quiet });
|
|
231
|
+
if (code !== 0 && oldSchema) {
|
|
232
|
+
await promises_1.default.writeFile(outputPath, oldSchema);
|
|
233
|
+
console.error(`Schema generation failed.${quiet ? ' Start with --verbose flag to see error details.' : ''}`);
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
console.log(`Schema generated to ${node_path_1.default.relative(cwd, schemaPath)}`);
|
|
238
|
+
return 0;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
console.error('Schema generation failed', error);
|
|
243
|
+
return 2;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function buildPrismaField(field, nameLength = 0, typeLength = 0) {
|
|
247
|
+
const paddedName = field.name.padEnd(nameLength);
|
|
248
|
+
const paddedType = field.type.padEnd(typeLength);
|
|
249
|
+
return ` ${paddedName} ${paddedType} ${field.attributes?.join(' ') ?? ''}`.trimEnd();
|
|
250
|
+
}
|
|
251
|
+
function calculateMaxLengths(fields) {
|
|
252
|
+
let nameLength = 0;
|
|
253
|
+
let typeLength = 0;
|
|
254
|
+
for (const field of fields) {
|
|
255
|
+
if (field.name.length > nameLength) {
|
|
256
|
+
nameLength = field.name.length;
|
|
257
|
+
}
|
|
258
|
+
if (field.type.length > typeLength) {
|
|
259
|
+
typeLength = field.type.length;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return { nameLength, typeLength };
|
|
263
|
+
}
|
|
264
|
+
function createIdSchema(id = {}) {
|
|
265
|
+
const defaultType = !id.generator || id.generator === 'autoincrement()' ? 'Int' : 'String';
|
|
266
|
+
const defaultGenerator = id.type !== 'string' ? 'autoincrement()' : 'uuid()';
|
|
267
|
+
return {
|
|
268
|
+
name: 'id',
|
|
269
|
+
type: (0, common_1.capitalize)(id.type ?? defaultType),
|
|
270
|
+
attributes: ['@id', `@default(${id.generator ?? defaultGenerator})`]
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function createScalarsSchema(modelName, scalars = {}) {
|
|
274
|
+
const fields = [];
|
|
275
|
+
for (const [name, scalar] of Object.entries(scalars)) {
|
|
276
|
+
fields.push(createScalarSchema(name, modelName, scalar));
|
|
277
|
+
}
|
|
278
|
+
return fields;
|
|
279
|
+
}
|
|
280
|
+
function createScalarSchema(name, modelName, scalar) {
|
|
281
|
+
const attributes = [];
|
|
282
|
+
const isSqlite = databaseType() === common_1.DatabaseType.Sqlite;
|
|
283
|
+
const sanitize = (val) => {
|
|
284
|
+
if ((0, common_1.isNumber)(val) || (0, common_1.isBoolean)(val)) {
|
|
285
|
+
return val;
|
|
286
|
+
}
|
|
287
|
+
else if ((0, common_1.isString)(val)) {
|
|
288
|
+
return val.replace(/"/g, '\\"');
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
return JSON.stringify(val).replace(/"/g, '\\"');
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
let typeSuffix = scalar.required === false ? '?' : '';
|
|
295
|
+
if (scalar.array) {
|
|
296
|
+
typeSuffix = '[]';
|
|
297
|
+
}
|
|
298
|
+
const type = scalar.type === 'enum' && scalar.values?.length
|
|
299
|
+
? `${modelName}${(0, common_1.capitalize)(name)}${typeSuffix}`
|
|
300
|
+
: `${(0, common_1.capitalize)(scalar.type)}${typeSuffix}`;
|
|
301
|
+
if (scalar.unique) {
|
|
302
|
+
attributes.push(`@unique`);
|
|
303
|
+
}
|
|
304
|
+
let defaultAttribute;
|
|
305
|
+
if (scalar.default !== undefined) {
|
|
306
|
+
if (['string', 'dateTime', 'json'].includes(scalar.type) && !scalar.array) {
|
|
307
|
+
defaultAttribute = `@default("${sanitize(scalar.default)}")`;
|
|
308
|
+
}
|
|
309
|
+
else if (scalar.array) {
|
|
310
|
+
const defaultValues = (0, common_1.isArray)(scalar.default)
|
|
311
|
+
? scalar.default
|
|
312
|
+
: [scalar.default];
|
|
313
|
+
const mappedValues = defaultValues
|
|
314
|
+
.map((v) => ['string', 'dateTime', 'json'].includes(scalar.type)
|
|
315
|
+
? `"${sanitize(v)}"`
|
|
316
|
+
: `${v}`)
|
|
317
|
+
.join(', ');
|
|
318
|
+
defaultAttribute = `@default([${mappedValues}])`;
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
defaultAttribute = `@default(${sanitize(scalar.default)})`;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
else if (scalar.defaultGenerator !== undefined) {
|
|
325
|
+
defaultAttribute = `@default(${scalar.defaultGenerator})`;
|
|
326
|
+
}
|
|
327
|
+
else if (scalar.defaultExpression?.length) {
|
|
328
|
+
defaultAttribute = `@default(dbgenerated("${scalar.defaultExpression}"))`;
|
|
329
|
+
}
|
|
330
|
+
if (defaultAttribute) {
|
|
331
|
+
attributes.push(defaultAttribute);
|
|
332
|
+
}
|
|
333
|
+
if (scalar.type === 'string' && scalar.maxLength && !isSqlite) {
|
|
334
|
+
attributes.push(`@db.VarChar(${scalar.maxLength})`);
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
name,
|
|
338
|
+
type,
|
|
339
|
+
attributes
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function createRelationsSchema(modelName, relations = {}) {
|
|
343
|
+
const fields = [];
|
|
344
|
+
for (const [name, relation] of Object.entries(relations)) {
|
|
345
|
+
fields.push(...createRelationSchema(name, modelName, relation));
|
|
346
|
+
}
|
|
347
|
+
return fields;
|
|
348
|
+
}
|
|
349
|
+
function createRelationSchema(name, modelName, relation) {
|
|
350
|
+
const attributes = [];
|
|
351
|
+
const relationName = `${modelName}${(0, common_1.capitalize)(name)}${relation.model}`;
|
|
352
|
+
const relationFieldName = `${name}Id`;
|
|
353
|
+
const relationSuffix = relation.required === false ? '?' : '';
|
|
354
|
+
const type = relation.array
|
|
355
|
+
? `${relation.model}[]`
|
|
356
|
+
: `${relation.model}${relationSuffix}`;
|
|
357
|
+
if (relation.array) {
|
|
358
|
+
attributes.push(`@relation("${relationName}")`);
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
const referentialActions = [];
|
|
362
|
+
if (relation.onDelete) {
|
|
363
|
+
referentialActions.push(`onDelete: ${(0, common_1.capitalize)(relation.onDelete)}`);
|
|
364
|
+
}
|
|
365
|
+
if (relation.onUpdate) {
|
|
366
|
+
referentialActions.push(`onUpdate: ${(0, common_1.capitalize)(relation.onUpdate)}`);
|
|
367
|
+
}
|
|
368
|
+
const referentialConfig = referentialActions.length > 0 ? `, ${referentialActions.join(', ')}` : '';
|
|
369
|
+
attributes.push(`@relation("${relationName}", fields: [${relationFieldName}], references: [id]${referentialConfig})`);
|
|
370
|
+
}
|
|
371
|
+
const relationFields = [
|
|
372
|
+
{
|
|
373
|
+
name,
|
|
374
|
+
type,
|
|
375
|
+
attributes
|
|
376
|
+
}
|
|
377
|
+
];
|
|
378
|
+
if (!relation.array && relation.owner) {
|
|
379
|
+
relationFields.push({
|
|
380
|
+
name: relationFieldName,
|
|
381
|
+
type: `Int${relationSuffix}`,
|
|
382
|
+
attributes: relation.unique === true ? ['@unique'] : []
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
return relationFields;
|
|
386
|
+
}
|
|
387
|
+
function createFilesSchema(modelName, files = {}) {
|
|
388
|
+
const fields = [];
|
|
389
|
+
for (const [name, file] of Object.entries(files)) {
|
|
390
|
+
fields.push(...createFileSchema(name, modelName, file));
|
|
391
|
+
}
|
|
392
|
+
return fields;
|
|
393
|
+
}
|
|
394
|
+
function createFileSchema(name, modelName, file) {
|
|
395
|
+
const attributes = [];
|
|
396
|
+
const type = file.array ? 'File[]' : 'File?';
|
|
397
|
+
const fileRelationName = `${modelName}${(0, common_1.capitalize)(name)}File`;
|
|
398
|
+
const fileRelationFieldName = `${name}Id`;
|
|
399
|
+
if (file.array) {
|
|
400
|
+
attributes.push(`@relation("${fileRelationName}")`);
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
attributes.push(`@relation("${fileRelationName}", fields: [${fileRelationFieldName}], references: [id])`);
|
|
404
|
+
}
|
|
405
|
+
const fileFields = [
|
|
406
|
+
{
|
|
407
|
+
name,
|
|
408
|
+
type,
|
|
409
|
+
attributes
|
|
410
|
+
}
|
|
411
|
+
];
|
|
412
|
+
if (!file.array) {
|
|
413
|
+
fileFields.push({
|
|
414
|
+
name: fileRelationFieldName,
|
|
415
|
+
type: 'Int?',
|
|
416
|
+
attributes: ['@unique']
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return fileFields;
|
|
420
|
+
}
|
|
421
|
+
function createAuditSchema(modelName, authModel, audit = {}) {
|
|
422
|
+
const defaultAudit = {
|
|
423
|
+
updatedAt: true,
|
|
424
|
+
createdAt: true,
|
|
425
|
+
createdById: true
|
|
426
|
+
};
|
|
427
|
+
const mergedAudit = { ...defaultAudit, ...audit };
|
|
428
|
+
const fields = [];
|
|
429
|
+
const authModelName = authModel?.name;
|
|
430
|
+
if (mergedAudit.updatedAt) {
|
|
431
|
+
fields.push({
|
|
432
|
+
name: 'updatedAt',
|
|
433
|
+
type: 'DateTime',
|
|
434
|
+
attributes: ['@updatedAt']
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
if (mergedAudit.createdAt) {
|
|
438
|
+
fields.push({
|
|
439
|
+
name: 'createdAt',
|
|
440
|
+
type: 'DateTime',
|
|
441
|
+
attributes: ['@default(now())']
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
if (mergedAudit.createdById && authModelName) {
|
|
445
|
+
fields.push({
|
|
446
|
+
name: 'createdBy',
|
|
447
|
+
type: `${authModelName}?`,
|
|
448
|
+
attributes: [
|
|
449
|
+
`@relation("${modelName}CreatedBy${authModelName}", fields: [createdById], references: [id])`
|
|
450
|
+
]
|
|
451
|
+
});
|
|
452
|
+
fields.push({
|
|
453
|
+
name: 'createdById',
|
|
454
|
+
type: 'Int?'
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
return fields;
|
|
458
|
+
}
|
|
459
|
+
function createIndexSchema(index) {
|
|
460
|
+
const indexes = [];
|
|
461
|
+
if (!index || index.length === 0) {
|
|
462
|
+
return indexes;
|
|
463
|
+
}
|
|
464
|
+
for (const idx of index) {
|
|
465
|
+
const indexValue = (0, common_1.isArray)(idx) ? `[${idx.join(', ')}]` : idx;
|
|
466
|
+
const indexExpression = `@@index(${indexValue})`;
|
|
467
|
+
if (!indexes.includes(indexExpression)) {
|
|
468
|
+
indexes.push(indexExpression);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return indexes;
|
|
472
|
+
}
|
|
473
|
+
function databaseType() {
|
|
474
|
+
return (0, common_1.resolveDatabaseType)(common_1.config.DATABASE_TYPE, common_1.config.DATABASE_URL);
|
|
475
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ResourceModel } from '@appweaver/common';
|
|
2
|
+
/**
|
|
3
|
+
* Generates TypeScript type definitions for the given resource models and writes them to a specified directory.
|
|
4
|
+
*
|
|
5
|
+
* @param {Record<string, ResourceModel>} models - A set of resource models for which TypeScript types will be generated.
|
|
6
|
+
* @param {string} typesPath - The relative path to the directory where the generated types will be written.
|
|
7
|
+
* @param {boolean} [quiet=false] - If true, suppresses detailed logging during the operation.
|
|
8
|
+
* @return {Promise<number>} A promise that resolves to a status code: `0` for success, `2` for failure.
|
|
9
|
+
*/
|
|
10
|
+
export declare function generateTypes(models: Record<string, ResourceModel>, typesPath: string, quiet?: boolean): Promise<number>;
|