@ingenyus/swarm-wasp 0.1.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 +229 -21
- package/dist/.tsbuildinfo +1 -1
- package/dist/common/filesystem.d.ts +0 -14
- package/dist/common/filesystem.d.ts.map +1 -1
- package/dist/common/filesystem.js +123 -0
- package/dist/common/index.d.ts +0 -1
- package/dist/common/index.d.ts.map +1 -1
- package/dist/common/index.js +366 -0
- package/dist/common/prisma.js +140 -0
- package/dist/common/schemas.d.ts +8 -42
- package/dist/common/schemas.d.ts.map +1 -1
- package/dist/common/schemas.js +54 -0
- package/dist/common/templates.js +52 -0
- package/dist/generators/action/action-generator.d.ts +16 -29
- package/dist/generators/action/action-generator.d.ts.map +1 -1
- package/dist/generators/action/action-generator.js +1425 -0
- package/dist/generators/action/index.js +1425 -0
- package/dist/generators/action/schema.d.ts +11 -23
- package/dist/generators/action/schema.d.ts.map +1 -1
- package/dist/generators/action/schema.js +115 -0
- package/dist/generators/api/api-generator.d.ts +19 -26
- package/dist/generators/api/api-generator.d.ts.map +1 -1
- package/dist/generators/api/api-generator.js +1104 -0
- package/dist/generators/api/index.js +1104 -0
- package/dist/generators/api/schema.d.ts +13 -21
- package/dist/generators/api/schema.d.ts.map +1 -1
- package/dist/generators/api/schema.js +117 -0
- package/dist/generators/api-namespace/api-namespace-generator.d.ts +10 -17
- package/dist/generators/api-namespace/api-namespace-generator.d.ts.map +1 -1
- package/dist/generators/api-namespace/api-namespace-generator.js +1028 -0
- package/dist/generators/api-namespace/index.js +1028 -0
- package/dist/generators/api-namespace/schema.d.ts +4 -12
- package/dist/generators/api-namespace/schema.d.ts.map +1 -1
- package/dist/generators/api-namespace/schema.js +89 -0
- package/dist/generators/base/{entity-generator.base.d.ts → component-generator.base.d.ts} +9 -9
- package/dist/generators/base/component-generator.base.d.ts.map +1 -0
- package/dist/generators/base/component-generator.base.js +931 -0
- package/dist/generators/base/index.d.ts +1 -1
- package/dist/generators/base/index.d.ts.map +1 -1
- package/dist/generators/base/index.js +1330 -0
- package/dist/generators/base/operation-generator.base.d.ts +12 -3
- package/dist/generators/base/operation-generator.base.d.ts.map +1 -1
- package/dist/generators/base/operation-generator.base.js +1331 -0
- package/dist/generators/base/wasp-generator.base.d.ts +2 -1
- package/dist/generators/base/wasp-generator.base.d.ts.map +1 -1
- package/dist/generators/base/wasp-generator.base.js +706 -0
- package/dist/generators/config/config-generator.d.ts +7 -4
- package/dist/generators/config/config-generator.d.ts.map +1 -1
- package/dist/generators/config/config-generator.js +0 -0
- package/dist/generators/config/index.js +596 -0
- package/dist/generators/config/wasp-config-generator.d.ts +1 -1
- package/dist/generators/config/wasp-config-generator.d.ts.map +1 -1
- package/dist/generators/config/wasp-config-generator.js +596 -0
- package/dist/generators/crud/crud-generator.d.ts +34 -22
- package/dist/generators/crud/crud-generator.d.ts.map +1 -1
- package/dist/generators/crud/crud-generator.js +1550 -0
- package/dist/generators/crud/index.js +1550 -0
- package/dist/generators/crud/schema.d.ts +25 -18
- package/dist/generators/crud/schema.d.ts.map +1 -1
- package/dist/generators/crud/schema.js +133 -0
- package/dist/generators/feature/feature-generator.d.ts +20 -0
- package/dist/generators/feature/feature-generator.d.ts.map +1 -0
- package/dist/generators/feature/feature-generator.js +765 -0
- package/dist/generators/feature/index.d.ts +2 -0
- package/dist/generators/feature/index.d.ts.map +1 -0
- package/dist/generators/feature/index.js +765 -0
- package/dist/generators/feature/schema.d.ts +5 -0
- package/dist/generators/feature/schema.d.ts.map +1 -0
- package/dist/generators/feature/schema.js +86 -0
- package/dist/generators/index.d.ts +1 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2211 -0
- package/dist/generators/job/index.js +1099 -0
- package/dist/generators/job/job-generator.d.ts +12 -23
- package/dist/generators/job/job-generator.d.ts.map +1 -1
- package/dist/generators/job/job-generator.js +1099 -0
- package/dist/generators/job/schema.d.ts +6 -18
- package/dist/generators/job/schema.d.ts.map +1 -1
- package/dist/generators/job/schema.js +152 -0
- package/dist/generators/query/index.js +1425 -0
- package/dist/generators/query/query-generator.d.ts +16 -29
- package/dist/generators/query/query-generator.d.ts.map +1 -1
- package/dist/generators/query/query-generator.js +1425 -0
- package/dist/generators/query/schema.d.ts +11 -23
- package/dist/generators/query/schema.d.ts.map +1 -1
- package/dist/generators/query/schema.js +115 -0
- package/dist/generators/route/index.js +1038 -0
- package/dist/generators/route/route-generator.d.ts +11 -20
- package/dist/generators/route/route-generator.d.ts.map +1 -1
- package/dist/generators/route/route-generator.js +1038 -0
- package/dist/generators/route/schema.d.ts +5 -15
- package/dist/generators/route/schema.d.ts.map +1 -1
- package/dist/generators/route/schema.js +90 -0
- package/dist/index.d.ts +2 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1980 -2115
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/wasp.d.ts +3 -0
- package/dist/plugins/wasp.d.ts.map +1 -0
- package/dist/types/constants.d.ts +4 -22
- package/dist/types/constants.d.ts.map +1 -1
- package/dist/types/constants.js +8 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +8 -2
- package/dist/wasp-config/app.d.ts +2 -1
- package/dist/wasp-config/app.d.ts.map +1 -1
- package/dist/wasp-config/app.js +357 -0
- package/dist/wasp-config/index.js +357 -0
- package/dist/wasp-config/stubs/index.js +48 -0
- package/package.json +5 -14
- package/dist/common/plugin.d.ts +0 -2
- package/dist/common/plugin.d.ts.map +0 -1
- package/dist/generators/args.types.d.ts +0 -85
- package/dist/generators/args.types.d.ts.map +0 -1
- package/dist/generators/base/entity-generator.base.d.ts.map +0 -1
- package/dist/generators/feature-directory/feature-directory-generator.d.ts +0 -18
- package/dist/generators/feature-directory/feature-directory-generator.d.ts.map +0 -1
- package/dist/generators/feature-directory/index.d.ts +0 -2
- package/dist/generators/feature-directory/index.d.ts.map +0 -1
- package/dist/generators/feature-directory/schema.d.ts +0 -8
- package/dist/generators/feature-directory/schema.d.ts.map +0 -1
- package/dist/plugin.d.ts +0 -6
- package/dist/plugin.d.ts.map +0 -1
- /package/dist/generators/{feature-directory → feature}/templates/feature.wasp.eta +0 -0
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
// src/common/filesystem.ts
|
|
2
|
-
import { toPascalCase, validateFeaturePath } from "@ingenyus/swarm";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
6
|
-
|
|
7
1
|
// src/types/constants.ts
|
|
8
2
|
var PLUGIN_NAME = "wasp";
|
|
9
3
|
var OPERATION_TYPES = ["query", "action"];
|
|
10
|
-
var
|
|
4
|
+
var API_HTTP_METHODS = [
|
|
5
|
+
"ALL",
|
|
6
|
+
"GET",
|
|
7
|
+
"POST",
|
|
8
|
+
"PUT",
|
|
9
|
+
"DELETE"
|
|
10
|
+
];
|
|
11
11
|
var OPERATIONS = {
|
|
12
12
|
CREATE: "create",
|
|
13
13
|
UPDATE: "update",
|
|
@@ -57,7 +57,20 @@ var CONFIG_TYPES = {
|
|
|
57
57
|
CRUD: "Crud"
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
// src/generators/base/component-generator.base.ts
|
|
61
|
+
import {
|
|
62
|
+
hasHelperMethodCall,
|
|
63
|
+
logger as singletonLogger4,
|
|
64
|
+
toCamelCase,
|
|
65
|
+
toKebabCase as toKebabCase2,
|
|
66
|
+
validateFeaturePath as validateFeaturePath3
|
|
67
|
+
} from "@ingenyus/swarm";
|
|
68
|
+
import path6 from "path";
|
|
69
|
+
|
|
60
70
|
// src/common/filesystem.ts
|
|
71
|
+
import { toPascalCase, validateFeaturePath } from "@ingenyus/swarm";
|
|
72
|
+
import fs from "fs";
|
|
73
|
+
import path from "path";
|
|
61
74
|
var realFileSystem = {
|
|
62
75
|
readFileSync: fs.readFileSync,
|
|
63
76
|
writeFileSync: fs.writeFileSync,
|
|
@@ -102,13 +115,6 @@ function ensureDirectoryExists(fileSystem, dir) {
|
|
|
102
115
|
fileSystem.mkdirSync(dir, { recursive: true });
|
|
103
116
|
}
|
|
104
117
|
}
|
|
105
|
-
function featureExists(fileSystem, featurePath) {
|
|
106
|
-
return fileSystem.existsSync(getFeatureDir(fileSystem, featurePath));
|
|
107
|
-
}
|
|
108
|
-
function getConfigDir(fileSystem) {
|
|
109
|
-
const waspRoot = findWaspRoot(fileSystem);
|
|
110
|
-
return path.join(waspRoot, "config");
|
|
111
|
-
}
|
|
112
118
|
function normaliseFeaturePath(featurePath) {
|
|
113
119
|
const segments = validateFeaturePath(featurePath);
|
|
114
120
|
const normalisedSegments = [];
|
|
@@ -131,81 +137,21 @@ function getFeatureImportPath(featurePath) {
|
|
|
131
137
|
const segments = validateFeaturePath(featurePath);
|
|
132
138
|
return segments.join("/");
|
|
133
139
|
}
|
|
134
|
-
function getFeatureTargetDir(fileSystem, featurePath, type) {
|
|
135
|
-
validateFeaturePath(featurePath);
|
|
136
|
-
const normalisedPath = normaliseFeaturePath(featurePath);
|
|
137
|
-
const featureDir = getFeatureDir(fileSystem, normalisedPath);
|
|
138
|
-
const typeKey = type.toLowerCase();
|
|
139
|
-
const typeDirectory = TYPE_DIRECTORIES[typeKey];
|
|
140
|
-
const targetDirectory = path.join(featureDir, typeDirectory);
|
|
141
|
-
const importDirectory = `@src/${normalisedPath}/${typeDirectory}`;
|
|
142
|
-
return { targetDirectory, importDirectory };
|
|
143
|
-
}
|
|
144
140
|
function getRouteNameFromPath(routePath) {
|
|
145
141
|
const lastSegment = routePath.split("/").filter(Boolean).pop() || "index";
|
|
146
142
|
const cleanSegment = lastSegment.replace(/[:*]/g, "");
|
|
147
143
|
return `${toPascalCase(cleanSegment)}Page`;
|
|
148
144
|
}
|
|
149
|
-
function getAppRootDir(fileSystem) {
|
|
150
|
-
const currentFile = fileURLToPath(import.meta.url);
|
|
151
|
-
let currentDir = path.dirname(currentFile);
|
|
152
|
-
while (currentDir !== path.dirname(currentDir)) {
|
|
153
|
-
const dirName = path.basename(currentDir);
|
|
154
|
-
if (dirName === "src" || dirName === "dist") {
|
|
155
|
-
return currentDir;
|
|
156
|
-
}
|
|
157
|
-
const packageJsonPath = path.join(currentDir, "package.json");
|
|
158
|
-
if (fileSystem.existsSync(packageJsonPath)) {
|
|
159
|
-
const srcPath = path.join(currentDir, "src");
|
|
160
|
-
const distPath = path.join(currentDir, "dist");
|
|
161
|
-
if (fileSystem.existsSync(distPath)) {
|
|
162
|
-
return distPath;
|
|
163
|
-
} else if (fileSystem.existsSync(srcPath)) {
|
|
164
|
-
return srcPath;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
currentDir = path.dirname(currentDir);
|
|
168
|
-
}
|
|
169
|
-
throw new Error("Could not determine application root directory");
|
|
170
|
-
}
|
|
171
|
-
function getTemplatesDir(fileSystem) {
|
|
172
|
-
const appRoot = getAppRootDir(fileSystem);
|
|
173
|
-
return path.join(appRoot, "templates");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// src/common/plugin.ts
|
|
177
|
-
import path2 from "path";
|
|
178
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
179
|
-
function getPluginVersion() {
|
|
180
|
-
const __dirname = path2.dirname(fileURLToPath2(import.meta.url));
|
|
181
|
-
let currentDir = __dirname;
|
|
182
|
-
while (currentDir !== path2.dirname(currentDir)) {
|
|
183
|
-
const packageJsonPath = path2.join(currentDir, "package.json");
|
|
184
|
-
if (realFileSystem.existsSync(packageJsonPath)) {
|
|
185
|
-
try {
|
|
186
|
-
const packageJson = JSON.parse(
|
|
187
|
-
realFileSystem.readFileSync(packageJsonPath, "utf8")
|
|
188
|
-
);
|
|
189
|
-
if (packageJson.name === "@ingenyus/swarm-wasp") {
|
|
190
|
-
return packageJson.version;
|
|
191
|
-
}
|
|
192
|
-
} catch (e) {
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
currentDir = path2.dirname(currentDir);
|
|
196
|
-
}
|
|
197
|
-
return "0.1.0";
|
|
198
|
-
}
|
|
199
145
|
|
|
200
146
|
// src/common/prisma.ts
|
|
201
147
|
import {
|
|
202
148
|
getSchema
|
|
203
149
|
} from "@mrleebo/prisma-ast";
|
|
204
150
|
import fs2 from "fs";
|
|
205
|
-
import
|
|
151
|
+
import path2 from "path";
|
|
206
152
|
async function getEntityMetadata(modelName) {
|
|
207
153
|
try {
|
|
208
|
-
const schemaPath =
|
|
154
|
+
const schemaPath = path2.join(process.cwd(), "schema.prisma");
|
|
209
155
|
const schemaContent = fs2.readFileSync(schemaPath, "utf8");
|
|
210
156
|
const schema9 = getSchema(schemaContent);
|
|
211
157
|
const model = schema9.list?.find(
|
|
@@ -326,97 +272,61 @@ function getPrismaToTsType(type) {
|
|
|
326
272
|
}
|
|
327
273
|
|
|
328
274
|
// src/common/schemas.ts
|
|
329
|
-
import {
|
|
275
|
+
import { commandRegistry } from "@ingenyus/swarm";
|
|
330
276
|
import { z } from "zod";
|
|
331
277
|
var commonSchemas = {
|
|
332
|
-
feature:
|
|
333
|
-
description: "The feature directory this
|
|
334
|
-
|
|
278
|
+
feature: z.string().min(1, "Feature is required").meta({
|
|
279
|
+
description: "The feature directory this component will be generated in"
|
|
280
|
+
}).register(commandRegistry, {
|
|
335
281
|
shortName: "f",
|
|
336
282
|
examples: ["root", "auth", "dashboard/users"],
|
|
337
|
-
helpText:
|
|
283
|
+
helpText: "Can be nested as a logical or relative path, e.g. 'dashboard/users' or 'features/dashboard/features/users'"
|
|
338
284
|
}),
|
|
339
|
-
name:
|
|
340
|
-
description: "The name of the generated resource",
|
|
341
|
-
friendlyName: "Name",
|
|
285
|
+
name: z.string().min(1, "Name is required").meta({ description: "The name of the generated component" }).register(commandRegistry, {
|
|
342
286
|
shortName: "n",
|
|
343
287
|
examples: ["users", "task"],
|
|
344
288
|
helpText: "Will be used for generated files and configuration entries"
|
|
345
289
|
}),
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
290
|
+
target: z.string().min(1, "Target directory is required").meta({ description: "The target path of the generated directory" }).register(commandRegistry, {
|
|
291
|
+
shortName: "t",
|
|
292
|
+
examples: ["dashboard/users", "features/dashboard/features/users"],
|
|
293
|
+
helpText: "A logical or relative path, e.g. 'dashboard/users' or 'features/dashboard/features/users'"
|
|
294
|
+
}),
|
|
295
|
+
path: z.string().min(1, "Path is required").meta({ description: "The path that this component will be accessible at" }).register(commandRegistry, {
|
|
349
296
|
shortName: "p",
|
|
350
297
|
examples: ["/api/users/:id", "/api/products"],
|
|
351
|
-
helpText:
|
|
298
|
+
helpText: "Supports Express-style placeholders, e.g. '/api/users/:id'"
|
|
299
|
+
}),
|
|
300
|
+
dataType: z.string().min(1, "Data type is required").meta({ description: "The data type/model name for this operation" }).register(commandRegistry, {
|
|
301
|
+
shortName: "d",
|
|
302
|
+
examples: ["User", "Product", "Task"],
|
|
303
|
+
helpText: "The Wasp entity or model name this operation will interact with"
|
|
352
304
|
}),
|
|
353
|
-
entities:
|
|
354
|
-
description: "The Wasp entities that
|
|
355
|
-
|
|
305
|
+
entities: z.array(z.string()).optional().meta({
|
|
306
|
+
description: "The Wasp entities that will be available to this component (optional)"
|
|
307
|
+
}).register(commandRegistry, {
|
|
356
308
|
shortName: "e",
|
|
357
|
-
examples: ["User,
|
|
358
|
-
helpText: "
|
|
309
|
+
examples: ["User", "User Task"],
|
|
310
|
+
helpText: "An array of Wasp entity names"
|
|
359
311
|
}),
|
|
360
|
-
force:
|
|
361
|
-
description: "Force overwrite of existing files and configuration entries (optional)"
|
|
362
|
-
|
|
312
|
+
force: z.boolean().optional().meta({
|
|
313
|
+
description: "Force overwrite of existing files and configuration entries (optional)"
|
|
314
|
+
}).register(commandRegistry, {
|
|
363
315
|
shortName: "F",
|
|
364
316
|
helpText: "CAUTION: Will overwrite existing files and configuration entries with current parameters"
|
|
365
317
|
}),
|
|
366
|
-
auth:
|
|
367
|
-
description: "Require authentication for this
|
|
368
|
-
|
|
318
|
+
auth: z.boolean().optional().meta({
|
|
319
|
+
description: "Require authentication for this component (optional)"
|
|
320
|
+
}).register(commandRegistry, {
|
|
369
321
|
shortName: "a",
|
|
370
322
|
helpText: "Will generate authentication checks"
|
|
371
323
|
})
|
|
372
324
|
};
|
|
373
|
-
var getTypedArrayValidator = (validValues) => {
|
|
374
|
-
return (input) => {
|
|
375
|
-
if (!input) return true;
|
|
376
|
-
const values = input.split(",").map((s) => s.trim()).filter(Boolean);
|
|
377
|
-
const normalisedValues = validValues.map((value) => value.toLowerCase());
|
|
378
|
-
for (const value of values) {
|
|
379
|
-
const normalisedValue = value.toLowerCase();
|
|
380
|
-
if (!normalisedValues.includes(normalisedValue)) {
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return true;
|
|
385
|
-
};
|
|
386
|
-
};
|
|
387
|
-
var getTypedArrayTransformer = (validValues) => {
|
|
388
|
-
return (input) => {
|
|
389
|
-
if (!input) return void 0;
|
|
390
|
-
return input.split(",").map((s) => s.trim()).filter(Boolean).map((value) => {
|
|
391
|
-
const normalisedValue = value.toLowerCase();
|
|
392
|
-
const validValue = validValues.find(
|
|
393
|
-
(val) => val.toLowerCase() === normalisedValue
|
|
394
|
-
);
|
|
395
|
-
return validValue;
|
|
396
|
-
});
|
|
397
|
-
};
|
|
398
|
-
};
|
|
399
|
-
var getTypedValueValidator = (validValues) => {
|
|
400
|
-
return (value) => {
|
|
401
|
-
const normalisedValue = value.toLowerCase();
|
|
402
|
-
const normalisedValidOps = validValues.map((val) => val.toLowerCase());
|
|
403
|
-
return normalisedValidOps.includes(normalisedValue);
|
|
404
|
-
};
|
|
405
|
-
};
|
|
406
|
-
var getTypedValueTransformer = (validValues) => {
|
|
407
|
-
return (value) => {
|
|
408
|
-
const normalisedValue = value.toLowerCase();
|
|
409
|
-
const validValue = validValues.find(
|
|
410
|
-
(val) => val.toLowerCase() === normalisedValue
|
|
411
|
-
);
|
|
412
|
-
return validValue;
|
|
413
|
-
};
|
|
414
|
-
};
|
|
415
325
|
|
|
416
326
|
// src/common/templates.ts
|
|
417
327
|
import { toKebabCase } from "@ingenyus/swarm";
|
|
418
328
|
import { Eta } from "eta";
|
|
419
|
-
import
|
|
329
|
+
import path3 from "path";
|
|
420
330
|
var TemplateUtility = class {
|
|
421
331
|
constructor(fileSystem) {
|
|
422
332
|
this.fileSystem = fileSystem;
|
|
@@ -424,14 +334,14 @@ var TemplateUtility = class {
|
|
|
424
334
|
processTemplate(templatePath, replacements) {
|
|
425
335
|
const declarations = Object.keys(replacements).map((key) => `${key}=it.${key}`).join(", ");
|
|
426
336
|
const functionHeader = declarations ? `const ${declarations};` : void 0;
|
|
427
|
-
const templateDir =
|
|
337
|
+
const templateDir = path3.dirname(templatePath);
|
|
428
338
|
const eta = new Eta({
|
|
429
339
|
autoTrim: false,
|
|
430
340
|
autoEscape: false,
|
|
431
341
|
views: templateDir,
|
|
432
342
|
functionHeader
|
|
433
343
|
});
|
|
434
|
-
const templateName =
|
|
344
|
+
const templateName = path3.basename(templatePath).replace(/\.eta$/, "");
|
|
435
345
|
if (this.fileSystem.existsSync(templatePath)) {
|
|
436
346
|
return eta.render(templateName, replacements);
|
|
437
347
|
} else {
|
|
@@ -449,11 +359,11 @@ var TemplateUtility = class {
|
|
|
449
359
|
resolveTemplatePath(relativePath, generatorName, currentFileUrl) {
|
|
450
360
|
const generatorDirName = toKebabCase(generatorName);
|
|
451
361
|
const currentFilePath = new URL(currentFileUrl).pathname;
|
|
452
|
-
const currentFileDir =
|
|
453
|
-
const currentFileName =
|
|
362
|
+
const currentFileDir = path3.dirname(currentFilePath);
|
|
363
|
+
const currentFileName = path3.basename(currentFilePath);
|
|
454
364
|
const isInstalledPackage = currentFileDir.includes("node_modules") && currentFileDir.endsWith("/dist") && currentFileName === "index.js";
|
|
455
|
-
const startDir = isInstalledPackage ? currentFileDir :
|
|
456
|
-
return
|
|
365
|
+
const startDir = isInstalledPackage ? currentFileDir : path3.dirname(path3.dirname(currentFileDir));
|
|
366
|
+
return path3.join(
|
|
457
367
|
startDir,
|
|
458
368
|
"generators",
|
|
459
369
|
generatorDirName,
|
|
@@ -463,2212 +373,2199 @@ var TemplateUtility = class {
|
|
|
463
373
|
}
|
|
464
374
|
};
|
|
465
375
|
|
|
466
|
-
// src/
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
swarmVersion: "0.1.0",
|
|
473
|
-
generators: [
|
|
474
|
-
new ActionGenerator(),
|
|
475
|
-
new ApiGenerator(),
|
|
476
|
-
new ApiNamespaceGenerator(),
|
|
477
|
-
new CrudGenerator(),
|
|
478
|
-
new FeatureDirectoryGenerator(),
|
|
479
|
-
new JobGenerator(),
|
|
480
|
-
new QueryGenerator(),
|
|
481
|
-
new RouteGenerator()
|
|
482
|
-
]
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
var _apiPlugin = null;
|
|
486
|
-
function getWaspPlugin() {
|
|
487
|
-
if (!_apiPlugin) {
|
|
488
|
-
_apiPlugin = createWaspPlugin();
|
|
489
|
-
}
|
|
490
|
-
return _apiPlugin;
|
|
491
|
-
}
|
|
492
|
-
var wasp = getWaspPlugin;
|
|
493
|
-
|
|
494
|
-
// src/wasp-config/app.ts
|
|
495
|
-
import fs3 from "fs";
|
|
376
|
+
// src/generators/feature/feature-generator.ts
|
|
377
|
+
import {
|
|
378
|
+
handleFatalError as handleFatalError2,
|
|
379
|
+
logger as singletonLogger3,
|
|
380
|
+
validateFeaturePath as validateFeaturePath2
|
|
381
|
+
} from "@ingenyus/swarm";
|
|
496
382
|
import path5 from "path";
|
|
383
|
+
|
|
384
|
+
// src/generators/base/wasp-generator.base.ts
|
|
497
385
|
import {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
386
|
+
GeneratorBase,
|
|
387
|
+
logger as singletonLogger2,
|
|
388
|
+
SwarmConfigManager,
|
|
389
|
+
TemplateResolver
|
|
390
|
+
} from "@ingenyus/swarm";
|
|
391
|
+
|
|
392
|
+
// src/generators/config/wasp-config-generator.ts
|
|
393
|
+
import {
|
|
394
|
+
handleFatalError,
|
|
395
|
+
parseHelperMethodDefinition,
|
|
396
|
+
logger as singletonLogger
|
|
397
|
+
} from "@ingenyus/swarm";
|
|
398
|
+
import path4 from "path";
|
|
399
|
+
var WaspConfigGenerator = class {
|
|
400
|
+
constructor(logger = singletonLogger, fileSystem = realFileSystem) {
|
|
401
|
+
this.logger = logger;
|
|
402
|
+
this.fileSystem = fileSystem;
|
|
403
|
+
this.templateUtility = new TemplateUtility(fileSystem);
|
|
503
404
|
}
|
|
405
|
+
path = path4;
|
|
406
|
+
templateUtility;
|
|
504
407
|
/**
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
* @
|
|
509
|
-
* @param config The base configuration for the application
|
|
510
|
-
* @returns An initialized Swarm instance
|
|
408
|
+
* Gets the template path for feature config templates.
|
|
409
|
+
* Feature config templates are located in the feature generator's templates directory.
|
|
410
|
+
* @param templateName - The name of the template file (e.g., 'feature.wasp.eta')
|
|
411
|
+
* @returns The full path to the template file
|
|
511
412
|
*/
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
auth(authConfig) {
|
|
519
|
-
super.auth(authConfig);
|
|
520
|
-
return this;
|
|
521
|
-
}
|
|
522
|
-
client(clientConfig) {
|
|
523
|
-
super.client(clientConfig);
|
|
524
|
-
return this;
|
|
525
|
-
}
|
|
526
|
-
db(dbConfig) {
|
|
527
|
-
super.db(dbConfig);
|
|
528
|
-
return this;
|
|
529
|
-
}
|
|
530
|
-
emailSender(emailSenderConfig) {
|
|
531
|
-
super.emailSender(emailSenderConfig);
|
|
532
|
-
return this;
|
|
533
|
-
}
|
|
534
|
-
job(name, jobConfig) {
|
|
535
|
-
super.job(name, jobConfig);
|
|
536
|
-
return this;
|
|
537
|
-
}
|
|
538
|
-
query(name, queryConfig) {
|
|
539
|
-
super.query(name, queryConfig);
|
|
540
|
-
return this;
|
|
541
|
-
}
|
|
542
|
-
route(name, routeConfig) {
|
|
543
|
-
super.route(name, routeConfig);
|
|
544
|
-
return this;
|
|
545
|
-
}
|
|
546
|
-
api(name, apiConfig) {
|
|
547
|
-
super.api(name, apiConfig);
|
|
548
|
-
return this;
|
|
549
|
-
}
|
|
550
|
-
apiNamespace(name, apiNamespaceConfig) {
|
|
551
|
-
super.apiNamespace(name, apiNamespaceConfig);
|
|
552
|
-
return this;
|
|
553
|
-
}
|
|
554
|
-
crud(name, crudConfig) {
|
|
555
|
-
super.crud(name, crudConfig);
|
|
556
|
-
return this;
|
|
557
|
-
}
|
|
558
|
-
action(name, actionConfig) {
|
|
559
|
-
super.action(name, actionConfig);
|
|
560
|
-
return this;
|
|
413
|
+
getTemplatePath(templateName) {
|
|
414
|
+
return this.templateUtility.resolveTemplatePath(
|
|
415
|
+
templateName,
|
|
416
|
+
"feature",
|
|
417
|
+
import.meta.url
|
|
418
|
+
);
|
|
561
419
|
}
|
|
562
420
|
/**
|
|
563
|
-
*
|
|
564
|
-
* @param
|
|
565
|
-
* @param name Route name, e.g. "DashboardRoute"
|
|
566
|
-
* @param options Route configuration options
|
|
421
|
+
* Generate a TypeScript Wasp config file in a feature directory
|
|
422
|
+
* @param featurePath - The feature directory path
|
|
567
423
|
*/
|
|
568
|
-
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
};
|
|
586
|
-
super.route(name, routeConfig);
|
|
587
|
-
return this;
|
|
424
|
+
generate(featurePath) {
|
|
425
|
+
const featureDir = getFeatureDir(this.fileSystem, featurePath);
|
|
426
|
+
if (!this.fileSystem.existsSync(featureDir)) {
|
|
427
|
+
this.fileSystem.mkdirSync(featureDir, { recursive: true });
|
|
428
|
+
}
|
|
429
|
+
const templatePath = this.getTemplatePath("feature.wasp.eta");
|
|
430
|
+
if (!this.fileSystem.existsSync(templatePath)) {
|
|
431
|
+
this.logger.error(`Template not found: ${templatePath}`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const configFilePath = path4.join(featureDir, `feature.wasp.ts`);
|
|
435
|
+
if (this.fileSystem.existsSync(configFilePath)) {
|
|
436
|
+
this.logger.warn(`Feature config already exists: ${configFilePath}`);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
this.fileSystem.copyFileSync(templatePath, configFilePath);
|
|
440
|
+
this.logger.success(`Generated feature config: ${configFilePath}`);
|
|
588
441
|
}
|
|
589
442
|
/**
|
|
590
|
-
*
|
|
591
|
-
* @param
|
|
592
|
-
* @param
|
|
593
|
-
* @
|
|
443
|
+
* Updates or creates a feature configuration file with a pre-built declaration.
|
|
444
|
+
* @param featurePath - The path to the feature
|
|
445
|
+
* @param declaration - The pre-built declaration string to add or update
|
|
446
|
+
* @returns The updated feature configuration file
|
|
594
447
|
*/
|
|
595
|
-
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
"
|
|
600
|
-
|
|
448
|
+
update(featurePath, declaration) {
|
|
449
|
+
const configDir = getFeatureDir(this.fileSystem, featurePath);
|
|
450
|
+
const configFilePath = path4.join(configDir, `feature.wasp.ts`);
|
|
451
|
+
if (!this.fileSystem.existsSync(configFilePath)) {
|
|
452
|
+
const templatePath = this.getTemplatePath("feature.wasp.eta");
|
|
453
|
+
if (!this.fileSystem.existsSync(templatePath)) {
|
|
454
|
+
handleFatalError(`Feature config template not found: ${templatePath}`);
|
|
455
|
+
}
|
|
456
|
+
this.fileSystem.copyFileSync(templatePath, configFilePath);
|
|
457
|
+
}
|
|
458
|
+
let content = this.fileSystem.readFileSync(configFilePath, "utf8");
|
|
459
|
+
content = this.normaliseSemicolons(content);
|
|
460
|
+
const parsed = parseHelperMethodDefinition(declaration);
|
|
461
|
+
if (!parsed) {
|
|
462
|
+
handleFatalError(`Could not parse definition: ${declaration}`);
|
|
463
|
+
return content;
|
|
464
|
+
}
|
|
465
|
+
const { methodName } = parsed;
|
|
466
|
+
const hadExistingDefinitions = this.hasExistingDefinitions(
|
|
467
|
+
content,
|
|
468
|
+
methodName
|
|
601
469
|
);
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
name
|
|
470
|
+
content = this.removeExistingDefinition(content, declaration);
|
|
471
|
+
const hasExistingDefinitions = this.hasExistingDefinitions(
|
|
472
|
+
content,
|
|
473
|
+
methodName
|
|
607
474
|
);
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
475
|
+
const lines = content.split("\n");
|
|
476
|
+
const configureFunctionStart = lines.findIndex(
|
|
477
|
+
(line) => line.trim().startsWith("export default function")
|
|
478
|
+
);
|
|
479
|
+
if (configureFunctionStart === -1) {
|
|
480
|
+
handleFatalError("Could not find configure function in feature config");
|
|
481
|
+
}
|
|
482
|
+
const appLineIndex = lines.findIndex(
|
|
483
|
+
(line, index) => index > configureFunctionStart && line.trim() === "app"
|
|
484
|
+
);
|
|
485
|
+
if (appLineIndex === -1) {
|
|
486
|
+
const insertIndex = configureFunctionStart + 1;
|
|
487
|
+
const itemsToInsert = [" app"];
|
|
488
|
+
const comment = this.getMethodComment(methodName);
|
|
489
|
+
itemsToInsert.push(` ${comment}`);
|
|
490
|
+
itemsToInsert.push(declaration.trimEnd());
|
|
491
|
+
lines.splice(insertIndex, 0, ...itemsToInsert);
|
|
492
|
+
} else {
|
|
493
|
+
const { insertIndex, addComment } = this.findGroupInsertionPoint(
|
|
494
|
+
lines,
|
|
495
|
+
methodName,
|
|
496
|
+
declaration,
|
|
497
|
+
hadExistingDefinitions || hasExistingDefinitions
|
|
498
|
+
);
|
|
499
|
+
const newLines = this.insertWithSpacing(
|
|
500
|
+
lines,
|
|
501
|
+
declaration,
|
|
502
|
+
insertIndex,
|
|
503
|
+
methodName,
|
|
504
|
+
addComment
|
|
505
|
+
);
|
|
506
|
+
const normalisedContent2 = this.normaliseSemicolons(newLines.join("\n"));
|
|
507
|
+
this.fileSystem.writeFileSync(configFilePath, normalisedContent2);
|
|
508
|
+
return configFilePath;
|
|
509
|
+
}
|
|
510
|
+
const normalisedContent = this.normaliseSemicolons(lines.join("\n"));
|
|
511
|
+
this.fileSystem.writeFileSync(configFilePath, normalisedContent);
|
|
512
|
+
return configFilePath;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Determines the insertion index for a method name based on alphabetical ordering
|
|
516
|
+
* of existing groups in the configuration file.
|
|
517
|
+
* @param groups - Object containing existing method groups
|
|
518
|
+
* @param methodName - The method name to find insertion index for
|
|
519
|
+
* @returns The insertion index for the method name
|
|
628
520
|
*/
|
|
629
|
-
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
521
|
+
getInsertionIndexForMethod(groups, methodName) {
|
|
522
|
+
const existingMethods = Object.keys(groups).filter(
|
|
523
|
+
(method) => groups[method].length > 0
|
|
524
|
+
);
|
|
525
|
+
const allMethods = [...existingMethods, methodName].sort();
|
|
526
|
+
return allMethods.indexOf(methodName);
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Gets the comment text for a method type.
|
|
530
|
+
* @param methodName The method name (e.g., 'addApi')
|
|
531
|
+
* @returns The comment text for the method type
|
|
532
|
+
*/
|
|
533
|
+
getMethodComment(methodName) {
|
|
534
|
+
const entityName = methodName.startsWith("add") ? methodName.slice(3) : methodName;
|
|
535
|
+
return `// ${entityName} definitions`;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Finds the correct insertion point for a new configuration item.
|
|
539
|
+
* @param lines - Array of file lines
|
|
540
|
+
* @param methodName - The method name (e.g., 'addApi')
|
|
541
|
+
* @param definition - The definition string to parse for item name
|
|
542
|
+
* @returns Object with insertion index and whether to add a comment
|
|
543
|
+
*/
|
|
544
|
+
findGroupInsertionPoint(lines, methodName, definition, hasExistingDefinitionsOfType) {
|
|
545
|
+
const appLineIndex = lines.findIndex((line) => line.trim() === "app");
|
|
546
|
+
if (appLineIndex === -1) {
|
|
547
|
+
return { insertIndex: appLineIndex + 1, addComment: false };
|
|
548
|
+
}
|
|
549
|
+
const methodCalls = [];
|
|
550
|
+
for (let i = appLineIndex + 1; i < lines.length; i++) {
|
|
551
|
+
const line = lines[i].trim();
|
|
552
|
+
if (line.startsWith(".") && line.includes("(")) {
|
|
553
|
+
let methodCallContent = line;
|
|
554
|
+
let j = i;
|
|
555
|
+
let closingParenCount = 0;
|
|
556
|
+
let foundClosingParen = false;
|
|
557
|
+
for (let k = 0; k < methodCallContent.length; k++) {
|
|
558
|
+
if (methodCallContent[k] === "(") closingParenCount++;
|
|
559
|
+
if (methodCallContent[k] === ")") closingParenCount--;
|
|
560
|
+
if (closingParenCount === 0 && methodCallContent[k] === ")") {
|
|
561
|
+
foundClosingParen = true;
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
while (!foundClosingParen && j < lines.length - 1) {
|
|
566
|
+
j++;
|
|
567
|
+
methodCallContent += " " + lines[j].trim();
|
|
568
|
+
for (let k = 0; k < lines[j].length; k++) {
|
|
569
|
+
if (lines[j][k] === "(") closingParenCount++;
|
|
570
|
+
if (lines[j][k] === ")") closingParenCount--;
|
|
571
|
+
if (closingParenCount === 0 && lines[j][k] === ")") {
|
|
572
|
+
foundClosingParen = true;
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
const match = methodCallContent.match(
|
|
578
|
+
/\.(\w+)\([^,]+,\s*['"`]([^'"`]+)['"`]/
|
|
641
579
|
);
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
580
|
+
if (match) {
|
|
581
|
+
methodCalls.push({
|
|
582
|
+
lineIndex: i,
|
|
583
|
+
endLineIndex: j,
|
|
584
|
+
methodName: match[1],
|
|
585
|
+
itemName: match[2]
|
|
586
|
+
});
|
|
587
|
+
}
|
|
647
588
|
}
|
|
648
|
-
|
|
649
|
-
};
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
getAll: processOperationOptions("getAll", options.getAll),
|
|
654
|
-
get: processOperationOptions("get", options.get),
|
|
655
|
-
create: processOperationOptions("create", options.create),
|
|
656
|
-
update: processOperationOptions("update", options.update),
|
|
657
|
-
delete: processOperationOptions("delete", options.delete)
|
|
589
|
+
}
|
|
590
|
+
const groups = {};
|
|
591
|
+
methodCalls.forEach((call) => {
|
|
592
|
+
if (!groups[call.methodName]) {
|
|
593
|
+
groups[call.methodName] = [];
|
|
658
594
|
}
|
|
595
|
+
groups[call.methodName].push({
|
|
596
|
+
lineIndex: call.lineIndex,
|
|
597
|
+
endLineIndex: call.endLineIndex,
|
|
598
|
+
itemName: call.itemName
|
|
599
|
+
});
|
|
659
600
|
});
|
|
660
|
-
|
|
601
|
+
const targetGroup = groups[methodName] || [];
|
|
602
|
+
if (targetGroup.length === 0) {
|
|
603
|
+
const targetGroupIndex = this.getInsertionIndexForMethod(
|
|
604
|
+
groups,
|
|
605
|
+
methodName
|
|
606
|
+
);
|
|
607
|
+
const existingMethods = Object.keys(groups).filter((method) => groups[method].length > 0).sort();
|
|
608
|
+
for (let i = targetGroupIndex; i < existingMethods.length; i++) {
|
|
609
|
+
const groupMethod = existingMethods[i];
|
|
610
|
+
if (groups[groupMethod] && groups[groupMethod].length > 0) {
|
|
611
|
+
const firstItem = groups[groupMethod][0];
|
|
612
|
+
let insertIndex = firstItem.lineIndex;
|
|
613
|
+
for (let j = firstItem.lineIndex - 1; j > appLineIndex; j--) {
|
|
614
|
+
const line = lines[j].trim();
|
|
615
|
+
if (line.startsWith("//") && line.includes("definitions")) {
|
|
616
|
+
insertIndex = j;
|
|
617
|
+
break;
|
|
618
|
+
} else if (line.startsWith(".") || line === "") {
|
|
619
|
+
continue;
|
|
620
|
+
} else {
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return { insertIndex, addComment: !hasExistingDefinitionsOfType };
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
for (let i = targetGroupIndex - 1; i >= 0; i--) {
|
|
628
|
+
const groupMethod = existingMethods[i];
|
|
629
|
+
if (groups[groupMethod] && groups[groupMethod].length > 0) {
|
|
630
|
+
const lastItem2 = groups[groupMethod][groups[groupMethod].length - 1];
|
|
631
|
+
return {
|
|
632
|
+
insertIndex: lastItem2.endLineIndex + 1,
|
|
633
|
+
addComment: !hasExistingDefinitionsOfType
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
insertIndex: appLineIndex + 1,
|
|
639
|
+
addComment: !hasExistingDefinitionsOfType
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
const parsed = parseHelperMethodDefinition(definition);
|
|
643
|
+
if (!parsed) {
|
|
644
|
+
return { insertIndex: appLineIndex + 1, addComment: false };
|
|
645
|
+
}
|
|
646
|
+
const { firstParam: itemName } = parsed;
|
|
647
|
+
for (let i = 0; i < targetGroup.length; i++) {
|
|
648
|
+
if (itemName.localeCompare(targetGroup[i].itemName) < 0) {
|
|
649
|
+
return { insertIndex: targetGroup[i].lineIndex, addComment: false };
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const lastItem = targetGroup[targetGroup.length - 1];
|
|
653
|
+
return { insertIndex: lastItem.endLineIndex + 1, addComment: false };
|
|
661
654
|
}
|
|
662
655
|
/**
|
|
663
|
-
*
|
|
664
|
-
* @param
|
|
665
|
-
* @param
|
|
666
|
-
* @param
|
|
656
|
+
* Inserts a definition with optional comment header.
|
|
657
|
+
* @param lines - Array of file lines
|
|
658
|
+
* @param declaration - The declaration to insert
|
|
659
|
+
* @param insertIndex - The index where to insert
|
|
660
|
+
* @param methodName - The method name for comment generation
|
|
661
|
+
* @param addComment - Whether to add a comment before the declaration
|
|
662
|
+
* @returns The modified lines array
|
|
667
663
|
*/
|
|
668
|
-
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
importPath,
|
|
678
|
-
options.entities,
|
|
679
|
-
options.auth
|
|
680
|
-
);
|
|
681
|
-
super.action(name, config);
|
|
682
|
-
return this;
|
|
664
|
+
insertWithSpacing(lines, declaration, insertIndex, methodName, addComment = false) {
|
|
665
|
+
const newLines = [...lines];
|
|
666
|
+
if (addComment) {
|
|
667
|
+
const comment = this.getMethodComment(methodName);
|
|
668
|
+
newLines.splice(insertIndex, 0, ` ${comment}`);
|
|
669
|
+
insertIndex += 1;
|
|
670
|
+
}
|
|
671
|
+
newLines.splice(insertIndex, 0, declaration.trimEnd());
|
|
672
|
+
return newLines;
|
|
683
673
|
}
|
|
684
674
|
/**
|
|
685
|
-
*
|
|
686
|
-
* @param
|
|
687
|
-
* @param
|
|
688
|
-
* @
|
|
675
|
+
* Checks if there are any existing definitions of a specific type in the content.
|
|
676
|
+
* @param content - The file content to search
|
|
677
|
+
* @param methodName - The method name to check for (e.g., 'addJob', 'addApi')
|
|
678
|
+
* @returns true if there are existing definitions of this type, false otherwise
|
|
689
679
|
*/
|
|
690
|
-
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
name,
|
|
699
|
-
importPath,
|
|
700
|
-
options.entities,
|
|
701
|
-
options.auth
|
|
702
|
-
);
|
|
703
|
-
super.query(name, config);
|
|
704
|
-
return this;
|
|
680
|
+
hasExistingDefinitions(content, methodName) {
|
|
681
|
+
const lines = content.split("\n");
|
|
682
|
+
for (const line of lines) {
|
|
683
|
+
if (line.trim().startsWith(`.${methodName}(`)) {
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return false;
|
|
705
688
|
}
|
|
706
689
|
/**
|
|
707
|
-
*
|
|
708
|
-
*
|
|
709
|
-
* @param
|
|
710
|
-
* @param
|
|
690
|
+
* Removes an existing definition from the content by finding the helper method call
|
|
691
|
+
* and removing the entire method call block.
|
|
692
|
+
* @param content - The file content
|
|
693
|
+
* @param definition - The new definition to find the existing one from
|
|
694
|
+
* @returns The content with the existing definition removed
|
|
711
695
|
*/
|
|
712
|
-
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
);
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
696
|
+
removeExistingDefinition(content, definition) {
|
|
697
|
+
const parsed = parseHelperMethodDefinition(definition);
|
|
698
|
+
if (!parsed) {
|
|
699
|
+
return content;
|
|
700
|
+
}
|
|
701
|
+
const { methodName, firstParam } = parsed;
|
|
702
|
+
let contentLines = content.split("\n");
|
|
703
|
+
let openingLineIndex = -1;
|
|
704
|
+
for (let i = 0; i < contentLines.length; i++) {
|
|
705
|
+
const line = contentLines[i];
|
|
706
|
+
if (line.trim().startsWith(`.${methodName}(`)) {
|
|
707
|
+
if (firstParam && line.includes(firstParam)) {
|
|
708
|
+
openingLineIndex = i;
|
|
709
|
+
break;
|
|
725
710
|
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (openingLineIndex === -1) {
|
|
714
|
+
return content;
|
|
715
|
+
}
|
|
716
|
+
let closingLineIndex = -1;
|
|
717
|
+
let parenCount = 0;
|
|
718
|
+
let braceCount = 0;
|
|
719
|
+
let foundOpening = false;
|
|
720
|
+
for (let i = openingLineIndex; i < contentLines.length; i++) {
|
|
721
|
+
const line = contentLines[i];
|
|
722
|
+
for (const char of line) {
|
|
723
|
+
if (char === "(") {
|
|
724
|
+
parenCount++;
|
|
725
|
+
foundOpening = true;
|
|
726
|
+
} else if (char === ")") {
|
|
727
|
+
parenCount--;
|
|
728
|
+
if (foundOpening && parenCount === 0 && braceCount === 0) {
|
|
729
|
+
closingLineIndex = i;
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
} else if (char === "{") {
|
|
733
|
+
braceCount++;
|
|
734
|
+
} else if (char === "}") {
|
|
735
|
+
braceCount--;
|
|
732
736
|
}
|
|
733
737
|
}
|
|
734
|
-
|
|
735
|
-
|
|
738
|
+
if (closingLineIndex !== -1) {
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (closingLineIndex === -1) {
|
|
743
|
+
this.logger.warn(
|
|
744
|
+
"Could not find closing parenthesis for existing definition"
|
|
745
|
+
);
|
|
746
|
+
return content;
|
|
747
|
+
}
|
|
748
|
+
contentLines.splice(
|
|
749
|
+
openingLineIndex,
|
|
750
|
+
closingLineIndex - openingLineIndex + 1
|
|
751
|
+
);
|
|
752
|
+
return contentLines.join("\n");
|
|
736
753
|
}
|
|
737
754
|
/**
|
|
738
|
-
*
|
|
739
|
-
* @param
|
|
740
|
-
* @param
|
|
741
|
-
* @
|
|
755
|
+
* Adds a definition to the content by finding the appropriate place to insert it.
|
|
756
|
+
* @param content - The current file content
|
|
757
|
+
* @param definition - The definition to add
|
|
758
|
+
* @returns The updated content with the new definition
|
|
742
759
|
*/
|
|
743
|
-
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
760
|
+
addDefinitionToContent(content, definition) {
|
|
761
|
+
const lines = content.split("\n");
|
|
762
|
+
const lastLineIndex = lines.length - 1;
|
|
763
|
+
let insertIndex = lastLineIndex;
|
|
764
|
+
for (let i = lastLineIndex; i >= 0; i--) {
|
|
765
|
+
const line = lines[i].trim();
|
|
766
|
+
if (line && !line.startsWith("}")) {
|
|
767
|
+
insertIndex = i;
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
lines.splice(insertIndex + 1, 0, ` ${definition}`);
|
|
772
|
+
return lines.join("\n");
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Normalises semicolons in the config file by removing them from method chain calls
|
|
776
|
+
* while preserving them in other contexts (imports, declarations, etc.).
|
|
777
|
+
* @param content - The file content to normalise
|
|
778
|
+
* @returns The normalised content
|
|
779
|
+
*/
|
|
780
|
+
normaliseSemicolons(content) {
|
|
781
|
+
const lines = content.split("\n");
|
|
782
|
+
const configureFunctionStart = lines.findIndex(
|
|
783
|
+
(line) => line.trim().startsWith("export default function")
|
|
749
784
|
);
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
785
|
+
if (configureFunctionStart === -1) {
|
|
786
|
+
return content;
|
|
787
|
+
}
|
|
788
|
+
const appLineIndex = lines.findIndex(
|
|
789
|
+
(line, index) => index > configureFunctionStart && line.trim().startsWith("app")
|
|
790
|
+
);
|
|
791
|
+
if (appLineIndex === -1) {
|
|
792
|
+
return content;
|
|
793
|
+
}
|
|
794
|
+
let braceCount = 0;
|
|
795
|
+
let functionEndIndex = lines.length - 1;
|
|
796
|
+
for (let i = configureFunctionStart; i < lines.length; i++) {
|
|
797
|
+
const line = lines[i];
|
|
798
|
+
for (const char of line) {
|
|
799
|
+
if (char === "{") braceCount++;
|
|
800
|
+
if (char === "}") {
|
|
801
|
+
braceCount--;
|
|
802
|
+
if (braceCount === 0) {
|
|
803
|
+
functionEndIndex = i;
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
755
807
|
}
|
|
756
|
-
|
|
757
|
-
|
|
808
|
+
if (braceCount === 0 && i > configureFunctionStart) {
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
let lastMethodCallIndex = -1;
|
|
813
|
+
for (let i = appLineIndex + 1; i < functionEndIndex; i++) {
|
|
814
|
+
const line = lines[i];
|
|
815
|
+
const trimmed = line.trim();
|
|
816
|
+
if ((trimmed.endsWith(")") || trimmed.endsWith(");")) && !trimmed.startsWith("//")) {
|
|
817
|
+
lines[i] = line.replace(/;\s*$/, "");
|
|
818
|
+
lastMethodCallIndex = i;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
if (lastMethodCallIndex !== -1 && !lines[lastMethodCallIndex].trim().endsWith(";")) {
|
|
822
|
+
lines[lastMethodCallIndex] = lines[lastMethodCallIndex] + ";";
|
|
823
|
+
}
|
|
824
|
+
return lines.join("\n");
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
// src/generators/base/wasp-generator.base.ts
|
|
829
|
+
var WaspGeneratorBase = class extends GeneratorBase {
|
|
830
|
+
constructor(fileSystem = realFileSystem, logger = singletonLogger2) {
|
|
831
|
+
super(fileSystem, logger);
|
|
832
|
+
this.fileSystem = fileSystem;
|
|
833
|
+
this.logger = logger;
|
|
834
|
+
this.configGenerator = new WaspConfigGenerator(logger, fileSystem);
|
|
835
|
+
this.templateUtility = new TemplateUtility(fileSystem);
|
|
836
|
+
this.templateResolver = new TemplateResolver(fileSystem);
|
|
837
|
+
}
|
|
838
|
+
configGenerator;
|
|
839
|
+
templateUtility;
|
|
840
|
+
templateResolver;
|
|
841
|
+
swarmConfig;
|
|
842
|
+
configLoaded = false;
|
|
843
|
+
// Plugin name from swarm.config.json
|
|
844
|
+
pluginName = PLUGIN_NAME;
|
|
845
|
+
async loadSwarmConfig() {
|
|
846
|
+
if (this.configLoaded) return;
|
|
847
|
+
const configManager = new SwarmConfigManager();
|
|
848
|
+
this.swarmConfig = await configManager.loadConfig();
|
|
849
|
+
this.configLoaded = true;
|
|
850
|
+
}
|
|
851
|
+
async getCustomTemplateDir() {
|
|
852
|
+
await this.loadSwarmConfig();
|
|
853
|
+
return this.swarmConfig?.templateDirectory;
|
|
758
854
|
}
|
|
759
855
|
/**
|
|
760
|
-
*
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
856
|
+
* Resolves template path with override support
|
|
857
|
+
*/
|
|
858
|
+
async getTemplatePath(templateName) {
|
|
859
|
+
const defaultPath = this.getDefaultTemplatePath(templateName);
|
|
860
|
+
const customPath = await this.getCustomTemplateDir();
|
|
861
|
+
if (!customPath) {
|
|
862
|
+
return defaultPath;
|
|
863
|
+
}
|
|
864
|
+
const { path: resolvedPath, isCustom } = this.templateResolver.resolveTemplatePath(
|
|
865
|
+
this.pluginName,
|
|
866
|
+
this.name,
|
|
867
|
+
templateName,
|
|
868
|
+
defaultPath,
|
|
869
|
+
customPath
|
|
870
|
+
);
|
|
871
|
+
if (isCustom) {
|
|
872
|
+
this.logger.info(`Using custom template: ${resolvedPath}`);
|
|
873
|
+
}
|
|
874
|
+
return resolvedPath;
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Processes a template and writes the result to a file
|
|
766
878
|
*/
|
|
767
|
-
|
|
768
|
-
const
|
|
769
|
-
|
|
879
|
+
async renderTemplateToFile(templateName, replacements, outputPath, readableFileType, force) {
|
|
880
|
+
const templatePath = await this.getTemplatePath(templateName);
|
|
881
|
+
const fileExists = this.checkFileExists(
|
|
882
|
+
outputPath,
|
|
883
|
+
force,
|
|
884
|
+
readableFileType
|
|
885
|
+
);
|
|
886
|
+
const content = this.templateUtility.processTemplate(
|
|
887
|
+
templatePath,
|
|
888
|
+
replacements
|
|
889
|
+
);
|
|
890
|
+
this.writeFile(outputPath, content, readableFileType, fileExists);
|
|
891
|
+
return fileExists;
|
|
770
892
|
}
|
|
771
893
|
/**
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
* @returns The plural form of the word
|
|
894
|
+
* Generic existence check with force flag handling
|
|
895
|
+
* Consolidates the pattern used in both file and config checks
|
|
775
896
|
*/
|
|
776
|
-
|
|
777
|
-
if (
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
return word + "es";
|
|
781
|
-
} else {
|
|
782
|
-
return word + "s";
|
|
897
|
+
checkExistence(exists, itemDescription, force, errorMessage) {
|
|
898
|
+
if (exists && !force) {
|
|
899
|
+
this.logger.error(`${itemDescription}. Use --force to overwrite`);
|
|
900
|
+
throw new Error(errorMessage || itemDescription);
|
|
783
901
|
}
|
|
902
|
+
return exists;
|
|
784
903
|
}
|
|
785
904
|
/**
|
|
786
|
-
*
|
|
905
|
+
* Checks if a file exists and handles force flag logic
|
|
787
906
|
*/
|
|
788
|
-
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const getAllFeatureFiles = (dir) => {
|
|
797
|
-
let results = [];
|
|
798
|
-
const list = fs3.readdirSync(dir, { withFileTypes: true });
|
|
799
|
-
for (const entry of list) {
|
|
800
|
-
const fullPath = path5.join(dir, entry.name);
|
|
801
|
-
if (entry.isDirectory()) {
|
|
802
|
-
results = results.concat(getAllFeatureFiles(fullPath));
|
|
803
|
-
} else if (entry.isFile() && entry.name.endsWith(".wasp.ts")) {
|
|
804
|
-
results.push(path5.relative(featuresDir, fullPath));
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
return results;
|
|
808
|
-
};
|
|
809
|
-
const featureFiles = getAllFeatureFiles(featuresDir);
|
|
810
|
-
for (const file of featureFiles) {
|
|
811
|
-
try {
|
|
812
|
-
const featureName = path5.dirname(file);
|
|
813
|
-
const modulePath = path5.join(
|
|
814
|
-
process.cwd(),
|
|
815
|
-
".wasp",
|
|
816
|
-
"src",
|
|
817
|
-
"features",
|
|
818
|
-
file.replace(".ts", ".js")
|
|
819
|
-
);
|
|
820
|
-
const module = await import(modulePath);
|
|
821
|
-
if (module.default) {
|
|
822
|
-
module.default(this, featureName);
|
|
823
|
-
}
|
|
824
|
-
} catch (error) {
|
|
825
|
-
console.error(`Failed to load feature module ${file}:`, error);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
return this;
|
|
907
|
+
checkFileExists(filePath, force, fileType) {
|
|
908
|
+
const fileExists = this.fileSystem.existsSync(filePath);
|
|
909
|
+
return this.checkExistence(
|
|
910
|
+
fileExists,
|
|
911
|
+
`${fileType} already exists: ${filePath}`,
|
|
912
|
+
force,
|
|
913
|
+
`${fileType} already exists`
|
|
914
|
+
);
|
|
829
915
|
}
|
|
830
916
|
/**
|
|
831
|
-
*
|
|
832
|
-
* @param name The operation name
|
|
833
|
-
* @param importPath Import path (excluding `@src/` prefix), e.g. "features/dashboard/server/queries/getTasks"
|
|
834
|
-
* @param entities Comma-separated list of entities (optional, defaults to datatype)
|
|
835
|
-
* @param auth Require authentication (optional)
|
|
917
|
+
* Safely writes a file with proper error handling and logging
|
|
836
918
|
*/
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
},
|
|
843
|
-
entities,
|
|
844
|
-
auth: auth || false
|
|
845
|
-
};
|
|
919
|
+
writeFile(filePath, content, fileType, fileExists) {
|
|
920
|
+
this.fileSystem.writeFileSync(filePath, content);
|
|
921
|
+
this.logger.success(
|
|
922
|
+
`${fileExists ? "Overwrote" : "Generated"} ${fileType}: ${filePath}`
|
|
923
|
+
);
|
|
846
924
|
}
|
|
847
925
|
};
|
|
848
926
|
|
|
849
|
-
// src/generators/
|
|
850
|
-
import {
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
toKebabCase as toKebabCase2,
|
|
855
|
-
validateFeaturePath as validateFeaturePath3
|
|
856
|
-
} from "@ingenyus/swarm";
|
|
857
|
-
import path8 from "path";
|
|
858
|
-
|
|
859
|
-
// src/generators/feature-directory/feature-directory-generator.ts
|
|
860
|
-
import {
|
|
861
|
-
handleFatalError as handleFatalError2,
|
|
862
|
-
SignaleLogger as SignaleLogger3,
|
|
863
|
-
validateFeaturePath as validateFeaturePath2
|
|
864
|
-
} from "@ingenyus/swarm";
|
|
865
|
-
import path7 from "path";
|
|
866
|
-
|
|
867
|
-
// src/generators/base/wasp-generator.base.ts
|
|
868
|
-
import {
|
|
869
|
-
GeneratorBase,
|
|
870
|
-
SignaleLogger as SignaleLogger2,
|
|
871
|
-
SwarmConfigManager,
|
|
872
|
-
TemplateResolver
|
|
873
|
-
} from "@ingenyus/swarm";
|
|
927
|
+
// src/generators/feature/schema.ts
|
|
928
|
+
import { z as z2 } from "zod";
|
|
929
|
+
var schema = z2.object({
|
|
930
|
+
target: commonSchemas.target
|
|
931
|
+
});
|
|
874
932
|
|
|
875
|
-
// src/generators/
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
SignaleLogger
|
|
880
|
-
} from "@ingenyus/swarm";
|
|
881
|
-
import path6 from "path";
|
|
882
|
-
var WaspConfigGenerator = class {
|
|
883
|
-
constructor(logger = new SignaleLogger(), fileSystem = realFileSystem) {
|
|
933
|
+
// src/generators/feature/feature-generator.ts
|
|
934
|
+
var FeatureGenerator = class extends WaspGeneratorBase {
|
|
935
|
+
constructor(logger = singletonLogger3, fileSystem = realFileSystem) {
|
|
936
|
+
super(fileSystem, logger);
|
|
884
937
|
this.logger = logger;
|
|
885
938
|
this.fileSystem = fileSystem;
|
|
886
|
-
this.
|
|
939
|
+
this.name = "feature";
|
|
940
|
+
this.description = "Generates a feature directory containing a Wasp configuration file";
|
|
887
941
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
* Feature config templates are located in the feature-directory generator's templates directory.
|
|
893
|
-
* @param templateName - The name of the template file (e.g., 'feature.wasp.eta')
|
|
894
|
-
* @returns The full path to the template file
|
|
895
|
-
*/
|
|
896
|
-
getTemplatePath(templateName) {
|
|
942
|
+
name;
|
|
943
|
+
description;
|
|
944
|
+
schema = schema;
|
|
945
|
+
getDefaultTemplatePath(templateName) {
|
|
897
946
|
return this.templateUtility.resolveTemplatePath(
|
|
898
947
|
templateName,
|
|
899
|
-
|
|
948
|
+
this.name,
|
|
900
949
|
import.meta.url
|
|
901
950
|
);
|
|
902
951
|
}
|
|
903
952
|
/**
|
|
904
|
-
*
|
|
905
|
-
* @param
|
|
906
|
-
*/
|
|
907
|
-
generate(featurePath) {
|
|
908
|
-
const featureDir = getFeatureDir(this.fileSystem, featurePath);
|
|
909
|
-
if (!this.fileSystem.existsSync(featureDir)) {
|
|
910
|
-
this.fileSystem.mkdirSync(featureDir, { recursive: true });
|
|
911
|
-
}
|
|
912
|
-
const templatePath = this.getTemplatePath("feature.wasp.eta");
|
|
913
|
-
if (!this.fileSystem.existsSync(templatePath)) {
|
|
914
|
-
this.logger.error(`Template not found: ${templatePath}`);
|
|
915
|
-
return;
|
|
916
|
-
}
|
|
917
|
-
const configFilePrefix = featurePath.split("/").at(-1);
|
|
918
|
-
const configFilePath = path6.join(featureDir, `${configFilePrefix}.wasp.ts`);
|
|
919
|
-
if (this.fileSystem.existsSync(configFilePath)) {
|
|
920
|
-
this.logger.warn(`Feature config already exists: ${configFilePath}`);
|
|
921
|
-
return;
|
|
922
|
-
}
|
|
923
|
-
this.fileSystem.copyFileSync(templatePath, configFilePath);
|
|
924
|
-
this.logger.success(`Generated feature config: ${configFilePath}`);
|
|
925
|
-
}
|
|
926
|
-
/**
|
|
927
|
-
* Updates or creates a feature configuration file with a pre-built declaration.
|
|
928
|
-
* @param featurePath - The path to the feature
|
|
929
|
-
* @param declaration - The pre-built declaration string to add or update
|
|
930
|
-
* @returns The updated feature configuration file
|
|
953
|
+
* Generates a feature directory containing a Wasp configuration file
|
|
954
|
+
* @param target - The target path of the generated directory
|
|
931
955
|
*/
|
|
932
|
-
|
|
933
|
-
const
|
|
934
|
-
const
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
956
|
+
async generate(args) {
|
|
957
|
+
const { target } = args;
|
|
958
|
+
const segments = validateFeaturePath2(target);
|
|
959
|
+
const normalisedPath = normaliseFeaturePath(target);
|
|
960
|
+
const sourceRoot = path5.join(findWaspRoot(this.fileSystem), "src");
|
|
961
|
+
if (segments.length > 1) {
|
|
962
|
+
const parentPath = segments.slice(0, -1).join("/");
|
|
963
|
+
const parentNormalisedPath = normaliseFeaturePath(parentPath);
|
|
964
|
+
const parentFeatureDir = path5.join(sourceRoot, parentNormalisedPath);
|
|
965
|
+
if (!this.fileSystem.existsSync(parentFeatureDir)) {
|
|
966
|
+
handleFatalError2(
|
|
967
|
+
`Parent feature '${parentPath}' does not exist. Please create it first.`
|
|
968
|
+
);
|
|
940
969
|
}
|
|
941
|
-
this.fileSystem.copyFileSync(templatePath, configFilePath);
|
|
942
|
-
}
|
|
943
|
-
let content = this.fileSystem.readFileSync(configFilePath, "utf8");
|
|
944
|
-
content = this.normaliseSemicolons(content);
|
|
945
|
-
const parsed = parseHelperMethodDefinition(declaration);
|
|
946
|
-
if (!parsed) {
|
|
947
|
-
handleFatalError(`Could not parse definition: ${declaration}`);
|
|
948
|
-
return content;
|
|
949
|
-
}
|
|
950
|
-
const { methodName } = parsed;
|
|
951
|
-
const hadExistingDefinitions = this.hasExistingDefinitions(
|
|
952
|
-
content,
|
|
953
|
-
methodName
|
|
954
|
-
);
|
|
955
|
-
content = this.removeExistingDefinition(content, declaration);
|
|
956
|
-
const hasExistingDefinitions = this.hasExistingDefinitions(
|
|
957
|
-
content,
|
|
958
|
-
methodName
|
|
959
|
-
);
|
|
960
|
-
const lines = content.split("\n");
|
|
961
|
-
const configureFunctionStart = lines.findIndex(
|
|
962
|
-
(line) => line.trim().startsWith("export default function")
|
|
963
|
-
);
|
|
964
|
-
if (configureFunctionStart === -1) {
|
|
965
|
-
handleFatalError("Could not find configure function in feature config");
|
|
966
970
|
}
|
|
967
|
-
const
|
|
968
|
-
|
|
971
|
+
const featureDir = path5.join(sourceRoot, normalisedPath);
|
|
972
|
+
this.fileSystem.mkdirSync(featureDir, { recursive: true });
|
|
973
|
+
this.configGenerator.generate(normalisedPath);
|
|
974
|
+
this.logger.success(`Generated feature: ${normalisedPath}`);
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
// src/generators/base/component-generator.base.ts
|
|
979
|
+
var ComponentGeneratorBase = class extends WaspGeneratorBase {
|
|
980
|
+
constructor(logger = singletonLogger4, fileSystem = realFileSystem, featureDirectoryGenerator = new FeatureGenerator(logger, fileSystem)) {
|
|
981
|
+
super(fileSystem, logger);
|
|
982
|
+
this.logger = logger;
|
|
983
|
+
this.fileSystem = fileSystem;
|
|
984
|
+
this.featureDirectoryGenerator = featureDirectoryGenerator;
|
|
985
|
+
this.featureDirectoryGenerator = featureDirectoryGenerator;
|
|
986
|
+
}
|
|
987
|
+
getDefaultTemplatePath(templateName) {
|
|
988
|
+
return this.templateUtility.resolveTemplatePath(
|
|
989
|
+
templateName,
|
|
990
|
+
this.name,
|
|
991
|
+
import.meta.url
|
|
969
992
|
);
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
const
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
addComment
|
|
990
|
-
);
|
|
991
|
-
const normalisedContent2 = this.normaliseSemicolons(newLines.join("\n"));
|
|
992
|
-
this.fileSystem.writeFileSync(configFilePath, normalisedContent2);
|
|
993
|
-
return configFilePath;
|
|
993
|
+
}
|
|
994
|
+
get name() {
|
|
995
|
+
return toKebabCase2(this.componentType);
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Validates that the feature config file exists in the target or ancestor directories
|
|
999
|
+
*/
|
|
1000
|
+
validateFeatureConfig(featurePath) {
|
|
1001
|
+
const normalisedPath = normaliseFeaturePath(featurePath);
|
|
1002
|
+
const segments = normalisedPath.split("/");
|
|
1003
|
+
for (let i = segments.length; i > 0; i--) {
|
|
1004
|
+
const pathSegments = segments.slice(0, i);
|
|
1005
|
+
const currentPath = pathSegments.join("/");
|
|
1006
|
+
const featureName = pathSegments[pathSegments.length - 1];
|
|
1007
|
+
const featureDir = getFeatureDir(this.fileSystem, currentPath);
|
|
1008
|
+
const configPath = path6.join(featureDir, `feature.wasp.ts`);
|
|
1009
|
+
if (this.fileSystem.existsSync(configPath)) {
|
|
1010
|
+
return configPath;
|
|
1011
|
+
}
|
|
994
1012
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1013
|
+
this.logger.error(
|
|
1014
|
+
`Feature config file not found in '${normalisedPath}' or any ancestor directories`
|
|
1015
|
+
);
|
|
1016
|
+
this.logger.error(
|
|
1017
|
+
`Expected to find a feature.wasp.ts config file in one of the feature directories`
|
|
1018
|
+
);
|
|
1019
|
+
throw new Error("Feature config file not found");
|
|
998
1020
|
}
|
|
999
1021
|
/**
|
|
1000
|
-
*
|
|
1001
|
-
* of existing groups in the configuration file.
|
|
1002
|
-
* @param groups - Object containing existing method groups
|
|
1003
|
-
* @param methodName - The method name to find insertion index for
|
|
1004
|
-
* @returns The insertion index for the method name
|
|
1022
|
+
* Checks if a config item already exists in the feature config
|
|
1005
1023
|
*/
|
|
1006
|
-
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1024
|
+
checkConfigExists(configPath, methodName, itemName, force) {
|
|
1025
|
+
const configContent = this.fileSystem.readFileSync(configPath, "utf8");
|
|
1026
|
+
const configExists = hasHelperMethodCall(
|
|
1027
|
+
configContent,
|
|
1028
|
+
methodName,
|
|
1029
|
+
itemName
|
|
1030
|
+
);
|
|
1031
|
+
return this.checkExistence(
|
|
1032
|
+
configExists,
|
|
1033
|
+
`${methodName} config already exists in ${configPath}`,
|
|
1034
|
+
force,
|
|
1035
|
+
`${methodName} config already exists`
|
|
1009
1036
|
);
|
|
1010
|
-
const allMethods = [...existingMethods, methodName].sort();
|
|
1011
|
-
return allMethods.indexOf(methodName);
|
|
1012
1037
|
}
|
|
1013
1038
|
/**
|
|
1014
|
-
*
|
|
1015
|
-
* @param methodName The method name (e.g., 'addApi')
|
|
1016
|
-
* @returns The comment text for the method type
|
|
1039
|
+
* Updates the feature config with a new definition
|
|
1017
1040
|
*/
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1041
|
+
updateFeatureConfig(featurePath, definition, configPath, configExists, methodName) {
|
|
1042
|
+
this.configGenerator.update(featurePath, definition);
|
|
1043
|
+
this.logger.success(
|
|
1044
|
+
`${configExists ? "Updated" : "Added"} ${methodName} config in: ${configPath}`
|
|
1045
|
+
);
|
|
1021
1046
|
}
|
|
1022
1047
|
/**
|
|
1023
|
-
*
|
|
1024
|
-
*
|
|
1025
|
-
* @param methodName - The method name (e.g., 'addApi')
|
|
1026
|
-
* @param definition - The definition string to parse for item name
|
|
1027
|
-
* @returns Object with insertion index and whether to add a comment
|
|
1048
|
+
* Consolidated helper for updating config files with existence check
|
|
1049
|
+
* This replaces the duplicated updateConfigFile pattern in concrete generators
|
|
1028
1050
|
*/
|
|
1029
|
-
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
for (let k = 0; k < methodCallContent.length; k++) {
|
|
1043
|
-
if (methodCallContent[k] === "(") closingParenCount++;
|
|
1044
|
-
if (methodCallContent[k] === ")") closingParenCount--;
|
|
1045
|
-
if (closingParenCount === 0 && methodCallContent[k] === ")") {
|
|
1046
|
-
foundClosingParen = true;
|
|
1047
|
-
break;
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
while (!foundClosingParen && j < lines.length - 1) {
|
|
1051
|
-
j++;
|
|
1052
|
-
methodCallContent += " " + lines[j].trim();
|
|
1053
|
-
for (let k = 0; k < lines[j].length; k++) {
|
|
1054
|
-
if (lines[j][k] === "(") closingParenCount++;
|
|
1055
|
-
if (lines[j][k] === ")") closingParenCount--;
|
|
1056
|
-
if (closingParenCount === 0 && lines[j][k] === ")") {
|
|
1057
|
-
foundClosingParen = true;
|
|
1058
|
-
break;
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
const match = methodCallContent.match(
|
|
1063
|
-
/\.(\w+)\([^,]+,\s*['"`]([^'"`]+)['"`]/
|
|
1064
|
-
);
|
|
1065
|
-
if (match) {
|
|
1066
|
-
methodCalls.push({
|
|
1067
|
-
lineIndex: i,
|
|
1068
|
-
endLineIndex: j,
|
|
1069
|
-
methodName: match[1],
|
|
1070
|
-
itemName: match[2]
|
|
1071
|
-
});
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
const groups = {};
|
|
1076
|
-
methodCalls.forEach((call) => {
|
|
1077
|
-
if (!groups[call.methodName]) {
|
|
1078
|
-
groups[call.methodName] = [];
|
|
1079
|
-
}
|
|
1080
|
-
groups[call.methodName].push({
|
|
1081
|
-
lineIndex: call.lineIndex,
|
|
1082
|
-
endLineIndex: call.endLineIndex,
|
|
1083
|
-
itemName: call.itemName
|
|
1084
|
-
});
|
|
1085
|
-
});
|
|
1086
|
-
const targetGroup = groups[methodName] || [];
|
|
1087
|
-
if (targetGroup.length === 0) {
|
|
1088
|
-
const targetGroupIndex = this.getInsertionIndexForMethod(
|
|
1089
|
-
groups,
|
|
1051
|
+
updateConfigWithCheck(configPath, methodName, entityName, definition, featurePath, force) {
|
|
1052
|
+
const configExists = this.checkConfigExists(
|
|
1053
|
+
configPath,
|
|
1054
|
+
methodName,
|
|
1055
|
+
entityName,
|
|
1056
|
+
force
|
|
1057
|
+
);
|
|
1058
|
+
if (!configExists || force) {
|
|
1059
|
+
this.updateFeatureConfig(
|
|
1060
|
+
featurePath,
|
|
1061
|
+
definition,
|
|
1062
|
+
configPath,
|
|
1063
|
+
configExists,
|
|
1090
1064
|
methodName
|
|
1091
1065
|
);
|
|
1092
|
-
const existingMethods = Object.keys(groups).filter((method) => groups[method].length > 0).sort();
|
|
1093
|
-
for (let i = targetGroupIndex; i < existingMethods.length; i++) {
|
|
1094
|
-
const groupMethod = existingMethods[i];
|
|
1095
|
-
if (groups[groupMethod] && groups[groupMethod].length > 0) {
|
|
1096
|
-
const firstItem = groups[groupMethod][0];
|
|
1097
|
-
let insertIndex = firstItem.lineIndex;
|
|
1098
|
-
for (let j = firstItem.lineIndex - 1; j > appLineIndex; j--) {
|
|
1099
|
-
const line = lines[j].trim();
|
|
1100
|
-
if (line.startsWith("//") && line.includes("definitions")) {
|
|
1101
|
-
insertIndex = j;
|
|
1102
|
-
break;
|
|
1103
|
-
} else if (line.startsWith(".") || line === "") {
|
|
1104
|
-
continue;
|
|
1105
|
-
} else {
|
|
1106
|
-
break;
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
return { insertIndex, addComment: !hasExistingDefinitionsOfType };
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
for (let i = targetGroupIndex - 1; i >= 0; i--) {
|
|
1113
|
-
const groupMethod = existingMethods[i];
|
|
1114
|
-
if (groups[groupMethod] && groups[groupMethod].length > 0) {
|
|
1115
|
-
const lastItem2 = groups[groupMethod][groups[groupMethod].length - 1];
|
|
1116
|
-
return {
|
|
1117
|
-
insertIndex: lastItem2.endLineIndex + 1,
|
|
1118
|
-
addComment: !hasExistingDefinitionsOfType
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
return {
|
|
1123
|
-
insertIndex: appLineIndex + 1,
|
|
1124
|
-
addComment: !hasExistingDefinitionsOfType
|
|
1125
|
-
};
|
|
1126
|
-
}
|
|
1127
|
-
const parsed = parseHelperMethodDefinition(definition);
|
|
1128
|
-
if (!parsed) {
|
|
1129
|
-
return { insertIndex: appLineIndex + 1, addComment: false };
|
|
1130
|
-
}
|
|
1131
|
-
const { firstParam: itemName } = parsed;
|
|
1132
|
-
for (let i = 0; i < targetGroup.length; i++) {
|
|
1133
|
-
if (itemName.localeCompare(targetGroup[i].itemName) < 0) {
|
|
1134
|
-
return { insertIndex: targetGroup[i].lineIndex, addComment: false };
|
|
1135
|
-
}
|
|
1136
1066
|
}
|
|
1137
|
-
const lastItem = targetGroup[targetGroup.length - 1];
|
|
1138
|
-
return { insertIndex: lastItem.endLineIndex + 1, addComment: false };
|
|
1139
1067
|
}
|
|
1140
1068
|
/**
|
|
1141
|
-
*
|
|
1142
|
-
* @param
|
|
1143
|
-
* @param
|
|
1144
|
-
* @param
|
|
1145
|
-
* @
|
|
1146
|
-
* @param addComment - Whether to add a comment before the declaration
|
|
1147
|
-
* @returns The modified lines array
|
|
1069
|
+
* Gets the appropriate directory for a feature based on its path.
|
|
1070
|
+
* @param fileSystem - The filesystem abstraction
|
|
1071
|
+
* @param featurePath - The full feature path
|
|
1072
|
+
* @param type - The type of file being generated
|
|
1073
|
+
* @returns The target directory and import path
|
|
1148
1074
|
*/
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1075
|
+
getFeatureTargetDir(fileSystem, featurePath, type) {
|
|
1076
|
+
validateFeaturePath3(featurePath);
|
|
1077
|
+
const normalisedPath = normaliseFeaturePath(featurePath);
|
|
1078
|
+
const featureDir = getFeatureDir(fileSystem, normalisedPath);
|
|
1079
|
+
const typeKey = type.toLowerCase();
|
|
1080
|
+
const typeDirectory = TYPE_DIRECTORIES[typeKey];
|
|
1081
|
+
const targetDirectory = path6.join(featureDir, typeDirectory);
|
|
1082
|
+
const importDirectory = `@src/${normalisedPath}/${typeDirectory}`;
|
|
1083
|
+
return { targetDirectory, importDirectory };
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Ensures a target directory exists and returns its path
|
|
1087
|
+
*/
|
|
1088
|
+
ensureTargetDirectory(featurePath, type) {
|
|
1089
|
+
const { targetDirectory, importDirectory } = this.getFeatureTargetDir(
|
|
1090
|
+
this.fileSystem,
|
|
1091
|
+
featurePath,
|
|
1092
|
+
type
|
|
1093
|
+
);
|
|
1094
|
+
ensureDirectoryExists(this.fileSystem, targetDirectory);
|
|
1095
|
+
return { targetDirectory, importDirectory };
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Generate middleware file for API or API namespace
|
|
1099
|
+
*/
|
|
1100
|
+
async generateMiddlewareFile(targetFile, name, force) {
|
|
1101
|
+
const replacements = {
|
|
1102
|
+
name,
|
|
1103
|
+
middlewareType: toCamelCase(this.componentType || "")
|
|
1104
|
+
};
|
|
1105
|
+
await this.renderTemplateToFile(
|
|
1106
|
+
"middleware.eta",
|
|
1107
|
+
replacements,
|
|
1108
|
+
targetFile,
|
|
1109
|
+
"Middleware file",
|
|
1110
|
+
force
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
|
|
1115
|
+
// src/generators/base/operation-generator.base.ts
|
|
1116
|
+
import {
|
|
1117
|
+
capitalise,
|
|
1118
|
+
getPlural,
|
|
1119
|
+
handleFatalError as handleFatalError3,
|
|
1120
|
+
toPascalCase as toPascalCase2
|
|
1121
|
+
} from "@ingenyus/swarm";
|
|
1122
|
+
var OperationGeneratorBase = class extends ComponentGeneratorBase {
|
|
1123
|
+
/**
|
|
1124
|
+
* Gets the operation name based on operation type and model name.
|
|
1125
|
+
*/
|
|
1126
|
+
getOperationName(operation, modelName, customName) {
|
|
1127
|
+
if (customName) {
|
|
1128
|
+
return customName;
|
|
1129
|
+
}
|
|
1130
|
+
switch (operation) {
|
|
1131
|
+
case OPERATIONS.GETALL:
|
|
1132
|
+
return `getAll${getPlural(modelName)}`;
|
|
1133
|
+
case OPERATIONS.GETFILTERED:
|
|
1134
|
+
return `getFiltered${getPlural(modelName)}`;
|
|
1135
|
+
default:
|
|
1136
|
+
return `${operation}${modelName}`;
|
|
1155
1137
|
}
|
|
1156
|
-
newLines.splice(insertIndex, 0, declaration.trimEnd());
|
|
1157
|
-
return newLines;
|
|
1158
1138
|
}
|
|
1159
1139
|
/**
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1162
|
-
*
|
|
1163
|
-
* @returns true if there are existing definitions of this type, false otherwise
|
|
1140
|
+
* Gets the template path for operation templates.
|
|
1141
|
+
* This method resolves operation templates to the operation generator's directory
|
|
1142
|
+
* instead of the current generator's directory.
|
|
1164
1143
|
*/
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
}
|
|
1172
|
-
return false;
|
|
1144
|
+
getOperationTemplatePath(templateName) {
|
|
1145
|
+
return this.templateUtility.resolveTemplatePath(
|
|
1146
|
+
templateName,
|
|
1147
|
+
"operation",
|
|
1148
|
+
import.meta.url
|
|
1149
|
+
);
|
|
1173
1150
|
}
|
|
1174
1151
|
/**
|
|
1175
|
-
*
|
|
1176
|
-
* and removing the entire method call block.
|
|
1177
|
-
* @param content - The file content
|
|
1178
|
-
* @param definition - The new definition to find the existing one from
|
|
1179
|
-
* @returns The content with the existing definition removed
|
|
1152
|
+
* Gets the TypeScript type name for an operation.
|
|
1180
1153
|
*/
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
if (firstParam && line.includes(firstParam)) {
|
|
1193
|
-
openingLineIndex = i;
|
|
1194
|
-
break;
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
if (openingLineIndex === -1) {
|
|
1199
|
-
return content;
|
|
1200
|
-
}
|
|
1201
|
-
let closingLineIndex = -1;
|
|
1202
|
-
let parenCount = 0;
|
|
1203
|
-
let braceCount = 0;
|
|
1204
|
-
let foundOpening = false;
|
|
1205
|
-
for (let i = openingLineIndex; i < contentLines.length; i++) {
|
|
1206
|
-
const line = contentLines[i];
|
|
1207
|
-
for (const char of line) {
|
|
1208
|
-
if (char === "(") {
|
|
1209
|
-
parenCount++;
|
|
1210
|
-
foundOpening = true;
|
|
1211
|
-
} else if (char === ")") {
|
|
1212
|
-
parenCount--;
|
|
1213
|
-
if (foundOpening && parenCount === 0 && braceCount === 0) {
|
|
1214
|
-
closingLineIndex = i;
|
|
1215
|
-
break;
|
|
1216
|
-
}
|
|
1217
|
-
} else if (char === "{") {
|
|
1218
|
-
braceCount++;
|
|
1219
|
-
} else if (char === "}") {
|
|
1220
|
-
braceCount--;
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
if (closingLineIndex !== -1) {
|
|
1224
|
-
break;
|
|
1154
|
+
getOperationTypeName(operation, modelName) {
|
|
1155
|
+
return toPascalCase2(this.getOperationName(operation, modelName));
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Generates import statements for an operation.
|
|
1159
|
+
*/
|
|
1160
|
+
generateImports(model, modelName, operation) {
|
|
1161
|
+
const imports = [];
|
|
1162
|
+
if (operation !== OPERATIONS.GETALL) {
|
|
1163
|
+
if (needsPrismaImport(model)) {
|
|
1164
|
+
imports.push('import { Prisma } from "@prisma/client";');
|
|
1225
1165
|
}
|
|
1166
|
+
imports.push(`import { ${modelName} } from "wasp/entities";`);
|
|
1226
1167
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
contentLines.splice(
|
|
1234
|
-
openingLineIndex,
|
|
1235
|
-
closingLineIndex - openingLineIndex + 1
|
|
1168
|
+
imports.push('import { HttpError } from "wasp/server";');
|
|
1169
|
+
imports.push(
|
|
1170
|
+
`import type { ${this.getOperationTypeName(
|
|
1171
|
+
operation,
|
|
1172
|
+
modelName
|
|
1173
|
+
)} } from "wasp/server/operations";`
|
|
1236
1174
|
);
|
|
1237
|
-
return
|
|
1175
|
+
return imports.join("\n");
|
|
1238
1176
|
}
|
|
1239
1177
|
/**
|
|
1240
|
-
*
|
|
1241
|
-
* @param content - The current file content
|
|
1242
|
-
* @param definition - The definition to add
|
|
1243
|
-
* @returns The updated content with the new definition
|
|
1178
|
+
* Gets the operation type ("query" or "action") for a given operation.
|
|
1244
1179
|
*/
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
const lastLineIndex = lines.length - 1;
|
|
1248
|
-
let insertIndex = lastLineIndex;
|
|
1249
|
-
for (let i = lastLineIndex; i >= 0; i--) {
|
|
1250
|
-
const line = lines[i].trim();
|
|
1251
|
-
if (line && !line.startsWith("}")) {
|
|
1252
|
-
insertIndex = i;
|
|
1253
|
-
break;
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
lines.splice(insertIndex + 1, 0, ` ${definition}`);
|
|
1257
|
-
return lines.join("\n");
|
|
1180
|
+
getOperationType(operation) {
|
|
1181
|
+
return operation === OPERATIONS.GETALL || operation === OPERATIONS.GET || operation === OPERATIONS.GETFILTERED ? "query" : "action";
|
|
1258
1182
|
}
|
|
1259
1183
|
/**
|
|
1260
|
-
*
|
|
1261
|
-
* while preserving them in other contexts (imports, declarations, etc.).
|
|
1262
|
-
* @param content - The file content to normalise
|
|
1263
|
-
* @returns The normalised content
|
|
1184
|
+
* Generates the operation components needed for file and config generation.
|
|
1264
1185
|
*/
|
|
1265
|
-
|
|
1266
|
-
const
|
|
1267
|
-
const
|
|
1268
|
-
|
|
1186
|
+
async generateOperationComponents(modelName, operation, auth = false, entities = [modelName], isCrudOverride = false, crudName = null, customName) {
|
|
1187
|
+
const model = await getEntityMetadata(modelName);
|
|
1188
|
+
const operationType = this.getOperationType(operation);
|
|
1189
|
+
const operationName = this.getOperationName(
|
|
1190
|
+
operation,
|
|
1191
|
+
modelName,
|
|
1192
|
+
customName
|
|
1269
1193
|
);
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1194
|
+
const operationCode = await this.generateOperationCode(
|
|
1195
|
+
model,
|
|
1196
|
+
operation,
|
|
1197
|
+
auth,
|
|
1198
|
+
isCrudOverride,
|
|
1199
|
+
crudName
|
|
1275
1200
|
);
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1201
|
+
const configEntry = {
|
|
1202
|
+
operationName,
|
|
1203
|
+
entities,
|
|
1204
|
+
authRequired: auth
|
|
1205
|
+
};
|
|
1206
|
+
return {
|
|
1207
|
+
operationCode,
|
|
1208
|
+
configEntry,
|
|
1209
|
+
operationType,
|
|
1210
|
+
operationName
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Generates the code for an operation.
|
|
1215
|
+
*/
|
|
1216
|
+
async generateOperationCode(model, operation, auth = false, isCrudOverride = false, crudName = null) {
|
|
1217
|
+
const operationType = this.getOperationType(operation);
|
|
1218
|
+
const templatePath = this.getOperationTemplatePath(`${operation}.eta`);
|
|
1219
|
+
const allFieldNames = model.fields.map((f) => f.name);
|
|
1220
|
+
const idFields = getIdFields(model);
|
|
1221
|
+
const requiredFields = getRequiredFields(model);
|
|
1222
|
+
const optionalFields = getOptionalFields(model);
|
|
1223
|
+
const jsonFields = getJsonFields(model);
|
|
1224
|
+
const pluralModelName = getPlural(model.name);
|
|
1225
|
+
const pluralModelNameLower = pluralModelName.toLowerCase();
|
|
1226
|
+
const modelNameLower = model.name.toLowerCase();
|
|
1227
|
+
const operationName = this.getOperationName(operation, model.name);
|
|
1228
|
+
const imports = isCrudOverride ? "" : this.generateImports(model, model.name, operation);
|
|
1229
|
+
const jsonTypeHandling = generateJsonTypeHandling(jsonFields);
|
|
1230
|
+
let typeParams = "";
|
|
1231
|
+
switch (operation) {
|
|
1232
|
+
case "create": {
|
|
1233
|
+
const pickRequired = generatePickType(
|
|
1234
|
+
model.name,
|
|
1235
|
+
requiredFields,
|
|
1236
|
+
allFieldNames
|
|
1237
|
+
);
|
|
1238
|
+
const partialOptional = generatePartialType(
|
|
1239
|
+
generatePickType(model.name, optionalFields, allFieldNames)
|
|
1240
|
+
);
|
|
1241
|
+
typeParams = `<${generateIntersectionType(pickRequired, partialOptional)}>`;
|
|
1242
|
+
break;
|
|
1292
1243
|
}
|
|
1293
|
-
|
|
1244
|
+
case "update": {
|
|
1245
|
+
const pickId = generatePickType(model.name, idFields, allFieldNames);
|
|
1246
|
+
const omitId = generateOmitType(model.name, idFields, allFieldNames);
|
|
1247
|
+
const partialRest = generatePartialType(omitId);
|
|
1248
|
+
typeParams = `<${generateIntersectionType(pickId, partialRest)}>`;
|
|
1294
1249
|
break;
|
|
1295
1250
|
}
|
|
1251
|
+
case "delete":
|
|
1252
|
+
case "get":
|
|
1253
|
+
typeParams = `<${generatePickType(model.name, idFields, allFieldNames)}>`;
|
|
1254
|
+
break;
|
|
1255
|
+
case "getAll":
|
|
1256
|
+
typeParams = `<void>`;
|
|
1257
|
+
break;
|
|
1258
|
+
case "getFiltered":
|
|
1259
|
+
typeParams = `<${generatePartialType(model.name)}>`;
|
|
1260
|
+
break;
|
|
1296
1261
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1262
|
+
const authCheck = auth ? ` if (!context.user) {
|
|
1263
|
+
throw new HttpError(401);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
` : "";
|
|
1267
|
+
let typeAnnotation = "";
|
|
1268
|
+
let satisfiesType = "";
|
|
1269
|
+
if (isCrudOverride && crudName) {
|
|
1270
|
+
const opCap = capitalise(operation);
|
|
1271
|
+
if (operationType === "action") {
|
|
1272
|
+
typeAnnotation = `: ${crudName}.${opCap}Action${typeParams}`;
|
|
1273
|
+
} else {
|
|
1274
|
+
typeAnnotation = "";
|
|
1275
|
+
}
|
|
1276
|
+
if (operationType === "query") {
|
|
1277
|
+
satisfiesType = `satisfies ${crudName}.${opCap}Query${typeParams}`;
|
|
1278
|
+
} else {
|
|
1279
|
+
satisfiesType = "";
|
|
1280
|
+
}
|
|
1281
|
+
} else {
|
|
1282
|
+
if (operationType === "action") {
|
|
1283
|
+
typeAnnotation = `: ${this.getOperationTypeName(operation, model.name)}${typeParams}`;
|
|
1284
|
+
} else {
|
|
1285
|
+
typeAnnotation = "";
|
|
1286
|
+
}
|
|
1287
|
+
if (operationType === "query") {
|
|
1288
|
+
satisfiesType = `satisfies ${this.getOperationTypeName(operation, model.name)}${typeParams}`;
|
|
1289
|
+
} else {
|
|
1290
|
+
satisfiesType = "";
|
|
1304
1291
|
}
|
|
1305
1292
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
configLoaded = false;
|
|
1328
|
-
// Plugin name from swarm.config.json
|
|
1329
|
-
pluginName = PLUGIN_NAME;
|
|
1330
|
-
async loadSwarmConfig() {
|
|
1331
|
-
if (this.configLoaded) return;
|
|
1332
|
-
const configManager = new SwarmConfigManager();
|
|
1333
|
-
this.swarmConfig = await configManager.loadConfig();
|
|
1334
|
-
this.configLoaded = true;
|
|
1335
|
-
}
|
|
1336
|
-
async getCustomTemplateDir() {
|
|
1337
|
-
await this.loadSwarmConfig();
|
|
1338
|
-
return this.swarmConfig?.templateDirectory;
|
|
1293
|
+
const isCompositeKey = idFields.length > 1;
|
|
1294
|
+
const compositeKeyName = isCompositeKey ? idFields.join("_") : "";
|
|
1295
|
+
const idFieldParams = isCompositeKey ? idFields.join(", ") : idFields[0];
|
|
1296
|
+
const whereClause = isCompositeKey ? `${compositeKeyName}: { ${idFields.map((f) => `${f}`).join(", ")} }` : idFields[0];
|
|
1297
|
+
const replacements = {
|
|
1298
|
+
operationName,
|
|
1299
|
+
modelName: model.name,
|
|
1300
|
+
authCheck,
|
|
1301
|
+
imports,
|
|
1302
|
+
idField: idFields[0],
|
|
1303
|
+
idFieldParams,
|
|
1304
|
+
whereClause,
|
|
1305
|
+
isCompositeKey: String(isCompositeKey),
|
|
1306
|
+
compositeKeyName,
|
|
1307
|
+
jsonTypeHandling,
|
|
1308
|
+
typeAnnotation,
|
|
1309
|
+
satisfiesType,
|
|
1310
|
+
modelNameLower,
|
|
1311
|
+
pluralModelNameLower
|
|
1312
|
+
};
|
|
1313
|
+
return this.templateUtility.processTemplate(templatePath, replacements);
|
|
1339
1314
|
}
|
|
1340
1315
|
/**
|
|
1341
|
-
*
|
|
1316
|
+
* Generates an operation file for a given operation.
|
|
1342
1317
|
*/
|
|
1343
|
-
|
|
1344
|
-
const
|
|
1345
|
-
const
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
const { path: resolvedPath, isCustom } = this.templateResolver.resolveTemplatePath(
|
|
1350
|
-
this.pluginName,
|
|
1351
|
-
this.name,
|
|
1352
|
-
templateName,
|
|
1353
|
-
defaultPath,
|
|
1354
|
-
customPath
|
|
1318
|
+
generateOperationFile(operationsDir, operationName, operationCode, force = false) {
|
|
1319
|
+
const operationFile = `${operationsDir}/${operationName}.ts`;
|
|
1320
|
+
const fileExists = this.checkFileExists(
|
|
1321
|
+
operationFile,
|
|
1322
|
+
force,
|
|
1323
|
+
"Operation file"
|
|
1355
1324
|
);
|
|
1356
|
-
|
|
1357
|
-
this.logger.info(`Using custom template: ${resolvedPath}`);
|
|
1358
|
-
}
|
|
1359
|
-
return resolvedPath;
|
|
1325
|
+
this.writeFile(operationFile, operationCode, "operation file", fileExists);
|
|
1360
1326
|
}
|
|
1361
1327
|
/**
|
|
1362
|
-
*
|
|
1328
|
+
* Copies a directory of operation templates to the target feature directory.
|
|
1329
|
+
* @param templateDir - The source template directory
|
|
1330
|
+
* @param targetDir - The target feature directory
|
|
1363
1331
|
*/
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
force,
|
|
1369
|
-
readableFileType
|
|
1370
|
-
);
|
|
1371
|
-
const content = this.templateUtility.processTemplate(
|
|
1372
|
-
templatePath,
|
|
1373
|
-
replacements
|
|
1332
|
+
copyOperationTemplates(templateDir, targetDir) {
|
|
1333
|
+
copyDirectory(this.fileSystem, templateDir, targetDir);
|
|
1334
|
+
this.logger.debug(
|
|
1335
|
+
`Copied operation templates from ${templateDir} to ${targetDir}`
|
|
1374
1336
|
);
|
|
1375
|
-
this.writeFile(outputPath, content, readableFileType, fileExists);
|
|
1376
|
-
return fileExists;
|
|
1377
1337
|
}
|
|
1378
1338
|
/**
|
|
1379
|
-
*
|
|
1380
|
-
* Consolidates the pattern used in both file and config checks
|
|
1339
|
+
* Generates an operation definition for the feature configuration.
|
|
1381
1340
|
*/
|
|
1382
|
-
|
|
1383
|
-
if (
|
|
1384
|
-
|
|
1385
|
-
throw new Error(errorMessage || itemDescription);
|
|
1341
|
+
getDefinition(operationName, featurePath, entities, operationType, importPath, auth = false) {
|
|
1342
|
+
if (!OPERATION_TYPES.includes(operationType)) {
|
|
1343
|
+
handleFatalError3(`Unknown operation type: ${operationType}`);
|
|
1386
1344
|
}
|
|
1387
|
-
|
|
1345
|
+
const directory = TYPE_DIRECTORIES[operationType];
|
|
1346
|
+
const featureDir = getFeatureImportPath(featurePath);
|
|
1347
|
+
const templatePath = this.templateUtility.resolveTemplatePath(
|
|
1348
|
+
"operation.eta",
|
|
1349
|
+
"config",
|
|
1350
|
+
import.meta.url
|
|
1351
|
+
);
|
|
1352
|
+
return this.templateUtility.processTemplate(templatePath, {
|
|
1353
|
+
operationType: capitalise(operationType),
|
|
1354
|
+
operationName,
|
|
1355
|
+
featureDir,
|
|
1356
|
+
directory,
|
|
1357
|
+
entities: entities.map((e) => `"${e}"`).join(", "),
|
|
1358
|
+
importPath,
|
|
1359
|
+
auth: String(auth)
|
|
1360
|
+
});
|
|
1388
1361
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
// src/generators/action/schema.ts
|
|
1365
|
+
import { SchemaManager, commandRegistry as commandRegistry2 } from "@ingenyus/swarm";
|
|
1366
|
+
import { z as z3 } from "zod";
|
|
1367
|
+
var validActions = Object.values(ACTION_OPERATIONS);
|
|
1368
|
+
var actionSchema = z3.string().min(1, "Action type is required").transform((val) => SchemaManager.findEnumValue(ACTION_OPERATIONS, val)).pipe(
|
|
1369
|
+
z3.enum(ACTION_OPERATIONS, {
|
|
1370
|
+
message: `Invalid action. Must be one of: ${validActions.join(", ")}`
|
|
1371
|
+
})
|
|
1372
|
+
).meta({ description: "The action operation to generate" }).register(commandRegistry2, {
|
|
1373
|
+
shortName: "o",
|
|
1374
|
+
examples: validActions,
|
|
1375
|
+
helpText: `Available actions: ${validActions.join(", ")}`
|
|
1376
|
+
});
|
|
1377
|
+
var schema2 = z3.object({
|
|
1378
|
+
feature: commonSchemas.feature,
|
|
1379
|
+
operation: actionSchema,
|
|
1380
|
+
dataType: commonSchemas.dataType,
|
|
1381
|
+
name: commonSchemas.name.optional().meta({
|
|
1382
|
+
...commonSchemas.name.meta() ?? {},
|
|
1383
|
+
description: `${commonSchemas.name.meta()?.description ?? ""} (optional)`
|
|
1384
|
+
}).register(commandRegistry2, commandRegistry2.get(commonSchemas.name) ?? {}),
|
|
1385
|
+
entities: commonSchemas.entities,
|
|
1386
|
+
force: commonSchemas.force,
|
|
1387
|
+
auth: commonSchemas.auth
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
// src/generators/action/action-generator.ts
|
|
1391
|
+
var ActionGenerator = class extends OperationGeneratorBase {
|
|
1392
|
+
get componentType() {
|
|
1393
|
+
return CONFIG_TYPES.ACTION;
|
|
1394
|
+
}
|
|
1395
|
+
description = "Generates a Wasp Action";
|
|
1396
|
+
schema = schema2;
|
|
1397
|
+
async generate(args) {
|
|
1398
|
+
const { dataType, feature, name } = args;
|
|
1399
|
+
const operation = args.operation;
|
|
1400
|
+
const operationType = "action";
|
|
1401
|
+
const entities = args.entities ?? [];
|
|
1402
|
+
if (dataType && !entities.includes(dataType)) {
|
|
1403
|
+
entities.unshift(dataType);
|
|
1404
|
+
}
|
|
1405
|
+
const { operationCode, operationName } = await this.generateOperationComponents(
|
|
1406
|
+
dataType,
|
|
1407
|
+
operation,
|
|
1408
|
+
args.auth,
|
|
1409
|
+
entities,
|
|
1410
|
+
false,
|
|
1411
|
+
null,
|
|
1412
|
+
name
|
|
1413
|
+
);
|
|
1414
|
+
return this.handleGeneratorError(
|
|
1415
|
+
this.componentType,
|
|
1416
|
+
operationName,
|
|
1417
|
+
async () => {
|
|
1418
|
+
const configPath = this.validateFeatureConfig(feature);
|
|
1419
|
+
const { targetDirectory: operationsDir, importDirectory } = this.ensureTargetDirectory(feature, operationType);
|
|
1420
|
+
const importPath = `${importDirectory}/${operationName}`;
|
|
1421
|
+
this.generateOperationFile(
|
|
1422
|
+
operationsDir,
|
|
1423
|
+
operationName,
|
|
1424
|
+
operationCode,
|
|
1425
|
+
args.force || false
|
|
1426
|
+
);
|
|
1427
|
+
const definition = this.getDefinition(
|
|
1428
|
+
operationName,
|
|
1429
|
+
feature,
|
|
1430
|
+
entities,
|
|
1431
|
+
"action",
|
|
1432
|
+
importPath,
|
|
1433
|
+
args.auth
|
|
1434
|
+
);
|
|
1435
|
+
this.updateConfigWithCheck(
|
|
1436
|
+
configPath,
|
|
1437
|
+
"addAction",
|
|
1438
|
+
operationName,
|
|
1439
|
+
definition,
|
|
1440
|
+
feature,
|
|
1441
|
+
args.force || false
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
|
|
1448
|
+
// src/generators/api/api-generator.ts
|
|
1449
|
+
import { toCamelCase as toCamelCase2, toPascalCase as toPascalCase3 } from "@ingenyus/swarm";
|
|
1450
|
+
|
|
1451
|
+
// src/generators/api/schema.ts
|
|
1452
|
+
import { commandRegistry as commandRegistry3 } from "@ingenyus/swarm";
|
|
1453
|
+
import { z as z4 } from "zod";
|
|
1454
|
+
var validHttpMethods = API_HTTP_METHODS.map((method) => `${method}`);
|
|
1455
|
+
var schema3 = z4.object({
|
|
1456
|
+
method: z4.string().min(1, "HTTP method is required").transform((val) => val.toUpperCase()).pipe(
|
|
1457
|
+
z4.enum(API_HTTP_METHODS, {
|
|
1458
|
+
message: `Invalid HTTP method. Must be one of: ${validHttpMethods.join(", ")}`
|
|
1459
|
+
})
|
|
1460
|
+
).meta({ description: "The HTTP method used for this API Endpoint" }).register(commandRegistry3, {
|
|
1461
|
+
shortName: "m",
|
|
1462
|
+
examples: validHttpMethods,
|
|
1463
|
+
helpText: `Must be one of: ${validHttpMethods.join(", ")}`
|
|
1464
|
+
}),
|
|
1465
|
+
feature: commonSchemas.feature,
|
|
1466
|
+
name: commonSchemas.name,
|
|
1467
|
+
path: commonSchemas.path,
|
|
1468
|
+
entities: commonSchemas.entities,
|
|
1469
|
+
auth: commonSchemas.auth,
|
|
1470
|
+
force: commonSchemas.force,
|
|
1471
|
+
customMiddleware: z4.boolean().meta({ description: "Enable custom middleware for this API Endpoint" }).optional().register(commandRegistry3, {
|
|
1472
|
+
shortName: "c",
|
|
1473
|
+
helpText: "Will generate custom middleware file"
|
|
1474
|
+
})
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
// src/generators/api/api-generator.ts
|
|
1478
|
+
var ApiGenerator = class extends ComponentGeneratorBase {
|
|
1479
|
+
get componentType() {
|
|
1480
|
+
return CONFIG_TYPES.API;
|
|
1481
|
+
}
|
|
1482
|
+
description = "Generates a Wasp API Endpoint";
|
|
1483
|
+
schema = schema3;
|
|
1484
|
+
async generate(args) {
|
|
1485
|
+
const apiName = toCamelCase2(args.name);
|
|
1486
|
+
return this.handleGeneratorError(this.componentType, apiName, async () => {
|
|
1487
|
+
const configPath = this.validateFeatureConfig(args.feature);
|
|
1488
|
+
const {
|
|
1489
|
+
targetDirectory: apiTargetDirectory,
|
|
1490
|
+
importDirectory: apiImportDirectory
|
|
1491
|
+
} = this.ensureTargetDirectory(args.feature, this.name);
|
|
1492
|
+
const fileName = `${apiName}.ts`;
|
|
1493
|
+
const targetFile = `${apiTargetDirectory}/${fileName}`;
|
|
1494
|
+
await this.generateApiFile(targetFile, apiName, args);
|
|
1495
|
+
if (args.customMiddleware) {
|
|
1496
|
+
const { targetDirectory: middlewareTargetDirectory } = this.ensureTargetDirectory(args.feature, "middleware");
|
|
1497
|
+
const middlewareFile = `${middlewareTargetDirectory}/${apiName}.ts`;
|
|
1498
|
+
this.generateMiddlewareFile(
|
|
1499
|
+
middlewareFile,
|
|
1500
|
+
apiName,
|
|
1501
|
+
args.force || false
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
await this.updateConfigFile(
|
|
1505
|
+
apiName,
|
|
1506
|
+
fileName,
|
|
1507
|
+
apiImportDirectory,
|
|
1508
|
+
args,
|
|
1509
|
+
configPath
|
|
1510
|
+
);
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
async generateApiFile(targetFile, apiName, { method, auth = false, force = false }) {
|
|
1514
|
+
const replacements = this.buildTemplateData(apiName, method, auth);
|
|
1515
|
+
await this.renderTemplateToFile(
|
|
1516
|
+
"api.eta",
|
|
1517
|
+
replacements,
|
|
1518
|
+
targetFile,
|
|
1519
|
+
"API Endpoint file",
|
|
1520
|
+
force
|
|
1399
1521
|
);
|
|
1400
1522
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1523
|
+
async updateConfigFile(apiName, apiFile, importDirectory, args, configFilePath) {
|
|
1524
|
+
const {
|
|
1525
|
+
feature,
|
|
1526
|
+
force = false,
|
|
1527
|
+
entities,
|
|
1528
|
+
method,
|
|
1529
|
+
path: path9,
|
|
1530
|
+
auth,
|
|
1531
|
+
customMiddleware
|
|
1532
|
+
} = args;
|
|
1533
|
+
const importPath = this.path.join(importDirectory, apiFile);
|
|
1534
|
+
const definition = await this.getConfigDefinition(
|
|
1535
|
+
apiName,
|
|
1536
|
+
feature,
|
|
1537
|
+
Array.isArray(entities) ? entities : entities ? [entities] : [],
|
|
1538
|
+
method,
|
|
1539
|
+
path9,
|
|
1540
|
+
apiFile,
|
|
1541
|
+
auth,
|
|
1542
|
+
importPath,
|
|
1543
|
+
customMiddleware || false
|
|
1544
|
+
);
|
|
1545
|
+
this.updateConfigWithCheck(
|
|
1546
|
+
configFilePath,
|
|
1547
|
+
"addApi",
|
|
1548
|
+
apiName,
|
|
1549
|
+
definition,
|
|
1550
|
+
feature,
|
|
1551
|
+
force
|
|
1408
1552
|
);
|
|
1409
1553
|
}
|
|
1410
|
-
|
|
1554
|
+
async getConfigDefinition(apiName, featurePath, entities, method, route, apiFile, auth = false, importPath, customMiddleware = false) {
|
|
1555
|
+
const featureDir = getFeatureImportPath(featurePath);
|
|
1556
|
+
const configTemplatePath = await this.getTemplatePath("config/api.eta");
|
|
1557
|
+
return this.templateUtility.processTemplate(configTemplatePath, {
|
|
1558
|
+
apiName,
|
|
1559
|
+
featureDir,
|
|
1560
|
+
entities: entities.map((e) => `"${e}"`).join(", "),
|
|
1561
|
+
method,
|
|
1562
|
+
route,
|
|
1563
|
+
apiFile,
|
|
1564
|
+
auth: String(auth),
|
|
1565
|
+
importPath,
|
|
1566
|
+
customMiddleware: String(customMiddleware)
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
buildTemplateData(apiName, method, auth) {
|
|
1570
|
+
const apiType = toPascalCase3(apiName);
|
|
1571
|
+
const authCheck = auth ? ` if (!context.user) {
|
|
1572
|
+
throw new HttpError(401);
|
|
1573
|
+
}
|
|
1411
1574
|
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
});
|
|
1575
|
+
` : "";
|
|
1576
|
+
const methodCheck = method !== "ALL" ? ` if (req.method !== '${method}') {
|
|
1577
|
+
throw new HttpError(405);
|
|
1578
|
+
}
|
|
1417
1579
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
name;
|
|
1429
|
-
description;
|
|
1430
|
-
schema;
|
|
1431
|
-
getDefaultTemplatePath(templateName) {
|
|
1432
|
-
return this.templateUtility.resolveTemplatePath(
|
|
1433
|
-
templateName,
|
|
1434
|
-
this.name,
|
|
1435
|
-
import.meta.url
|
|
1436
|
-
);
|
|
1437
|
-
}
|
|
1438
|
-
/**
|
|
1439
|
-
* Generate feature directory structure (main entry point)
|
|
1440
|
-
* @param featurePath - The path to the feature
|
|
1441
|
-
*/
|
|
1442
|
-
async generate(flags) {
|
|
1443
|
-
const { path: featurePath } = flags;
|
|
1444
|
-
console.log("generate feature directory:", featurePath);
|
|
1445
|
-
const segments = validateFeaturePath2(featurePath);
|
|
1446
|
-
const normalisedPath = normaliseFeaturePath(featurePath);
|
|
1447
|
-
const sourceRoot = path7.join(findWaspRoot(this.fileSystem), "src");
|
|
1448
|
-
if (segments.length > 1) {
|
|
1449
|
-
const parentPath = segments.slice(0, -1).join("/");
|
|
1450
|
-
const parentNormalisedPath = normaliseFeaturePath(parentPath);
|
|
1451
|
-
const parentFeatureDir = path7.join(sourceRoot, parentNormalisedPath);
|
|
1452
|
-
if (!this.fileSystem.existsSync(parentFeatureDir)) {
|
|
1453
|
-
handleFatalError2(
|
|
1454
|
-
`Parent feature '${parentPath}' does not exist. Please create it first.`
|
|
1455
|
-
);
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
const featureDir = path7.join(sourceRoot, normalisedPath);
|
|
1459
|
-
this.fileSystem.mkdirSync(featureDir, { recursive: true });
|
|
1460
|
-
this.configGenerator.generate(normalisedPath);
|
|
1461
|
-
this.logger.success(`Generated feature: ${normalisedPath}`);
|
|
1580
|
+
` : "";
|
|
1581
|
+
const errorImport = auth || method !== "ALL" ? 'import { HttpError } from "wasp/server";\n' : "";
|
|
1582
|
+
const imports = `${errorImport}import type { ${apiType} } from "wasp/server/api";`;
|
|
1583
|
+
return {
|
|
1584
|
+
imports,
|
|
1585
|
+
apiType,
|
|
1586
|
+
apiName,
|
|
1587
|
+
methodCheck,
|
|
1588
|
+
authCheck
|
|
1589
|
+
};
|
|
1462
1590
|
}
|
|
1463
1591
|
};
|
|
1464
1592
|
|
|
1465
|
-
// src/generators/
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
);
|
|
1483
|
-
}
|
|
1484
|
-
get name() {
|
|
1485
|
-
return toKebabCase2(this.entityType.toString());
|
|
1593
|
+
// src/generators/api-namespace/api-namespace-generator.ts
|
|
1594
|
+
import { toCamelCase as toCamelCase3 } from "@ingenyus/swarm";
|
|
1595
|
+
import path7 from "path";
|
|
1596
|
+
|
|
1597
|
+
// src/generators/api-namespace/schema.ts
|
|
1598
|
+
import { z as z5 } from "zod";
|
|
1599
|
+
var schema4 = z5.object({
|
|
1600
|
+
feature: commonSchemas.feature,
|
|
1601
|
+
name: commonSchemas.name,
|
|
1602
|
+
path: commonSchemas.path,
|
|
1603
|
+
force: commonSchemas.force
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
// src/generators/api-namespace/api-namespace-generator.ts
|
|
1607
|
+
var ApiNamespaceGenerator = class extends ComponentGeneratorBase {
|
|
1608
|
+
get componentType() {
|
|
1609
|
+
return CONFIG_TYPES.API_NAMESPACE;
|
|
1486
1610
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
const
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1611
|
+
description = "Generates a Wasp API Namespace";
|
|
1612
|
+
schema = schema4;
|
|
1613
|
+
async generate(args) {
|
|
1614
|
+
const { name, path: namespacePath, feature } = args;
|
|
1615
|
+
const namespaceName = toCamelCase3(name);
|
|
1616
|
+
return this.handleGeneratorError(
|
|
1617
|
+
this.componentType,
|
|
1618
|
+
namespaceName,
|
|
1619
|
+
async () => {
|
|
1620
|
+
const configPath = this.validateFeatureConfig(feature);
|
|
1621
|
+
const { targetDirectory, importDirectory } = this.ensureTargetDirectory(
|
|
1622
|
+
feature,
|
|
1623
|
+
"middleware"
|
|
1624
|
+
);
|
|
1625
|
+
const targetFile = `${targetDirectory}/${namespaceName}.ts`;
|
|
1626
|
+
await this.generateMiddlewareFile(
|
|
1627
|
+
targetFile,
|
|
1628
|
+
namespaceName,
|
|
1629
|
+
args.force || false
|
|
1630
|
+
);
|
|
1631
|
+
await this.updateConfigFile(
|
|
1632
|
+
namespaceName,
|
|
1633
|
+
importDirectory,
|
|
1634
|
+
namespacePath,
|
|
1635
|
+
args,
|
|
1636
|
+
configPath
|
|
1637
|
+
);
|
|
1501
1638
|
}
|
|
1502
|
-
}
|
|
1503
|
-
this.logger.error(
|
|
1504
|
-
`Feature config file not found in '${normalisedPath}' or any ancestor directories`
|
|
1505
|
-
);
|
|
1506
|
-
this.logger.error(
|
|
1507
|
-
`Expected to find a .wasp.ts config file in one of the feature directories`
|
|
1508
1639
|
);
|
|
1509
|
-
throw new Error("Feature config file not found");
|
|
1510
1640
|
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
methodName,
|
|
1519
|
-
itemName
|
|
1641
|
+
async updateConfigFile(namespaceName, importDirectory, namespacePath, args, configFilePath) {
|
|
1642
|
+
const { force = false } = args;
|
|
1643
|
+
const importPath = path7.join(importDirectory, namespaceName);
|
|
1644
|
+
const definition = await this.getDefinition(
|
|
1645
|
+
namespaceName,
|
|
1646
|
+
importPath,
|
|
1647
|
+
namespacePath
|
|
1520
1648
|
);
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1649
|
+
this.updateConfigWithCheck(
|
|
1650
|
+
configFilePath,
|
|
1651
|
+
"addApiNamespace",
|
|
1652
|
+
namespaceName,
|
|
1653
|
+
definition,
|
|
1654
|
+
args.feature,
|
|
1655
|
+
force
|
|
1526
1656
|
);
|
|
1527
1657
|
}
|
|
1528
1658
|
/**
|
|
1529
|
-
*
|
|
1659
|
+
* Generates an apiNamespace definition for the feature configuration.
|
|
1530
1660
|
*/
|
|
1531
|
-
|
|
1532
|
-
this.
|
|
1533
|
-
|
|
1534
|
-
|
|
1661
|
+
async getDefinition(namespaceName, middlewareImportPath, pathValue) {
|
|
1662
|
+
const templatePath = this.templateUtility.resolveTemplatePath(
|
|
1663
|
+
"config/api-namespace.eta",
|
|
1664
|
+
"api-namespace",
|
|
1665
|
+
import.meta.url
|
|
1535
1666
|
);
|
|
1667
|
+
return this.templateUtility.processTemplate(templatePath, {
|
|
1668
|
+
namespaceName,
|
|
1669
|
+
middlewareImportPath,
|
|
1670
|
+
pathValue
|
|
1671
|
+
});
|
|
1536
1672
|
}
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1673
|
+
};
|
|
1674
|
+
|
|
1675
|
+
// src/generators/crud/crud-generator.ts
|
|
1676
|
+
import { getPlural as getPlural2, toCamelCase as toCamelCase4, toPascalCase as toPascalCase4 } from "@ingenyus/swarm";
|
|
1677
|
+
|
|
1678
|
+
// src/generators/crud/schema.ts
|
|
1679
|
+
import { SchemaManager as SchemaManager2, commandRegistry as commandRegistry4 } from "@ingenyus/swarm";
|
|
1680
|
+
import { z as z6 } from "zod";
|
|
1681
|
+
var validCrudOperations = Object.values(CRUD_OPERATIONS);
|
|
1682
|
+
var publicOperations = getCrudOperationsArray();
|
|
1683
|
+
var overrideOperations = getCrudOperationsArray();
|
|
1684
|
+
var excludeOperations = getCrudOperationsArray();
|
|
1685
|
+
var schema5 = z6.object({
|
|
1686
|
+
feature: commonSchemas.feature,
|
|
1687
|
+
name: commonSchemas.name,
|
|
1688
|
+
dataType: commonSchemas.dataType,
|
|
1689
|
+
public: publicOperations.meta({
|
|
1690
|
+
description: "Public CRUD operations (accessible without authentication)"
|
|
1691
|
+
}).register(commandRegistry4, {
|
|
1692
|
+
shortName: "b",
|
|
1693
|
+
examples: ["'get'", "'get' 'getAll'"],
|
|
1694
|
+
helpText: "Operations that can be accessed without authentication."
|
|
1695
|
+
}),
|
|
1696
|
+
override: overrideOperations.meta({ description: "Override existing CRUD operations" }).register(commandRegistry4, {
|
|
1697
|
+
shortName: "v",
|
|
1698
|
+
examples: ["'create'", "'create' 'update'"],
|
|
1699
|
+
helpText: "Operations that will have overriden implementations"
|
|
1700
|
+
}),
|
|
1701
|
+
exclude: excludeOperations.meta({ description: "Exclude specific CRUD operations from generation" }).register(commandRegistry4, {
|
|
1702
|
+
shortName: "x",
|
|
1703
|
+
examples: ["'delete'", "'delete' 'update'"],
|
|
1704
|
+
helpText: "Operations to exclude from generation"
|
|
1705
|
+
}),
|
|
1706
|
+
force: commonSchemas.force
|
|
1707
|
+
});
|
|
1708
|
+
function getCrudOperationsArray() {
|
|
1709
|
+
return z6.array(
|
|
1710
|
+
z6.string().transform((val) => {
|
|
1711
|
+
return SchemaManager2.findEnumValue(CRUD_OPERATIONS, val);
|
|
1712
|
+
}).pipe(
|
|
1713
|
+
z6.enum(CRUD_OPERATIONS, {
|
|
1714
|
+
message: `Must be one or more of: ${validCrudOperations.join(", ")}`
|
|
1715
|
+
})
|
|
1716
|
+
)
|
|
1717
|
+
).optional();
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
// src/generators/crud/crud-generator.ts
|
|
1721
|
+
var CRUD_OPERATIONS_LIST = [
|
|
1722
|
+
"get",
|
|
1723
|
+
"getAll",
|
|
1724
|
+
"create",
|
|
1725
|
+
"update",
|
|
1726
|
+
"delete"
|
|
1727
|
+
];
|
|
1728
|
+
var CrudGenerator = class extends OperationGeneratorBase {
|
|
1729
|
+
get componentType() {
|
|
1730
|
+
return CONFIG_TYPES.CRUD;
|
|
1557
1731
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1732
|
+
description = "Generates a Wasp CRUD operation";
|
|
1733
|
+
schema = schema5;
|
|
1734
|
+
async generate(args) {
|
|
1735
|
+
const { dataType, feature } = args;
|
|
1736
|
+
const crudName = toCamelCase4(getPlural2(dataType));
|
|
1737
|
+
const crudType = toPascalCase4(crudName);
|
|
1738
|
+
return this.handleGeneratorError(this.componentType, crudName, async () => {
|
|
1739
|
+
const configPath = this.validateFeatureConfig(feature);
|
|
1740
|
+
const { targetDirectory } = this.ensureTargetDirectory(
|
|
1741
|
+
feature,
|
|
1742
|
+
this.componentType.toLowerCase()
|
|
1743
|
+
);
|
|
1744
|
+
if ((args.override?.length ?? 0) > 0) {
|
|
1745
|
+
const targetFile = `${targetDirectory}/${crudName}.ts`;
|
|
1746
|
+
const operations = await this.getOperationsCode(
|
|
1747
|
+
dataType,
|
|
1748
|
+
crudName,
|
|
1749
|
+
args
|
|
1750
|
+
);
|
|
1751
|
+
await this.generateCrudFile(
|
|
1752
|
+
targetFile,
|
|
1753
|
+
dataType,
|
|
1754
|
+
operations,
|
|
1755
|
+
crudType,
|
|
1756
|
+
args
|
|
1757
|
+
);
|
|
1758
|
+
}
|
|
1759
|
+
await this.updateConfigFile(
|
|
1760
|
+
feature,
|
|
1761
|
+
crudName,
|
|
1762
|
+
dataType,
|
|
1763
|
+
args,
|
|
1764
|
+
configPath
|
|
1765
|
+
);
|
|
1766
|
+
});
|
|
1574
1767
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1768
|
+
async generateCrudFile(targetFile, dataType, operations, crudName, args) {
|
|
1769
|
+
const { override = [], force = false } = args;
|
|
1770
|
+
const model = await getEntityMetadata(dataType);
|
|
1771
|
+
const imports = this.generateCrudImports(
|
|
1772
|
+
model,
|
|
1773
|
+
dataType,
|
|
1774
|
+
crudName,
|
|
1775
|
+
override
|
|
1583
1776
|
);
|
|
1584
|
-
ensureDirectoryExists(this.fileSystem, targetDirectory);
|
|
1585
|
-
return { targetDirectory, importDirectory };
|
|
1586
|
-
}
|
|
1587
|
-
/**
|
|
1588
|
-
* Generate middleware file for API or API namespace
|
|
1589
|
-
*/
|
|
1590
|
-
async generateMiddlewareFile(targetFile, name, force) {
|
|
1591
1777
|
const replacements = {
|
|
1592
|
-
|
|
1593
|
-
|
|
1778
|
+
imports,
|
|
1779
|
+
operations
|
|
1594
1780
|
};
|
|
1595
1781
|
await this.renderTemplateToFile(
|
|
1596
|
-
"
|
|
1782
|
+
"crud.eta",
|
|
1597
1783
|
replacements,
|
|
1598
1784
|
targetFile,
|
|
1599
|
-
"
|
|
1785
|
+
"CRUD file",
|
|
1600
1786
|
force
|
|
1601
1787
|
);
|
|
1602
1788
|
}
|
|
1603
|
-
};
|
|
1604
|
-
|
|
1605
|
-
// src/generators/base/operation-generator.base.ts
|
|
1606
|
-
import {
|
|
1607
|
-
capitalise,
|
|
1608
|
-
getPlural,
|
|
1609
|
-
handleFatalError as handleFatalError3,
|
|
1610
|
-
toPascalCase as toPascalCase2
|
|
1611
|
-
} from "@ingenyus/swarm";
|
|
1612
|
-
var OperationGeneratorBase = class extends EntityGeneratorBase {
|
|
1613
|
-
/**
|
|
1614
|
-
* Gets the operation name based on operation type and model name.
|
|
1615
|
-
*/
|
|
1616
|
-
getOperationName(operation, modelName, customName) {
|
|
1617
|
-
if (customName) {
|
|
1618
|
-
return customName;
|
|
1619
|
-
}
|
|
1620
|
-
switch (operation) {
|
|
1621
|
-
case OPERATIONS.GETALL:
|
|
1622
|
-
return `getAll${getPlural(modelName)}`;
|
|
1623
|
-
case OPERATIONS.GETFILTERED:
|
|
1624
|
-
return `getFiltered${getPlural(modelName)}`;
|
|
1625
|
-
default:
|
|
1626
|
-
return `${operation}${modelName}`;
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
/**
|
|
1630
|
-
* Gets the template path for operation templates.
|
|
1631
|
-
* This method resolves operation templates to the operation generator's directory
|
|
1632
|
-
* instead of the current generator's directory.
|
|
1633
|
-
*/
|
|
1634
|
-
getOperationTemplatePath(templateName) {
|
|
1635
|
-
return this.templateUtility.resolveTemplatePath(
|
|
1636
|
-
templateName,
|
|
1637
|
-
"operation",
|
|
1638
|
-
import.meta.url
|
|
1639
|
-
);
|
|
1640
|
-
}
|
|
1641
|
-
/**
|
|
1642
|
-
* Gets the TypeScript type name for an operation.
|
|
1643
|
-
*/
|
|
1644
|
-
getOperationTypeName(operation, modelName) {
|
|
1645
|
-
return toPascalCase2(this.getOperationName(operation, modelName));
|
|
1646
|
-
}
|
|
1647
1789
|
/**
|
|
1648
1790
|
* Generates import statements for an operation.
|
|
1649
1791
|
*/
|
|
1650
|
-
|
|
1792
|
+
generateCrudImports(model, modelName, crudName, operations) {
|
|
1651
1793
|
const imports = [];
|
|
1652
|
-
if (operation !==
|
|
1794
|
+
if (operations.some((operation) => operation !== "getAll")) {
|
|
1653
1795
|
if (needsPrismaImport(model)) {
|
|
1654
1796
|
imports.push('import { Prisma } from "@prisma/client";');
|
|
1655
1797
|
}
|
|
1656
|
-
imports.push(`import { ${modelName} } from "wasp/entities";`);
|
|
1798
|
+
imports.push(`import { type ${modelName} } from "wasp/entities";`);
|
|
1657
1799
|
}
|
|
1658
1800
|
imports.push('import { HttpError } from "wasp/server";');
|
|
1659
|
-
imports.push(
|
|
1660
|
-
`import type { ${this.getOperationTypeName(
|
|
1661
|
-
operation,
|
|
1662
|
-
modelName
|
|
1663
|
-
)} } from "wasp/server/operations";`
|
|
1664
|
-
);
|
|
1801
|
+
imports.push(`import { type ${crudName} } from "wasp/server/crud";`);
|
|
1665
1802
|
return imports.join("\n");
|
|
1666
1803
|
}
|
|
1804
|
+
async updateConfigFile(feature, crudName, dataType, args, configPath) {
|
|
1805
|
+
const operations = this.buildOperations(args);
|
|
1806
|
+
const definition = this.getDefinition(crudName, dataType, operations);
|
|
1807
|
+
this.updateConfigWithCheck(
|
|
1808
|
+
configPath,
|
|
1809
|
+
"addCrud",
|
|
1810
|
+
crudName,
|
|
1811
|
+
definition,
|
|
1812
|
+
feature,
|
|
1813
|
+
args.force || false
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
buildOperations(args) {
|
|
1817
|
+
const {
|
|
1818
|
+
public: publicOps = [],
|
|
1819
|
+
override: overrideOps = [],
|
|
1820
|
+
exclude: excludeOps = []
|
|
1821
|
+
} = args;
|
|
1822
|
+
return CRUD_OPERATIONS_LIST.reduce(
|
|
1823
|
+
(acc, operation) => {
|
|
1824
|
+
if (excludeOps.includes(operation)) {
|
|
1825
|
+
return acc;
|
|
1826
|
+
}
|
|
1827
|
+
const operationConfig = {};
|
|
1828
|
+
if (publicOps.includes(operation)) {
|
|
1829
|
+
operationConfig.isPublic = true;
|
|
1830
|
+
}
|
|
1831
|
+
if (overrideOps.includes(operation)) {
|
|
1832
|
+
operationConfig.override = true;
|
|
1833
|
+
}
|
|
1834
|
+
acc[operation] = operationConfig;
|
|
1835
|
+
return acc;
|
|
1836
|
+
},
|
|
1837
|
+
{}
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1667
1840
|
/**
|
|
1668
|
-
*
|
|
1841
|
+
* Generates operation code for overridden CRUD operations and returns as a single string.
|
|
1669
1842
|
*/
|
|
1670
|
-
|
|
1671
|
-
|
|
1843
|
+
async getOperationsCode(dataType, crudName, args) {
|
|
1844
|
+
const { override = [], public: publicOps = [] } = args;
|
|
1845
|
+
if (override.length === 0) {
|
|
1846
|
+
return "";
|
|
1847
|
+
}
|
|
1848
|
+
const operationCodes = [];
|
|
1849
|
+
for (const operation of override) {
|
|
1850
|
+
const { operationCode } = await this.generateOperationComponents(
|
|
1851
|
+
dataType,
|
|
1852
|
+
operation,
|
|
1853
|
+
!publicOps.includes(operation),
|
|
1854
|
+
[dataType],
|
|
1855
|
+
true,
|
|
1856
|
+
toPascalCase4(crudName)
|
|
1857
|
+
);
|
|
1858
|
+
operationCodes.push(operationCode.replace(/^[\r\n]/, ""));
|
|
1859
|
+
}
|
|
1860
|
+
return operationCodes.join("");
|
|
1672
1861
|
}
|
|
1673
1862
|
/**
|
|
1674
|
-
* Generates
|
|
1863
|
+
* Generates a CRUD definition for the feature configuration.
|
|
1675
1864
|
*/
|
|
1676
|
-
|
|
1677
|
-
const
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
modelName,
|
|
1682
|
-
customName
|
|
1683
|
-
);
|
|
1684
|
-
const operationCode = await this.generateOperationCode(
|
|
1685
|
-
model,
|
|
1686
|
-
operation,
|
|
1687
|
-
auth,
|
|
1688
|
-
isCrudOverride,
|
|
1689
|
-
crudName
|
|
1865
|
+
getDefinition(crudName, dataType, operations) {
|
|
1866
|
+
const templatePath = this.templateUtility.resolveTemplatePath(
|
|
1867
|
+
"config/crud.eta",
|
|
1868
|
+
"crud",
|
|
1869
|
+
import.meta.url
|
|
1690
1870
|
);
|
|
1691
|
-
const
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
operationCode,
|
|
1698
|
-
configEntry,
|
|
1699
|
-
operationType,
|
|
1700
|
-
operationName
|
|
1701
|
-
};
|
|
1871
|
+
const operationsStr = JSON.stringify(operations, null, 2).replace(/"([^"]+)":/g, "$1:").slice(1, -1).split("\n").filter((line) => line.trim() !== "").map((line, index) => index === 0 ? line.trimStart() : " " + line).join("\n");
|
|
1872
|
+
return this.templateUtility.processTemplate(templatePath, {
|
|
1873
|
+
crudName: toPascalCase4(crudName),
|
|
1874
|
+
dataType,
|
|
1875
|
+
operations: operationsStr
|
|
1876
|
+
});
|
|
1702
1877
|
}
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
const
|
|
1715
|
-
|
|
1716
|
-
const
|
|
1717
|
-
const
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
)
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1878
|
+
};
|
|
1879
|
+
|
|
1880
|
+
// src/generators/job/job-generator.ts
|
|
1881
|
+
import { capitalise as capitalise2, toCamelCase as toCamelCase5 } from "@ingenyus/swarm";
|
|
1882
|
+
|
|
1883
|
+
// src/generators/job/schema.ts
|
|
1884
|
+
import { commandRegistry as commandRegistry5 } from "@ingenyus/swarm";
|
|
1885
|
+
import { z as z7 } from "zod";
|
|
1886
|
+
var cronSchema = z7.string().optional().refine(
|
|
1887
|
+
(val) => {
|
|
1888
|
+
if (!val) return true;
|
|
1889
|
+
const parts = val.trim().split(/\s+/);
|
|
1890
|
+
if (parts.length !== 5) return false;
|
|
1891
|
+
const [minute, hour, day, month, weekday] = parts;
|
|
1892
|
+
const validateCronField = (field, min, max) => {
|
|
1893
|
+
if (field === "*") return true;
|
|
1894
|
+
const rangeRegex = /^(\d+)(-(\d+))?(,(\d+)(-(\d+))?)*(\/(\d+))?$/;
|
|
1895
|
+
if (!rangeRegex.test(field)) return false;
|
|
1896
|
+
const items = field.split(",");
|
|
1897
|
+
for (const item of items) {
|
|
1898
|
+
if (item.includes("/")) {
|
|
1899
|
+
const [base, step] = item.split("/");
|
|
1900
|
+
const stepNum = parseInt(step, 10);
|
|
1901
|
+
if (isNaN(stepNum) || stepNum <= 0) return false;
|
|
1902
|
+
if (base === "*") continue;
|
|
1903
|
+
const baseNum = parseInt(base, 10);
|
|
1904
|
+
if (isNaN(baseNum) || baseNum < min || baseNum > max) return false;
|
|
1905
|
+
} else if (item.includes("-")) {
|
|
1906
|
+
const [start, end] = item.split("-");
|
|
1907
|
+
const startNum = parseInt(start, 10);
|
|
1908
|
+
const endNum = parseInt(end, 10);
|
|
1909
|
+
if (isNaN(startNum) || isNaN(endNum) || startNum < min || endNum > max || startNum > endNum)
|
|
1910
|
+
return false;
|
|
1911
|
+
} else {
|
|
1912
|
+
const num = parseInt(item, 10);
|
|
1913
|
+
if (isNaN(num) || num < min || num > max) return false;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
return true;
|
|
1917
|
+
};
|
|
1918
|
+
return validateCronField(minute, 0, 59) && validateCronField(hour, 0, 23) && validateCronField(day, 1, 31) && validateCronField(month, 1, 12) && validateCronField(weekday, 0, 6);
|
|
1919
|
+
},
|
|
1920
|
+
{
|
|
1921
|
+
message: 'Cron expression must be a valid five-field format: (minute hour day month weekday), e.g. "0 9 * * *"'
|
|
1922
|
+
}
|
|
1923
|
+
).meta({ description: "Cron schedule expression for the job" }).register(commandRegistry5, {
|
|
1924
|
+
shortName: "c",
|
|
1925
|
+
examples: ["0 9 * * *", "*/15 * * * *", "0 0 1 * *"],
|
|
1926
|
+
helpText: "Five-field cron expression: minute hour day month weekday"
|
|
1927
|
+
});
|
|
1928
|
+
var argsSchema = z7.string().optional().refine(
|
|
1929
|
+
(val) => {
|
|
1930
|
+
if (!val) return true;
|
|
1931
|
+
try {
|
|
1932
|
+
const parsed = JSON.parse(val);
|
|
1933
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed);
|
|
1934
|
+
} catch {
|
|
1935
|
+
return false;
|
|
1751
1936
|
}
|
|
1752
|
-
|
|
1753
|
-
|
|
1937
|
+
},
|
|
1938
|
+
{
|
|
1939
|
+
message: "Args must be a valid JSON object string"
|
|
1754
1940
|
}
|
|
1941
|
+
).meta({ description: "Arguments to pass to the job function when executed" }).register(commandRegistry5, {
|
|
1942
|
+
shortName: "a",
|
|
1943
|
+
examples: ['{"userId": 123}', '{"type": "cleanup", "batchSize": 100}'],
|
|
1944
|
+
helpText: "JSON object string that will be passed to the job function"
|
|
1945
|
+
});
|
|
1946
|
+
var schema6 = z7.object({
|
|
1947
|
+
feature: commonSchemas.feature,
|
|
1948
|
+
name: commonSchemas.name,
|
|
1949
|
+
entities: commonSchemas.entities,
|
|
1950
|
+
cron: cronSchema,
|
|
1951
|
+
args: argsSchema,
|
|
1952
|
+
force: commonSchemas.force
|
|
1953
|
+
});
|
|
1755
1954
|
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1955
|
+
// src/generators/job/job-generator.ts
|
|
1956
|
+
var JobGenerator = class extends ComponentGeneratorBase {
|
|
1957
|
+
get componentType() {
|
|
1958
|
+
return CONFIG_TYPES.JOB;
|
|
1959
|
+
}
|
|
1960
|
+
description = "Generates a Wasp Job";
|
|
1961
|
+
schema = schema6;
|
|
1962
|
+
async generate(args) {
|
|
1963
|
+
const jobName = toCamelCase5(args.name);
|
|
1964
|
+
return this.handleGeneratorError(this.componentType, jobName, async () => {
|
|
1965
|
+
const configPath = this.validateFeatureConfig(args.feature);
|
|
1966
|
+
const { targetDirectory } = this.ensureTargetDirectory(
|
|
1967
|
+
args.feature,
|
|
1968
|
+
this.componentType.toLowerCase()
|
|
1969
|
+
);
|
|
1970
|
+
const targetFile = `${targetDirectory}/${jobName}.ts`;
|
|
1971
|
+
await this.generateJobFile(targetFile, jobName, args);
|
|
1972
|
+
this.updateConfigFile(args.feature, jobName, args, configPath);
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
async generateJobFile(targetFile, jobName, args) {
|
|
1976
|
+
const jobType = capitalise2(jobName);
|
|
1977
|
+
const entities = args.entities ?? [];
|
|
1978
|
+
let imports = `import type { ${jobType} } from 'wasp/server/jobs';
|
|
1979
|
+
`;
|
|
1980
|
+
if (entities.length > 0) {
|
|
1981
|
+
imports += `import { ${entities.join(", ")} } from 'wasp/entities';
|
|
1982
|
+
`;
|
|
1782
1983
|
}
|
|
1783
|
-
const isCompositeKey = idFields.length > 1;
|
|
1784
|
-
const compositeKeyName = isCompositeKey ? idFields.join("_") : "";
|
|
1785
|
-
const idFieldParams = isCompositeKey ? idFields.join(", ") : idFields[0];
|
|
1786
|
-
const whereClause = isCompositeKey ? `${compositeKeyName}: { ${idFields.map((f) => `${f}`).join(", ")} }` : idFields[0];
|
|
1787
1984
|
const replacements = {
|
|
1788
|
-
operationName,
|
|
1789
|
-
modelName: model.name,
|
|
1790
|
-
authCheck,
|
|
1791
1985
|
imports,
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
whereClause,
|
|
1795
|
-
isCompositeKey: String(isCompositeKey),
|
|
1796
|
-
compositeKeyName,
|
|
1797
|
-
jsonTypeHandling,
|
|
1798
|
-
typeAnnotation,
|
|
1799
|
-
satisfiesType,
|
|
1800
|
-
modelNameLower,
|
|
1801
|
-
pluralModelNameLower
|
|
1986
|
+
jobType,
|
|
1987
|
+
jobName
|
|
1802
1988
|
};
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
const operationFile = `${operationsDir}/${operationName}.ts`;
|
|
1810
|
-
const fileExists = this.checkFileExists(
|
|
1811
|
-
operationFile,
|
|
1812
|
-
force,
|
|
1813
|
-
"Operation file"
|
|
1989
|
+
await this.renderTemplateToFile(
|
|
1990
|
+
"job.eta",
|
|
1991
|
+
replacements,
|
|
1992
|
+
targetFile,
|
|
1993
|
+
"job worker",
|
|
1994
|
+
args.force || false
|
|
1814
1995
|
);
|
|
1815
|
-
this.writeFile(operationFile, operationCode, "operation file", fileExists);
|
|
1816
1996
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
this.
|
|
1825
|
-
|
|
1997
|
+
updateConfigFile(featurePath, jobName, args, configPath) {
|
|
1998
|
+
const {
|
|
1999
|
+
entities = [],
|
|
2000
|
+
cron = "0 0 * * *",
|
|
2001
|
+
args: executionArgs = "{}",
|
|
2002
|
+
force = false
|
|
2003
|
+
} = args;
|
|
2004
|
+
const definition = this.getDefinition(
|
|
2005
|
+
jobName,
|
|
2006
|
+
entities,
|
|
2007
|
+
cron,
|
|
2008
|
+
executionArgs
|
|
2009
|
+
);
|
|
2010
|
+
this.updateConfigWithCheck(
|
|
2011
|
+
configPath,
|
|
2012
|
+
"job",
|
|
2013
|
+
jobName,
|
|
2014
|
+
definition,
|
|
2015
|
+
featurePath,
|
|
2016
|
+
force
|
|
1826
2017
|
);
|
|
1827
2018
|
}
|
|
1828
2019
|
/**
|
|
1829
|
-
* Generates
|
|
2020
|
+
* Generates a job definition for the feature configuration.
|
|
1830
2021
|
*/
|
|
1831
|
-
getDefinition(
|
|
1832
|
-
|
|
1833
|
-
handleFatalError3(`Unknown operation type: ${operationType}`);
|
|
1834
|
-
}
|
|
1835
|
-
const directory = TYPE_DIRECTORIES[operationType];
|
|
1836
|
-
const featureDir = getFeatureImportPath(featurePath);
|
|
1837
|
-
const templatePath = this.templateUtility.resolveTemplatePath(
|
|
1838
|
-
"operation.eta",
|
|
1839
|
-
"config",
|
|
1840
|
-
import.meta.url
|
|
1841
|
-
);
|
|
2022
|
+
getDefinition(jobName, entities, cron, args) {
|
|
2023
|
+
const templatePath = this.getDefaultTemplatePath("config/job.eta");
|
|
1842
2024
|
return this.templateUtility.processTemplate(templatePath, {
|
|
1843
|
-
|
|
1844
|
-
operationName,
|
|
1845
|
-
featureDir,
|
|
1846
|
-
directory,
|
|
2025
|
+
jobName,
|
|
1847
2026
|
entities: entities.map((e) => `"${e}"`).join(", "),
|
|
1848
|
-
|
|
1849
|
-
|
|
2027
|
+
cron,
|
|
2028
|
+
args
|
|
1850
2029
|
});
|
|
1851
2030
|
}
|
|
1852
2031
|
};
|
|
1853
2032
|
|
|
1854
|
-
// src/generators/
|
|
1855
|
-
import {
|
|
1856
|
-
import { z as
|
|
1857
|
-
var
|
|
1858
|
-
var
|
|
1859
|
-
|
|
1860
|
-
message: `Invalid
|
|
1861
|
-
})
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
examples: validActions,
|
|
1867
|
-
helpText: "Available actions: create, update, delete"
|
|
1868
|
-
}
|
|
1869
|
-
);
|
|
1870
|
-
var dataTypeSchema = extend2(z3.string().min(1, "Data type is required"), {
|
|
1871
|
-
description: "The data type/model name for this action",
|
|
1872
|
-
friendlyName: "Data Type",
|
|
1873
|
-
shortName: "d",
|
|
1874
|
-
examples: ["User", "Product", "Task"],
|
|
1875
|
-
helpText: "The Wasp entity or model name this action will work with"
|
|
2033
|
+
// src/generators/query/schema.ts
|
|
2034
|
+
import { SchemaManager as SchemaManager3, commandRegistry as commandRegistry6 } from "@ingenyus/swarm";
|
|
2035
|
+
import { z as z8 } from "zod";
|
|
2036
|
+
var validQueries = Object.values(QUERY_OPERATIONS);
|
|
2037
|
+
var querySchema = z8.string().min(1, "Query type is required").transform((val) => SchemaManager3.findEnumValue(QUERY_OPERATIONS, val)).pipe(
|
|
2038
|
+
z8.enum(QUERY_OPERATIONS, {
|
|
2039
|
+
message: `Invalid query. Must be one of: ${validQueries.join(", ")}`
|
|
2040
|
+
})
|
|
2041
|
+
).meta({ description: "The query operation to generate" }).register(commandRegistry6, {
|
|
2042
|
+
shortName: "o",
|
|
2043
|
+
examples: validQueries,
|
|
2044
|
+
helpText: `Available queries: ${validQueries.join(", ")}`
|
|
1876
2045
|
});
|
|
1877
|
-
var
|
|
2046
|
+
var schema7 = z8.object({
|
|
1878
2047
|
feature: commonSchemas.feature,
|
|
1879
|
-
operation:
|
|
1880
|
-
dataType:
|
|
1881
|
-
name:
|
|
2048
|
+
operation: querySchema,
|
|
2049
|
+
dataType: commonSchemas.dataType,
|
|
2050
|
+
name: commonSchemas.name.optional().meta({
|
|
2051
|
+
...commonSchemas.name.meta() ?? {},
|
|
2052
|
+
description: `${commonSchemas.name.meta()?.description ?? ""} (optional)`
|
|
2053
|
+
}).register(commandRegistry6, commandRegistry6.get(commonSchemas.name) ?? {}),
|
|
1882
2054
|
entities: commonSchemas.entities,
|
|
1883
2055
|
force: commonSchemas.force,
|
|
1884
2056
|
auth: commonSchemas.auth
|
|
1885
2057
|
});
|
|
1886
2058
|
|
|
1887
|
-
// src/generators/
|
|
1888
|
-
var
|
|
1889
|
-
get
|
|
1890
|
-
return CONFIG_TYPES.
|
|
2059
|
+
// src/generators/query/query-generator.ts
|
|
2060
|
+
var QueryGenerator = class extends OperationGeneratorBase {
|
|
2061
|
+
get componentType() {
|
|
2062
|
+
return CONFIG_TYPES.QUERY;
|
|
1891
2063
|
}
|
|
1892
|
-
description = "
|
|
1893
|
-
schema =
|
|
1894
|
-
async generate(
|
|
1895
|
-
const { dataType, feature, name } =
|
|
1896
|
-
const operation =
|
|
1897
|
-
const operationType = "
|
|
1898
|
-
const entities =
|
|
2064
|
+
description = "Generates a Wasp Query";
|
|
2065
|
+
schema = schema7;
|
|
2066
|
+
async generate(args) {
|
|
2067
|
+
const { dataType, feature, name } = args;
|
|
2068
|
+
const operation = args.operation;
|
|
2069
|
+
const operationType = "query";
|
|
2070
|
+
const entities = args.entities ?? [];
|
|
1899
2071
|
if (dataType && !entities.includes(dataType)) {
|
|
1900
2072
|
entities.unshift(dataType);
|
|
1901
2073
|
}
|
|
1902
2074
|
const { operationCode, operationName } = await this.generateOperationComponents(
|
|
1903
2075
|
dataType,
|
|
1904
2076
|
operation,
|
|
1905
|
-
|
|
2077
|
+
args.auth,
|
|
1906
2078
|
entities,
|
|
1907
2079
|
false,
|
|
1908
2080
|
null,
|
|
1909
2081
|
name
|
|
1910
2082
|
);
|
|
1911
|
-
return this.handleGeneratorError(
|
|
1912
|
-
this.
|
|
1913
|
-
operationName,
|
|
1914
|
-
async () => {
|
|
1915
|
-
const configPath = this.validateFeatureConfig(feature);
|
|
1916
|
-
const { targetDirectory: operationsDir, importDirectory } = this.ensureTargetDirectory(feature, operationType);
|
|
1917
|
-
const importPath = `${importDirectory}/${operationName}`;
|
|
1918
|
-
this.generateOperationFile(
|
|
1919
|
-
operationsDir,
|
|
1920
|
-
operationName,
|
|
1921
|
-
operationCode,
|
|
1922
|
-
|
|
1923
|
-
);
|
|
1924
|
-
const definition = this.getDefinition(
|
|
1925
|
-
operationName,
|
|
1926
|
-
feature,
|
|
1927
|
-
entities,
|
|
1928
|
-
"
|
|
1929
|
-
importPath,
|
|
1930
|
-
|
|
1931
|
-
);
|
|
1932
|
-
this.updateConfigWithCheck(
|
|
1933
|
-
configPath,
|
|
1934
|
-
"
|
|
1935
|
-
operationName,
|
|
1936
|
-
definition,
|
|
1937
|
-
feature,
|
|
1938
|
-
|
|
1939
|
-
);
|
|
1940
|
-
}
|
|
1941
|
-
);
|
|
1942
|
-
}
|
|
1943
|
-
};
|
|
1944
|
-
|
|
1945
|
-
// src/generators/api/api-generator.ts
|
|
1946
|
-
import { toCamelCase as toCamelCase2, toPascalCase as toPascalCase3 } from "@ingenyus/swarm";
|
|
1947
|
-
|
|
1948
|
-
// src/generators/api/schema.ts
|
|
1949
|
-
import { extend as extend3 } from "@ingenyus/swarm";
|
|
1950
|
-
import { z as z4 } from "zod";
|
|
1951
|
-
var validHttpMethods = Object.values(HTTP_METHODS);
|
|
1952
|
-
var schema3 = z4.object({
|
|
1953
|
-
method: extend3(
|
|
1954
|
-
z4.string().min(1, "HTTP method is required").refine(getTypedValueValidator(validHttpMethods), {
|
|
1955
|
-
message: `Invalid HTTP method. Must be one of: ${validHttpMethods.join(", ")}`
|
|
1956
|
-
}).transform(getTypedValueTransformer(validHttpMethods)),
|
|
1957
|
-
{
|
|
1958
|
-
description: "The HTTP method used for this API endpoint",
|
|
1959
|
-
friendlyName: "HTTP Method",
|
|
1960
|
-
shortName: "m",
|
|
1961
|
-
examples: validHttpMethods
|
|
1962
|
-
}
|
|
1963
|
-
),
|
|
1964
|
-
feature: commonSchemas.feature,
|
|
1965
|
-
name: commonSchemas.name,
|
|
1966
|
-
path: commonSchemas.path,
|
|
1967
|
-
entities: commonSchemas.entities,
|
|
1968
|
-
auth: commonSchemas.auth,
|
|
1969
|
-
force: commonSchemas.force,
|
|
1970
|
-
customMiddleware: z4.boolean().optional()
|
|
1971
|
-
});
|
|
1972
|
-
|
|
1973
|
-
// src/generators/api/api-generator.ts
|
|
1974
|
-
var ApiGenerator = class extends EntityGeneratorBase {
|
|
1975
|
-
get entityType() {
|
|
1976
|
-
return CONFIG_TYPES.API;
|
|
1977
|
-
}
|
|
1978
|
-
description = "Generate API endpoints for Wasp applications";
|
|
1979
|
-
schema = schema3;
|
|
1980
|
-
async generate(flags) {
|
|
1981
|
-
const apiName = toCamelCase2(flags?.name);
|
|
1982
|
-
return this.handleGeneratorError(this.entityType, apiName, async () => {
|
|
1983
|
-
const configPath = this.validateFeatureConfig(flags.feature);
|
|
1984
|
-
const {
|
|
1985
|
-
targetDirectory: apiTargetDirectory,
|
|
1986
|
-
importDirectory: apiImportDirectory
|
|
1987
|
-
} = this.ensureTargetDirectory(flags.feature, this.name);
|
|
1988
|
-
const fileName = `${apiName}.ts`;
|
|
1989
|
-
const targetFile = `${apiTargetDirectory}/${fileName}`;
|
|
1990
|
-
await this.generateApiFile(targetFile, apiName, flags);
|
|
1991
|
-
if (flags.customMiddleware) {
|
|
1992
|
-
const { targetDirectory: middlewareTargetDirectory } = this.ensureTargetDirectory(flags.feature, "middleware");
|
|
1993
|
-
const middlewareFile = `${middlewareTargetDirectory}/${apiName}.ts`;
|
|
1994
|
-
this.generateMiddlewareFile(
|
|
1995
|
-
middlewareFile,
|
|
1996
|
-
apiName,
|
|
1997
|
-
flags.force || false
|
|
1998
|
-
);
|
|
1999
|
-
}
|
|
2000
|
-
await this.updateConfigFile(
|
|
2001
|
-
flags.feature,
|
|
2002
|
-
apiName,
|
|
2003
|
-
fileName,
|
|
2004
|
-
apiImportDirectory,
|
|
2005
|
-
flags,
|
|
2006
|
-
configPath
|
|
2007
|
-
);
|
|
2008
|
-
});
|
|
2009
|
-
}
|
|
2010
|
-
async generateApiFile(targetFile, apiName, { method, auth = false, force = false }) {
|
|
2011
|
-
const replacements = this.buildTemplateData(apiName, method, auth);
|
|
2012
|
-
await this.renderTemplateToFile(
|
|
2013
|
-
"api.eta",
|
|
2014
|
-
replacements,
|
|
2015
|
-
targetFile,
|
|
2016
|
-
"API endpoint file",
|
|
2017
|
-
force
|
|
2018
|
-
);
|
|
2019
|
-
}
|
|
2020
|
-
async updateConfigFile(featurePath, apiName, apiFile, importDirectory, flags, configFilePath) {
|
|
2021
|
-
const { force = false, entities, method, route, auth } = flags;
|
|
2022
|
-
const importPath = this.path.join(importDirectory, apiFile);
|
|
2023
|
-
const definition = await this.getConfigDefinition(
|
|
2024
|
-
apiName,
|
|
2025
|
-
featurePath,
|
|
2026
|
-
Array.isArray(entities) ? entities : entities ? [entities] : [],
|
|
2027
|
-
method,
|
|
2028
|
-
route,
|
|
2029
|
-
apiFile,
|
|
2030
|
-
auth,
|
|
2031
|
-
importPath,
|
|
2032
|
-
flags.customMiddleware || false
|
|
2033
|
-
);
|
|
2034
|
-
this.updateConfigWithCheck(
|
|
2035
|
-
configFilePath,
|
|
2036
|
-
"addApi",
|
|
2037
|
-
apiName,
|
|
2038
|
-
definition,
|
|
2039
|
-
featurePath,
|
|
2040
|
-
force
|
|
2083
|
+
return this.handleGeneratorError(
|
|
2084
|
+
this.componentType,
|
|
2085
|
+
operationName,
|
|
2086
|
+
async () => {
|
|
2087
|
+
const configPath = this.validateFeatureConfig(feature);
|
|
2088
|
+
const { targetDirectory: operationsDir, importDirectory } = this.ensureTargetDirectory(feature, operationType);
|
|
2089
|
+
const importPath = `${importDirectory}/${operationName}`;
|
|
2090
|
+
this.generateOperationFile(
|
|
2091
|
+
operationsDir,
|
|
2092
|
+
operationName,
|
|
2093
|
+
operationCode,
|
|
2094
|
+
args.force || false
|
|
2095
|
+
);
|
|
2096
|
+
const definition = this.getDefinition(
|
|
2097
|
+
operationName,
|
|
2098
|
+
feature,
|
|
2099
|
+
entities,
|
|
2100
|
+
"query",
|
|
2101
|
+
importPath,
|
|
2102
|
+
args.auth
|
|
2103
|
+
);
|
|
2104
|
+
this.updateConfigWithCheck(
|
|
2105
|
+
configPath,
|
|
2106
|
+
"addQuery",
|
|
2107
|
+
operationName,
|
|
2108
|
+
definition,
|
|
2109
|
+
feature,
|
|
2110
|
+
args.force || false
|
|
2111
|
+
);
|
|
2112
|
+
}
|
|
2041
2113
|
);
|
|
2042
2114
|
}
|
|
2043
|
-
async getConfigDefinition(apiName, featurePath, entities, method, route, apiFile, auth = false, importPath, customMiddleware = false) {
|
|
2044
|
-
const featureDir = getFeatureImportPath(featurePath);
|
|
2045
|
-
const configTemplatePath = await this.getTemplatePath("config/api.eta");
|
|
2046
|
-
return this.templateUtility.processTemplate(configTemplatePath, {
|
|
2047
|
-
apiName,
|
|
2048
|
-
featureDir,
|
|
2049
|
-
entities: entities.map((e) => `"${e}"`).join(", "),
|
|
2050
|
-
method,
|
|
2051
|
-
route,
|
|
2052
|
-
apiFile,
|
|
2053
|
-
auth: String(auth),
|
|
2054
|
-
importPath,
|
|
2055
|
-
customMiddleware: String(customMiddleware)
|
|
2056
|
-
});
|
|
2057
|
-
}
|
|
2058
|
-
buildTemplateData(apiName, method, auth) {
|
|
2059
|
-
const apiType = toPascalCase3(apiName);
|
|
2060
|
-
const authCheck = auth ? ` if (!context.user) {
|
|
2061
|
-
throw new HttpError(401);
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
` : "";
|
|
2065
|
-
const methodCheck = method !== "ALL" ? ` if (req.method !== '${method}') {
|
|
2066
|
-
throw new HttpError(405);
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
` : "";
|
|
2070
|
-
const errorImport = auth || method !== "ALL" ? 'import { HttpError } from "wasp/server";\n' : "";
|
|
2071
|
-
const imports = `${errorImport}import type { ${apiType} } from "wasp/server/api";`;
|
|
2072
|
-
return {
|
|
2073
|
-
imports,
|
|
2074
|
-
apiType,
|
|
2075
|
-
apiName,
|
|
2076
|
-
methodCheck,
|
|
2077
|
-
authCheck
|
|
2078
|
-
};
|
|
2079
|
-
}
|
|
2080
2115
|
};
|
|
2081
2116
|
|
|
2082
|
-
// src/generators/
|
|
2083
|
-
import {
|
|
2084
|
-
|
|
2117
|
+
// src/generators/route/route-generator.ts
|
|
2118
|
+
import {
|
|
2119
|
+
formatDisplayName,
|
|
2120
|
+
toCamelCase as toCamelCase6,
|
|
2121
|
+
toPascalCase as toPascalCase5
|
|
2122
|
+
} from "@ingenyus/swarm";
|
|
2085
2123
|
|
|
2086
|
-
// src/generators/
|
|
2087
|
-
import { z as
|
|
2088
|
-
var
|
|
2124
|
+
// src/generators/route/schema.ts
|
|
2125
|
+
import { z as z9 } from "zod";
|
|
2126
|
+
var schema8 = z9.object({
|
|
2089
2127
|
feature: commonSchemas.feature,
|
|
2090
2128
|
name: commonSchemas.name,
|
|
2091
2129
|
path: commonSchemas.path,
|
|
2130
|
+
auth: commonSchemas.auth,
|
|
2092
2131
|
force: commonSchemas.force
|
|
2093
2132
|
});
|
|
2094
2133
|
|
|
2095
|
-
// src/generators/
|
|
2096
|
-
var
|
|
2097
|
-
get
|
|
2098
|
-
return CONFIG_TYPES.
|
|
2134
|
+
// src/generators/route/route-generator.ts
|
|
2135
|
+
var RouteGenerator = class extends ComponentGeneratorBase {
|
|
2136
|
+
get componentType() {
|
|
2137
|
+
return CONFIG_TYPES.ROUTE;
|
|
2099
2138
|
}
|
|
2100
|
-
description = "
|
|
2101
|
-
schema =
|
|
2102
|
-
async generate(
|
|
2103
|
-
const {
|
|
2104
|
-
const
|
|
2139
|
+
description = "Generates a Wasp Page and Route";
|
|
2140
|
+
schema = schema8;
|
|
2141
|
+
async generate(args) {
|
|
2142
|
+
const { path: routePath, name, feature } = args;
|
|
2143
|
+
const routeName = toCamelCase6(name || getRouteNameFromPath(routePath));
|
|
2144
|
+
const componentName = toPascalCase5(routeName);
|
|
2145
|
+
const fileName = `${componentName}.tsx`;
|
|
2105
2146
|
return this.handleGeneratorError(
|
|
2106
|
-
this.
|
|
2107
|
-
|
|
2147
|
+
this.componentType,
|
|
2148
|
+
routeName,
|
|
2108
2149
|
async () => {
|
|
2109
2150
|
const configPath = this.validateFeatureConfig(feature);
|
|
2110
|
-
const { targetDirectory
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
);
|
|
2114
|
-
const targetFile = `${targetDirectory}/${namespaceName}.ts`;
|
|
2115
|
-
await this.generateMiddlewareFile(
|
|
2116
|
-
targetFile,
|
|
2117
|
-
namespaceName,
|
|
2118
|
-
flags.force || false
|
|
2119
|
-
);
|
|
2120
|
-
await this.updateConfigFile(
|
|
2121
|
-
feature,
|
|
2122
|
-
namespaceName,
|
|
2123
|
-
importDirectory,
|
|
2124
|
-
apiPath,
|
|
2125
|
-
flags,
|
|
2126
|
-
configPath
|
|
2127
|
-
);
|
|
2151
|
+
const { targetDirectory } = this.ensureTargetDirectory(feature, "page");
|
|
2152
|
+
const targetFile = `${targetDirectory}/${fileName}`;
|
|
2153
|
+
await this.generatePageFile(targetFile, componentName, args);
|
|
2154
|
+
this.updateConfigFile(feature, routeName, routePath, args, configPath);
|
|
2128
2155
|
}
|
|
2129
2156
|
);
|
|
2130
2157
|
}
|
|
2131
|
-
async
|
|
2132
|
-
const
|
|
2133
|
-
const
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2158
|
+
async generatePageFile(targetFile, componentName, args) {
|
|
2159
|
+
const templatePath = "files/client/page.eta";
|
|
2160
|
+
const replacements = {
|
|
2161
|
+
componentName,
|
|
2162
|
+
displayName: formatDisplayName(componentName)
|
|
2163
|
+
};
|
|
2164
|
+
await this.renderTemplateToFile(
|
|
2165
|
+
"page.eta",
|
|
2166
|
+
replacements,
|
|
2167
|
+
targetFile,
|
|
2168
|
+
"Page file",
|
|
2169
|
+
args.force || false
|
|
2170
|
+
);
|
|
2171
|
+
}
|
|
2172
|
+
updateConfigFile(featurePath, routeName, routePath, args, configPath) {
|
|
2173
|
+
const definition = this.getDefinition(
|
|
2174
|
+
routeName,
|
|
2175
|
+
routePath,
|
|
2176
|
+
featurePath,
|
|
2177
|
+
args.auth
|
|
2138
2178
|
);
|
|
2139
2179
|
this.updateConfigWithCheck(
|
|
2140
|
-
|
|
2141
|
-
"
|
|
2142
|
-
|
|
2180
|
+
configPath,
|
|
2181
|
+
"addRoute",
|
|
2182
|
+
routeName,
|
|
2143
2183
|
definition,
|
|
2144
|
-
|
|
2145
|
-
force
|
|
2184
|
+
featurePath,
|
|
2185
|
+
args.force || false
|
|
2146
2186
|
);
|
|
2147
2187
|
}
|
|
2148
2188
|
/**
|
|
2149
|
-
* Generates
|
|
2189
|
+
* Generates a route definition for the feature configuration.
|
|
2150
2190
|
*/
|
|
2151
|
-
|
|
2152
|
-
const templatePath = this.
|
|
2153
|
-
"config/api-namespace.eta",
|
|
2154
|
-
"api-namespace",
|
|
2155
|
-
import.meta.url
|
|
2156
|
-
);
|
|
2191
|
+
getDefinition(routeName, routePath, featurePath, auth = false) {
|
|
2192
|
+
const templatePath = this.getDefaultTemplatePath("config/route.eta");
|
|
2157
2193
|
return this.templateUtility.processTemplate(templatePath, {
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2194
|
+
featureName: featurePath.split("/").pop() || featurePath,
|
|
2195
|
+
routeName,
|
|
2196
|
+
routePath,
|
|
2197
|
+
auth: String(auth)
|
|
2161
2198
|
});
|
|
2162
2199
|
}
|
|
2163
2200
|
};
|
|
2164
2201
|
|
|
2165
|
-
// src/
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
description: "Public CRUD operations (accessible without authentication)",
|
|
2180
|
-
friendlyName: "Public Operations",
|
|
2181
|
-
shortName: "b",
|
|
2182
|
-
examples: ["get,getAll", "create,update"],
|
|
2183
|
-
helpText: "Operations that can be accessed without authentication"
|
|
2184
|
-
}),
|
|
2185
|
-
override: extend4(overrideOperations, {
|
|
2186
|
-
description: "Override existing CRUD operations",
|
|
2187
|
-
friendlyName: "Override Operations",
|
|
2188
|
-
shortName: "o",
|
|
2189
|
-
examples: ["create,update"],
|
|
2190
|
-
helpText: "Operations to override if they already exist"
|
|
2191
|
-
}),
|
|
2192
|
-
exclude: extend4(excludeOperations, {
|
|
2193
|
-
description: "Exclude specific CRUD operations from generation",
|
|
2194
|
-
friendlyName: "Exclude Operations",
|
|
2195
|
-
shortName: "x",
|
|
2196
|
-
examples: ["delete", "update,delete"],
|
|
2197
|
-
helpText: "Operations to exclude from generation"
|
|
2198
|
-
}),
|
|
2199
|
-
force: commonSchemas.force
|
|
2200
|
-
});
|
|
2201
|
-
function getCrudOperationsArray() {
|
|
2202
|
-
return z6.string().optional().refine(getTypedArrayValidator(validCrudOperations), {
|
|
2203
|
-
message: `Must be one or more of: ${validCrudOperations.join(", ")}`
|
|
2204
|
-
}).transform(getTypedArrayTransformer(validCrudOperations));
|
|
2205
|
-
}
|
|
2202
|
+
// src/plugins/wasp.ts
|
|
2203
|
+
var wasp = {
|
|
2204
|
+
name: PLUGIN_NAME,
|
|
2205
|
+
generators: [
|
|
2206
|
+
new ActionGenerator(),
|
|
2207
|
+
new ApiGenerator(),
|
|
2208
|
+
new ApiNamespaceGenerator(),
|
|
2209
|
+
new CrudGenerator(),
|
|
2210
|
+
new FeatureGenerator(),
|
|
2211
|
+
new JobGenerator(),
|
|
2212
|
+
new QueryGenerator(),
|
|
2213
|
+
new RouteGenerator()
|
|
2214
|
+
]
|
|
2215
|
+
};
|
|
2206
2216
|
|
|
2207
|
-
// src/
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
get entityType() {
|
|
2217
|
-
return CONFIG_TYPES.CRUD;
|
|
2217
|
+
// src/wasp-config/app.ts
|
|
2218
|
+
import fs3 from "fs";
|
|
2219
|
+
import path8 from "path";
|
|
2220
|
+
import {
|
|
2221
|
+
App as WaspApp
|
|
2222
|
+
} from "wasp-config";
|
|
2223
|
+
var App = class _App extends WaspApp {
|
|
2224
|
+
constructor(name, config) {
|
|
2225
|
+
super(name, config);
|
|
2218
2226
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2227
|
+
/**
|
|
2228
|
+
* Static factory method that creates and initializes Swarm with configuration
|
|
2229
|
+
* dynamically loaded from feature directories
|
|
2230
|
+
*
|
|
2231
|
+
* @param name The name of the application
|
|
2232
|
+
* @param config The base configuration for the application
|
|
2233
|
+
* @returns An initialized Swarm instance
|
|
2234
|
+
*/
|
|
2235
|
+
static async create(name, config) {
|
|
2236
|
+
const app = new _App(name, config);
|
|
2237
|
+
await app.configureFeatures();
|
|
2238
|
+
return app;
|
|
2239
|
+
}
|
|
2240
|
+
// Chainable configuration methods
|
|
2241
|
+
auth(authConfig) {
|
|
2242
|
+
super.auth(authConfig);
|
|
2243
|
+
return this;
|
|
2244
|
+
}
|
|
2245
|
+
client(clientConfig) {
|
|
2246
|
+
super.client(clientConfig);
|
|
2247
|
+
return this;
|
|
2248
|
+
}
|
|
2249
|
+
db(dbConfig) {
|
|
2250
|
+
super.db(dbConfig);
|
|
2251
|
+
return this;
|
|
2252
|
+
}
|
|
2253
|
+
emailSender(emailSenderConfig) {
|
|
2254
|
+
super.emailSender(emailSenderConfig);
|
|
2255
|
+
return this;
|
|
2256
|
+
}
|
|
2257
|
+
job(name, jobConfig) {
|
|
2258
|
+
super.job(name, jobConfig);
|
|
2259
|
+
return this;
|
|
2260
|
+
}
|
|
2261
|
+
query(name, queryConfig) {
|
|
2262
|
+
super.query(name, queryConfig);
|
|
2263
|
+
return this;
|
|
2254
2264
|
}
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
import { type ${toPascalCase4(crudName)} } from "wasp/server/crud";`;
|
|
2259
|
-
const replacements = {
|
|
2260
|
-
imports,
|
|
2261
|
-
operations
|
|
2262
|
-
};
|
|
2263
|
-
await this.renderTemplateToFile(
|
|
2264
|
-
"crud.eta",
|
|
2265
|
-
replacements,
|
|
2266
|
-
targetFile,
|
|
2267
|
-
"CRUD file",
|
|
2268
|
-
force
|
|
2269
|
-
);
|
|
2265
|
+
route(name, routeConfig) {
|
|
2266
|
+
super.route(name, routeConfig);
|
|
2267
|
+
return this;
|
|
2270
2268
|
}
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
this.updateConfigWithCheck(
|
|
2275
|
-
configPath,
|
|
2276
|
-
"addCrud",
|
|
2277
|
-
crudName,
|
|
2278
|
-
definition,
|
|
2279
|
-
feature,
|
|
2280
|
-
flags.force || false
|
|
2281
|
-
);
|
|
2269
|
+
api(name, apiConfig) {
|
|
2270
|
+
super.api(name, apiConfig);
|
|
2271
|
+
return this;
|
|
2282
2272
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
return
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
const operationConfig = {};
|
|
2295
|
-
if (publicOps.includes(operation)) {
|
|
2296
|
-
operationConfig.isPublic = true;
|
|
2297
|
-
}
|
|
2298
|
-
if (overrideOps.includes(operation)) {
|
|
2299
|
-
operationConfig.override = true;
|
|
2300
|
-
}
|
|
2301
|
-
acc[operation] = operationConfig;
|
|
2302
|
-
return acc;
|
|
2303
|
-
},
|
|
2304
|
-
{}
|
|
2305
|
-
);
|
|
2273
|
+
apiNamespace(name, apiNamespaceConfig) {
|
|
2274
|
+
super.apiNamespace(name, apiNamespaceConfig);
|
|
2275
|
+
return this;
|
|
2276
|
+
}
|
|
2277
|
+
crud(name, crudConfig) {
|
|
2278
|
+
super.crud(name, crudConfig);
|
|
2279
|
+
return this;
|
|
2280
|
+
}
|
|
2281
|
+
action(name, actionConfig) {
|
|
2282
|
+
super.action(name, actionConfig);
|
|
2283
|
+
return this;
|
|
2306
2284
|
}
|
|
2307
2285
|
/**
|
|
2308
|
-
*
|
|
2286
|
+
* Helper method to add routes with simplified parameters
|
|
2287
|
+
* @param featureName The name of the feature
|
|
2288
|
+
* @param name Route name, e.g. "DashboardRoute"
|
|
2289
|
+
* @param options Route configuration options
|
|
2309
2290
|
*/
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2291
|
+
addRoute(featureName, name, options) {
|
|
2292
|
+
const componentName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
2293
|
+
const importPath = this.getFeatureImportPath(
|
|
2294
|
+
featureName,
|
|
2295
|
+
"client",
|
|
2296
|
+
"pages",
|
|
2297
|
+
componentName
|
|
2298
|
+
);
|
|
2299
|
+
const routeConfig = {
|
|
2300
|
+
path: options.path,
|
|
2301
|
+
to: this.page(componentName, {
|
|
2302
|
+
authRequired: options.auth || false,
|
|
2303
|
+
component: {
|
|
2304
|
+
import: componentName,
|
|
2305
|
+
from: `@src/${importPath}`
|
|
2306
|
+
}
|
|
2307
|
+
})
|
|
2308
|
+
};
|
|
2309
|
+
super.route(name, routeConfig);
|
|
2310
|
+
return this;
|
|
2327
2311
|
}
|
|
2328
2312
|
/**
|
|
2329
|
-
*
|
|
2313
|
+
* Helper method to add API endpoints with simplified parameters
|
|
2314
|
+
* @param featureName The name of the feature
|
|
2315
|
+
* @param name API endpoint name, e.g. "getTasksApi"
|
|
2316
|
+
* @param options API configuration options
|
|
2330
2317
|
*/
|
|
2331
|
-
|
|
2332
|
-
const
|
|
2333
|
-
|
|
2334
|
-
"
|
|
2335
|
-
|
|
2318
|
+
addApi(featureName, name, options) {
|
|
2319
|
+
const importPath = this.getFeatureImportPath(
|
|
2320
|
+
featureName,
|
|
2321
|
+
"server",
|
|
2322
|
+
"apis",
|
|
2323
|
+
name
|
|
2336
2324
|
);
|
|
2337
|
-
const
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2325
|
+
const middlewareImportPath = this.getFeatureImportPath(
|
|
2326
|
+
featureName,
|
|
2327
|
+
"server",
|
|
2328
|
+
"middleware",
|
|
2329
|
+
name
|
|
2330
|
+
);
|
|
2331
|
+
super.api(name, {
|
|
2332
|
+
fn: {
|
|
2333
|
+
import: name,
|
|
2334
|
+
from: `@src/${importPath}`
|
|
2335
|
+
},
|
|
2336
|
+
...options.customMiddleware && {
|
|
2337
|
+
import: name,
|
|
2338
|
+
from: `@src/${middlewareImportPath}`
|
|
2339
|
+
},
|
|
2340
|
+
entities: options.entities,
|
|
2341
|
+
httpRoute: { method: options.method, route: options.route },
|
|
2342
|
+
auth: options.auth || false
|
|
2342
2343
|
});
|
|
2344
|
+
return this;
|
|
2343
2345
|
}
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
} else {
|
|
2380
|
-
const num = parseInt(item, 10);
|
|
2381
|
-
if (isNaN(num) || num < min || num > max) return false;
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2384
|
-
return true;
|
|
2385
|
-
};
|
|
2386
|
-
return validateCronField(minute, 0, 59) && validateCronField(hour, 0, 23) && validateCronField(day, 1, 31) && validateCronField(month, 1, 12) && validateCronField(weekday, 0, 6);
|
|
2387
|
-
},
|
|
2388
|
-
{
|
|
2389
|
-
message: 'Cron expression must be a valid five-field format: (minute hour day month weekday), e.g. "0 9 * * *"'
|
|
2390
|
-
}
|
|
2391
|
-
),
|
|
2392
|
-
{
|
|
2393
|
-
description: "Cron schedule expression for the job",
|
|
2394
|
-
friendlyName: "Cron Schedule",
|
|
2395
|
-
shortName: "c",
|
|
2396
|
-
examples: ["0 9 * * *", "*/15 * * * *", "0 0 1 * *"],
|
|
2397
|
-
helpText: "Five-field cron expression: minute hour day month weekday"
|
|
2398
|
-
}
|
|
2399
|
-
);
|
|
2400
|
-
var argsSchema = extend5(
|
|
2401
|
-
z7.string().optional().refine(
|
|
2402
|
-
(val) => {
|
|
2403
|
-
if (!val) return true;
|
|
2404
|
-
try {
|
|
2405
|
-
const parsed = JSON.parse(val);
|
|
2406
|
-
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed);
|
|
2407
|
-
} catch {
|
|
2408
|
-
return false;
|
|
2346
|
+
/**
|
|
2347
|
+
* Helper method to add CRUD operations with simplified parameters
|
|
2348
|
+
* @param featureName The name of the feature
|
|
2349
|
+
* @param name The CRUD name
|
|
2350
|
+
* @param options CRUD configuration options
|
|
2351
|
+
*/
|
|
2352
|
+
addCrud(featureName, name, options) {
|
|
2353
|
+
const processOperationOptions = (operationName, operationOptions) => {
|
|
2354
|
+
if (!operationOptions) return void 0;
|
|
2355
|
+
const processedOptions = { ...operationOptions };
|
|
2356
|
+
if (operationOptions.override) {
|
|
2357
|
+
const operationDataType = operationName === "getAll" ? this.getPlural(options.entity) : options.entity;
|
|
2358
|
+
const operationComponent = `${operationName}${operationDataType}`;
|
|
2359
|
+
const importPath = this.getFeatureImportPath(
|
|
2360
|
+
featureName,
|
|
2361
|
+
"server",
|
|
2362
|
+
"cruds",
|
|
2363
|
+
name.charAt(0).toLowerCase() + name.slice(1)
|
|
2364
|
+
);
|
|
2365
|
+
processedOptions.overrideFn = {
|
|
2366
|
+
import: operationComponent,
|
|
2367
|
+
from: `@src/${importPath}`
|
|
2368
|
+
};
|
|
2369
|
+
delete processedOptions.override;
|
|
2370
|
+
}
|
|
2371
|
+
return processedOptions;
|
|
2372
|
+
};
|
|
2373
|
+
super.crud(this.getPlural(options.entity), {
|
|
2374
|
+
entity: options.entity,
|
|
2375
|
+
operations: {
|
|
2376
|
+
getAll: processOperationOptions("getAll", options.getAll),
|
|
2377
|
+
get: processOperationOptions("get", options.get),
|
|
2378
|
+
create: processOperationOptions("create", options.create),
|
|
2379
|
+
update: processOperationOptions("update", options.update),
|
|
2380
|
+
delete: processOperationOptions("delete", options.delete)
|
|
2409
2381
|
}
|
|
2410
|
-
},
|
|
2411
|
-
{
|
|
2412
|
-
message: "Args must be a valid JSON object string"
|
|
2413
|
-
}
|
|
2414
|
-
),
|
|
2415
|
-
{
|
|
2416
|
-
description: "Arguments to pass to the job function when executed",
|
|
2417
|
-
friendlyName: "Job Arguments",
|
|
2418
|
-
shortName: "a",
|
|
2419
|
-
examples: ['{"userId": 123}', '{"type": "cleanup", "batchSize": 100}'],
|
|
2420
|
-
helpText: "JSON object string that will be passed to the job function"
|
|
2421
|
-
}
|
|
2422
|
-
);
|
|
2423
|
-
var schema6 = z7.object({
|
|
2424
|
-
feature: commonSchemas.feature,
|
|
2425
|
-
name: commonSchemas.name,
|
|
2426
|
-
entities: commonSchemas.entities,
|
|
2427
|
-
cron: cronSchema,
|
|
2428
|
-
args: argsSchema,
|
|
2429
|
-
force: commonSchemas.force
|
|
2430
|
-
});
|
|
2431
|
-
|
|
2432
|
-
// src/generators/job/job-generator.ts
|
|
2433
|
-
var JobGenerator = class extends EntityGeneratorBase {
|
|
2434
|
-
get entityType() {
|
|
2435
|
-
return CONFIG_TYPES.JOB;
|
|
2436
|
-
}
|
|
2437
|
-
description = "Generate job workers for Wasp applications";
|
|
2438
|
-
schema = schema6;
|
|
2439
|
-
async generate(flags) {
|
|
2440
|
-
const jobName = toCamelCase5(flags.name);
|
|
2441
|
-
return this.handleGeneratorError(this.entityType, jobName, async () => {
|
|
2442
|
-
const configPath = this.validateFeatureConfig(flags.feature);
|
|
2443
|
-
const { targetDirectory } = this.ensureTargetDirectory(
|
|
2444
|
-
flags.feature,
|
|
2445
|
-
this.entityType.toLowerCase()
|
|
2446
|
-
);
|
|
2447
|
-
const targetFile = `${targetDirectory}/${jobName}.ts`;
|
|
2448
|
-
await this.generateJobFile(targetFile, jobName, flags);
|
|
2449
|
-
this.updateConfigFile(flags.feature, jobName, flags, configPath);
|
|
2450
2382
|
});
|
|
2383
|
+
return this;
|
|
2451
2384
|
}
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
"job worker",
|
|
2471
|
-
flags.force || false
|
|
2385
|
+
/**
|
|
2386
|
+
* Helper method to add actions with simplified parameters
|
|
2387
|
+
* @param featureName The name of the feature
|
|
2388
|
+
* @param name The action name
|
|
2389
|
+
* @param options Action configuration options
|
|
2390
|
+
*/
|
|
2391
|
+
addAction(featureName, name, options) {
|
|
2392
|
+
const importPath = this.getFeatureImportPath(
|
|
2393
|
+
featureName,
|
|
2394
|
+
"server",
|
|
2395
|
+
"actions",
|
|
2396
|
+
name
|
|
2397
|
+
);
|
|
2398
|
+
const config = this.getOperationConfig(
|
|
2399
|
+
name,
|
|
2400
|
+
importPath,
|
|
2401
|
+
options.entities,
|
|
2402
|
+
options.auth
|
|
2472
2403
|
);
|
|
2404
|
+
super.action(name, config);
|
|
2405
|
+
return this;
|
|
2473
2406
|
}
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2407
|
+
/**
|
|
2408
|
+
* Helper method to add queries with simplified parameters
|
|
2409
|
+
* @param featureName The name of the feature
|
|
2410
|
+
* @param name The query name
|
|
2411
|
+
* @param options Query configuration options
|
|
2412
|
+
*/
|
|
2413
|
+
addQuery(featureName, name, options) {
|
|
2414
|
+
const importPath = this.getFeatureImportPath(
|
|
2415
|
+
featureName,
|
|
2416
|
+
"server",
|
|
2417
|
+
"queries",
|
|
2418
|
+
name
|
|
2483
2419
|
);
|
|
2484
|
-
this.
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
featurePath,
|
|
2490
|
-
flags.force || false
|
|
2420
|
+
const config = this.getOperationConfig(
|
|
2421
|
+
name,
|
|
2422
|
+
importPath,
|
|
2423
|
+
options.entities,
|
|
2424
|
+
options.auth
|
|
2491
2425
|
);
|
|
2426
|
+
super.query(name, config);
|
|
2427
|
+
return this;
|
|
2492
2428
|
}
|
|
2493
2429
|
/**
|
|
2494
|
-
*
|
|
2430
|
+
* Helper method to add background jobs with simplified parameters
|
|
2431
|
+
* @param featureName The name of the feature
|
|
2432
|
+
* @param name Job name
|
|
2433
|
+
* @param options Job configuration options
|
|
2495
2434
|
*/
|
|
2496
|
-
|
|
2497
|
-
const
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2435
|
+
addJob(featureName, name, options) {
|
|
2436
|
+
const importPath = this.getFeatureImportPath(
|
|
2437
|
+
featureName,
|
|
2438
|
+
"server",
|
|
2439
|
+
"jobs",
|
|
2440
|
+
name
|
|
2441
|
+
);
|
|
2442
|
+
super.job(name, {
|
|
2443
|
+
executor: "PgBoss",
|
|
2444
|
+
perform: {
|
|
2445
|
+
fn: {
|
|
2446
|
+
import: name,
|
|
2447
|
+
from: `@src/${importPath}`
|
|
2448
|
+
}
|
|
2449
|
+
},
|
|
2450
|
+
entities: options.entities,
|
|
2451
|
+
...options.cron && {
|
|
2452
|
+
schedule: {
|
|
2453
|
+
cron: options.cron,
|
|
2454
|
+
args: options.args || {}
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2503
2457
|
});
|
|
2458
|
+
return this;
|
|
2504
2459
|
}
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
description: "The query operation to generate",
|
|
2517
|
-
friendlyName: "Query Operation",
|
|
2518
|
-
shortName: "o",
|
|
2519
|
-
examples: validQueries,
|
|
2520
|
-
helpText: "Available queries: get, getAll, getFiltered"
|
|
2521
|
-
}
|
|
2522
|
-
);
|
|
2523
|
-
var dataTypeSchema2 = extend6(z8.string().min(1, "Data type is required"), {
|
|
2524
|
-
description: "The data type/model name for this query",
|
|
2525
|
-
friendlyName: "Data Type",
|
|
2526
|
-
shortName: "d",
|
|
2527
|
-
examples: ["User", "Product", "Task"],
|
|
2528
|
-
helpText: "The Wasp entity or model name this query will work with"
|
|
2529
|
-
});
|
|
2530
|
-
var schema7 = z8.object({
|
|
2531
|
-
feature: commonSchemas.feature,
|
|
2532
|
-
operation: querySchema,
|
|
2533
|
-
dataType: dataTypeSchema2,
|
|
2534
|
-
name: extend6(commonSchemas.name.optional(), commonSchemas.name._metadata),
|
|
2535
|
-
entities: commonSchemas.entities,
|
|
2536
|
-
force: commonSchemas.force,
|
|
2537
|
-
auth: commonSchemas.auth
|
|
2538
|
-
});
|
|
2539
|
-
|
|
2540
|
-
// src/generators/query/query-generator.ts
|
|
2541
|
-
var QueryGenerator = class extends OperationGeneratorBase {
|
|
2542
|
-
get entityType() {
|
|
2543
|
-
return CONFIG_TYPES.QUERY;
|
|
2544
|
-
}
|
|
2545
|
-
description = "Generate queries (data fetching) for Wasp applications";
|
|
2546
|
-
schema = schema7;
|
|
2547
|
-
async generate(flags) {
|
|
2548
|
-
const { dataType, feature, name } = flags;
|
|
2549
|
-
const operation = flags.operation;
|
|
2550
|
-
const operationType = "query";
|
|
2551
|
-
const entities = flags.entities ? Array.isArray(flags.entities) ? flags.entities : flags.entities.split(",").map((e) => e.trim()).filter(Boolean) : [];
|
|
2552
|
-
if (dataType && !entities.includes(dataType)) {
|
|
2553
|
-
entities.unshift(dataType);
|
|
2554
|
-
}
|
|
2555
|
-
const { operationCode, operationName } = await this.generateOperationComponents(
|
|
2556
|
-
dataType,
|
|
2557
|
-
operation,
|
|
2558
|
-
flags.auth,
|
|
2559
|
-
entities,
|
|
2560
|
-
false,
|
|
2561
|
-
null,
|
|
2460
|
+
/**
|
|
2461
|
+
* Helper method to add API namespaces with simplified parameters
|
|
2462
|
+
* @param featureName The name of the feature
|
|
2463
|
+
* @param name Namespace name
|
|
2464
|
+
* @param options API namespace configuration options
|
|
2465
|
+
*/
|
|
2466
|
+
addApiNamespace(featureName, name, options) {
|
|
2467
|
+
const importPath = this.getFeatureImportPath(
|
|
2468
|
+
featureName,
|
|
2469
|
+
"server",
|
|
2470
|
+
"middleware",
|
|
2562
2471
|
name
|
|
2563
2472
|
);
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
const { targetDirectory: operationsDir, importDirectory } = this.ensureTargetDirectory(feature, operationType);
|
|
2570
|
-
const importPath = `${importDirectory}/${operationName}`;
|
|
2571
|
-
this.generateOperationFile(
|
|
2572
|
-
operationsDir,
|
|
2573
|
-
operationName,
|
|
2574
|
-
operationCode,
|
|
2575
|
-
flags.force || false
|
|
2576
|
-
);
|
|
2577
|
-
const definition = this.getDefinition(
|
|
2578
|
-
operationName,
|
|
2579
|
-
feature,
|
|
2580
|
-
entities,
|
|
2581
|
-
"query",
|
|
2582
|
-
importPath,
|
|
2583
|
-
flags.auth
|
|
2584
|
-
);
|
|
2585
|
-
this.updateConfigWithCheck(
|
|
2586
|
-
configPath,
|
|
2587
|
-
"addQuery",
|
|
2588
|
-
operationName,
|
|
2589
|
-
definition,
|
|
2590
|
-
feature,
|
|
2591
|
-
flags.force || false
|
|
2592
|
-
);
|
|
2473
|
+
super.apiNamespace(name, {
|
|
2474
|
+
path: options.path,
|
|
2475
|
+
middlewareConfigFn: {
|
|
2476
|
+
import: name,
|
|
2477
|
+
from: `@src/${importPath}`
|
|
2593
2478
|
}
|
|
2594
|
-
);
|
|
2479
|
+
});
|
|
2480
|
+
return this;
|
|
2595
2481
|
}
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
auth: commonSchemas.auth,
|
|
2608
|
-
force: commonSchemas.force
|
|
2609
|
-
});
|
|
2610
|
-
|
|
2611
|
-
// src/generators/route/route-generator.ts
|
|
2612
|
-
var RouteGenerator = class extends EntityGeneratorBase {
|
|
2613
|
-
get entityType() {
|
|
2614
|
-
return CONFIG_TYPES.ROUTE;
|
|
2482
|
+
/**
|
|
2483
|
+
* Calculates the import path for a feature component
|
|
2484
|
+
* @param featureName The name of the feature
|
|
2485
|
+
* @param type The type of component (client, server, etc.)
|
|
2486
|
+
* @param subPath The sub-path within the feature directory
|
|
2487
|
+
* @param fileName The name of the file (optional, defaults to featureName)
|
|
2488
|
+
* @returns The calculated import path
|
|
2489
|
+
*/
|
|
2490
|
+
getFeatureImportPath(featureName, type, subPath, fileName) {
|
|
2491
|
+
const file = fileName || featureName;
|
|
2492
|
+
return `features/${featureName}/${type}/${subPath}/${file}`;
|
|
2615
2493
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
});
|
|
2494
|
+
/**
|
|
2495
|
+
* Converts a singular word to its plural form
|
|
2496
|
+
* @param word The singular word to pluralize
|
|
2497
|
+
* @returns The plural form of the word
|
|
2498
|
+
*/
|
|
2499
|
+
getPlural(word) {
|
|
2500
|
+
if (word.endsWith("y")) {
|
|
2501
|
+
return word.slice(0, -1) + "ies";
|
|
2502
|
+
} else if (word.endsWith("s") || word.endsWith("sh") || word.endsWith("ch") || word.endsWith("x") || word.endsWith("z")) {
|
|
2503
|
+
return word + "es";
|
|
2504
|
+
} else {
|
|
2505
|
+
return word + "s";
|
|
2506
|
+
}
|
|
2630
2507
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2508
|
+
/**
|
|
2509
|
+
* Configures all feature modules by scanning the features directory
|
|
2510
|
+
*/
|
|
2511
|
+
async configureFeatures() {
|
|
2512
|
+
const featuresDir = path8.join(process.cwd(), "src", "features");
|
|
2513
|
+
if (!fs3.existsSync(featuresDir)) {
|
|
2514
|
+
console.warn(
|
|
2515
|
+
"Features directory not found, skipping feature configuration"
|
|
2516
|
+
);
|
|
2517
|
+
return this;
|
|
2518
|
+
}
|
|
2519
|
+
const getAllFeatureFiles = (dir) => {
|
|
2520
|
+
let results = [];
|
|
2521
|
+
const list = fs3.readdirSync(dir, { withFileTypes: true });
|
|
2522
|
+
for (const entry of list) {
|
|
2523
|
+
const fullPath = path8.join(dir, entry.name);
|
|
2524
|
+
if (entry.isDirectory()) {
|
|
2525
|
+
results = results.concat(getAllFeatureFiles(fullPath));
|
|
2526
|
+
} else if (entry.isFile() && entry.name.endsWith(".wasp.ts")) {
|
|
2527
|
+
results.push(path8.relative(featuresDir, fullPath));
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
return results;
|
|
2636
2531
|
};
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
featurePath,
|
|
2658
|
-
flags.force || false
|
|
2659
|
-
);
|
|
2532
|
+
const featureFiles = getAllFeatureFiles(featuresDir);
|
|
2533
|
+
for (const file of featureFiles) {
|
|
2534
|
+
try {
|
|
2535
|
+
const featureName = path8.dirname(file);
|
|
2536
|
+
const modulePath = path8.join(
|
|
2537
|
+
process.cwd(),
|
|
2538
|
+
".wasp",
|
|
2539
|
+
"src",
|
|
2540
|
+
"features",
|
|
2541
|
+
file.replace(".ts", ".js")
|
|
2542
|
+
);
|
|
2543
|
+
const module = await import(modulePath);
|
|
2544
|
+
if (module.default) {
|
|
2545
|
+
module.default(this, featureName);
|
|
2546
|
+
}
|
|
2547
|
+
} catch (error) {
|
|
2548
|
+
console.error(`Failed to load feature module ${file}:`, error);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
return this;
|
|
2660
2552
|
}
|
|
2661
2553
|
/**
|
|
2662
|
-
*
|
|
2554
|
+
* Helper method to get the configuration for an action or query
|
|
2555
|
+
* @param name The operation name
|
|
2556
|
+
* @param importPath Import path (excluding `@src/` prefix), e.g. "features/dashboard/server/queries/getTasks"
|
|
2557
|
+
* @param entities Comma-separated list of entities (optional, defaults to datatype)
|
|
2558
|
+
* @param auth Require authentication (optional)
|
|
2663
2559
|
*/
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2560
|
+
getOperationConfig(name, importPath, entities, auth) {
|
|
2561
|
+
return {
|
|
2562
|
+
fn: {
|
|
2563
|
+
import: name,
|
|
2564
|
+
from: `@src/${importPath}`
|
|
2565
|
+
},
|
|
2566
|
+
entities,
|
|
2567
|
+
auth: auth || false
|
|
2568
|
+
};
|
|
2672
2569
|
}
|
|
2673
2570
|
};
|
|
2674
2571
|
export {
|
|
@@ -2677,42 +2574,10 @@ export {
|
|
|
2677
2574
|
ApiNamespaceGenerator,
|
|
2678
2575
|
App,
|
|
2679
2576
|
CrudGenerator,
|
|
2680
|
-
|
|
2577
|
+
FeatureGenerator,
|
|
2681
2578
|
JobGenerator,
|
|
2682
2579
|
QueryGenerator,
|
|
2683
2580
|
RouteGenerator,
|
|
2684
|
-
TemplateUtility,
|
|
2685
2581
|
WaspConfigGenerator,
|
|
2686
|
-
commonSchemas,
|
|
2687
|
-
copyDirectory,
|
|
2688
|
-
createWaspPlugin,
|
|
2689
|
-
ensureDirectoryExists,
|
|
2690
|
-
featureExists,
|
|
2691
|
-
findWaspRoot,
|
|
2692
|
-
generateIntersectionType,
|
|
2693
|
-
generateJsonTypeHandling,
|
|
2694
|
-
generateOmitType,
|
|
2695
|
-
generatePartialType,
|
|
2696
|
-
generatePickType,
|
|
2697
|
-
getAppRootDir,
|
|
2698
|
-
getConfigDir,
|
|
2699
|
-
getEntityMetadata,
|
|
2700
|
-
getFeatureDir,
|
|
2701
|
-
getFeatureImportPath,
|
|
2702
|
-
getFeatureTargetDir,
|
|
2703
|
-
getIdFields,
|
|
2704
|
-
getJsonFields,
|
|
2705
|
-
getOptionalFields,
|
|
2706
|
-
getPluginVersion,
|
|
2707
|
-
getRequiredFields,
|
|
2708
|
-
getRouteNameFromPath,
|
|
2709
|
-
getTemplatesDir,
|
|
2710
|
-
getTypedArrayTransformer,
|
|
2711
|
-
getTypedArrayValidator,
|
|
2712
|
-
getTypedValueTransformer,
|
|
2713
|
-
getTypedValueValidator,
|
|
2714
|
-
needsPrismaImport,
|
|
2715
|
-
normaliseFeaturePath,
|
|
2716
|
-
realFileSystem,
|
|
2717
2582
|
wasp
|
|
2718
2583
|
};
|