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