@ingenyus/swarm-wasp 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +229 -21
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/common/filesystem.d.ts +0 -14
  4. package/dist/common/filesystem.d.ts.map +1 -1
  5. package/dist/common/filesystem.js +123 -0
  6. package/dist/common/index.d.ts +0 -1
  7. package/dist/common/index.d.ts.map +1 -1
  8. package/dist/common/index.js +366 -0
  9. package/dist/common/prisma.js +140 -0
  10. package/dist/common/schemas.d.ts +8 -42
  11. package/dist/common/schemas.d.ts.map +1 -1
  12. package/dist/common/schemas.js +54 -0
  13. package/dist/common/templates.js +52 -0
  14. package/dist/generators/action/action-generator.d.ts +16 -29
  15. package/dist/generators/action/action-generator.d.ts.map +1 -1
  16. package/dist/generators/action/action-generator.js +1425 -0
  17. package/dist/generators/action/index.js +1425 -0
  18. package/dist/generators/action/schema.d.ts +11 -23
  19. package/dist/generators/action/schema.d.ts.map +1 -1
  20. package/dist/generators/action/schema.js +115 -0
  21. package/dist/generators/api/api-generator.d.ts +19 -26
  22. package/dist/generators/api/api-generator.d.ts.map +1 -1
  23. package/dist/generators/api/api-generator.js +1104 -0
  24. package/dist/generators/api/index.js +1104 -0
  25. package/dist/generators/api/schema.d.ts +13 -21
  26. package/dist/generators/api/schema.d.ts.map +1 -1
  27. package/dist/generators/api/schema.js +117 -0
  28. package/dist/generators/api-namespace/api-namespace-generator.d.ts +10 -17
  29. package/dist/generators/api-namespace/api-namespace-generator.d.ts.map +1 -1
  30. package/dist/generators/api-namespace/api-namespace-generator.js +1028 -0
  31. package/dist/generators/api-namespace/index.js +1028 -0
  32. package/dist/generators/api-namespace/schema.d.ts +4 -12
  33. package/dist/generators/api-namespace/schema.d.ts.map +1 -1
  34. package/dist/generators/api-namespace/schema.js +89 -0
  35. package/dist/generators/base/{entity-generator.base.d.ts → component-generator.base.d.ts} +9 -9
  36. package/dist/generators/base/component-generator.base.d.ts.map +1 -0
  37. package/dist/generators/base/component-generator.base.js +931 -0
  38. package/dist/generators/base/index.d.ts +1 -1
  39. package/dist/generators/base/index.d.ts.map +1 -1
  40. package/dist/generators/base/index.js +1330 -0
  41. package/dist/generators/base/operation-generator.base.d.ts +12 -3
  42. package/dist/generators/base/operation-generator.base.d.ts.map +1 -1
  43. package/dist/generators/base/operation-generator.base.js +1331 -0
  44. package/dist/generators/base/wasp-generator.base.d.ts +2 -1
  45. package/dist/generators/base/wasp-generator.base.d.ts.map +1 -1
  46. package/dist/generators/base/wasp-generator.base.js +706 -0
  47. package/dist/generators/config/config-generator.d.ts +7 -4
  48. package/dist/generators/config/config-generator.d.ts.map +1 -1
  49. package/dist/generators/config/config-generator.js +0 -0
  50. package/dist/generators/config/index.js +596 -0
  51. package/dist/generators/config/wasp-config-generator.d.ts +1 -1
  52. package/dist/generators/config/wasp-config-generator.d.ts.map +1 -1
  53. package/dist/generators/config/wasp-config-generator.js +596 -0
  54. package/dist/generators/crud/crud-generator.d.ts +34 -22
  55. package/dist/generators/crud/crud-generator.d.ts.map +1 -1
  56. package/dist/generators/crud/crud-generator.js +1550 -0
  57. package/dist/generators/crud/index.js +1550 -0
  58. package/dist/generators/crud/schema.d.ts +25 -18
  59. package/dist/generators/crud/schema.d.ts.map +1 -1
  60. package/dist/generators/crud/schema.js +133 -0
  61. package/dist/generators/feature/feature-generator.d.ts +20 -0
  62. package/dist/generators/feature/feature-generator.d.ts.map +1 -0
  63. package/dist/generators/feature/feature-generator.js +765 -0
  64. package/dist/generators/feature/index.d.ts +2 -0
  65. package/dist/generators/feature/index.d.ts.map +1 -0
  66. package/dist/generators/feature/index.js +765 -0
  67. package/dist/generators/feature/schema.d.ts +5 -0
  68. package/dist/generators/feature/schema.d.ts.map +1 -0
  69. package/dist/generators/feature/schema.js +86 -0
  70. package/dist/generators/index.d.ts +1 -1
  71. package/dist/generators/index.d.ts.map +1 -1
  72. package/dist/generators/index.js +2211 -0
  73. package/dist/generators/job/index.js +1099 -0
  74. package/dist/generators/job/job-generator.d.ts +12 -23
  75. package/dist/generators/job/job-generator.d.ts.map +1 -1
  76. package/dist/generators/job/job-generator.js +1099 -0
  77. package/dist/generators/job/schema.d.ts +6 -18
  78. package/dist/generators/job/schema.d.ts.map +1 -1
  79. package/dist/generators/job/schema.js +152 -0
  80. package/dist/generators/query/index.js +1425 -0
  81. package/dist/generators/query/query-generator.d.ts +16 -29
  82. package/dist/generators/query/query-generator.d.ts.map +1 -1
  83. package/dist/generators/query/query-generator.js +1425 -0
  84. package/dist/generators/query/schema.d.ts +11 -23
  85. package/dist/generators/query/schema.d.ts.map +1 -1
  86. package/dist/generators/query/schema.js +115 -0
  87. package/dist/generators/route/index.js +1038 -0
  88. package/dist/generators/route/route-generator.d.ts +11 -20
  89. package/dist/generators/route/route-generator.d.ts.map +1 -1
  90. package/dist/generators/route/route-generator.js +1038 -0
  91. package/dist/generators/route/schema.d.ts +5 -15
  92. package/dist/generators/route/schema.d.ts.map +1 -1
  93. package/dist/generators/route/schema.js +90 -0
  94. package/dist/index.d.ts +2 -10
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +1980 -2115
  97. package/dist/plugins/index.d.ts +2 -0
  98. package/dist/plugins/index.d.ts.map +1 -0
  99. package/dist/plugins/wasp.d.ts +3 -0
  100. package/dist/plugins/wasp.d.ts.map +1 -0
  101. package/dist/types/constants.d.ts +4 -22
  102. package/dist/types/constants.d.ts.map +1 -1
  103. package/dist/types/constants.js +8 -2
  104. package/dist/types/index.d.ts +2 -2
  105. package/dist/types/index.d.ts.map +1 -1
  106. package/dist/types/index.js +8 -2
  107. package/dist/wasp-config/app.d.ts +2 -1
  108. package/dist/wasp-config/app.d.ts.map +1 -1
  109. package/dist/wasp-config/app.js +357 -0
  110. package/dist/wasp-config/index.js +357 -0
  111. package/dist/wasp-config/stubs/index.js +48 -0
  112. package/package.json +5 -14
  113. package/dist/common/plugin.d.ts +0 -2
  114. package/dist/common/plugin.d.ts.map +0 -1
  115. package/dist/generators/args.types.d.ts +0 -85
  116. package/dist/generators/args.types.d.ts.map +0 -1
  117. package/dist/generators/base/entity-generator.base.d.ts.map +0 -1
  118. package/dist/generators/feature-directory/feature-directory-generator.d.ts +0 -18
  119. package/dist/generators/feature-directory/feature-directory-generator.d.ts.map +0 -1
  120. package/dist/generators/feature-directory/index.d.ts +0 -2
  121. package/dist/generators/feature-directory/index.d.ts.map +0 -1
  122. package/dist/generators/feature-directory/schema.d.ts +0 -8
  123. package/dist/generators/feature-directory/schema.d.ts.map +0 -1
  124. package/dist/plugin.d.ts +0 -6
  125. package/dist/plugin.d.ts.map +0 -1
  126. /package/dist/generators/{feature-directory → feature}/templates/feature.wasp.eta +0 -0
@@ -0,0 +1,1099 @@
1
+ // src/generators/job/job-generator.ts
2
+ import { capitalise as capitalise2, toCamelCase as toCamelCase2 } from "@ingenyus/swarm";
3
+
4
+ // src/types/constants.ts
5
+ var PLUGIN_NAME = "wasp";
6
+ var TYPE_DIRECTORIES = {
7
+ component: "client/components",
8
+ hook: "client/hooks",
9
+ layout: "client/layouts",
10
+ page: "client/pages",
11
+ util: "client/utils",
12
+ action: "server/actions",
13
+ query: "server/queries",
14
+ middleware: "server/middleware",
15
+ job: "server/jobs",
16
+ api: "server/apis",
17
+ crud: "server/cruds",
18
+ type: "types"
19
+ };
20
+ var CONFIG_TYPES = {
21
+ ROUTE: "Route",
22
+ QUERY: "Query",
23
+ ACTION: "Action",
24
+ JOB: "Job",
25
+ API: "Api",
26
+ API_NAMESPACE: "ApiNamespace",
27
+ CRUD: "Crud"
28
+ };
29
+
30
+ // src/generators/base/component-generator.base.ts
31
+ import {
32
+ hasHelperMethodCall,
33
+ logger as singletonLogger4,
34
+ toCamelCase,
35
+ toKebabCase as toKebabCase2,
36
+ validateFeaturePath as validateFeaturePath3
37
+ } from "@ingenyus/swarm";
38
+ import path6 from "path";
39
+
40
+ // src/common/filesystem.ts
41
+ import { toPascalCase, validateFeaturePath } from "@ingenyus/swarm";
42
+ import fs from "fs";
43
+ import path from "path";
44
+ var realFileSystem = {
45
+ readFileSync: fs.readFileSync,
46
+ writeFileSync: fs.writeFileSync,
47
+ existsSync: fs.existsSync,
48
+ copyFileSync: fs.copyFileSync,
49
+ mkdirSync: fs.mkdirSync,
50
+ readdirSync: fs.readdirSync,
51
+ statSync: fs.statSync
52
+ };
53
+ function findWaspRoot(fileSystem, startDir = process.cwd()) {
54
+ const startDirPath = path.resolve(startDir);
55
+ let currentDirPath = startDirPath;
56
+ const root = path.parse(currentDirPath).root;
57
+ while (currentDirPath !== root) {
58
+ const waspRootPath = path.join(currentDirPath, ".wasproot");
59
+ if (fileSystem.existsSync(waspRootPath)) {
60
+ return currentDirPath;
61
+ }
62
+ currentDirPath = path.dirname(currentDirPath);
63
+ }
64
+ throw new Error(
65
+ `Couldn't find Wasp application root from ${startDirPath}. Make sure you are running this command from within a Wasp project directory.`
66
+ );
67
+ }
68
+ function ensureDirectoryExists(fileSystem, dir) {
69
+ if (!fileSystem.existsSync(dir)) {
70
+ fileSystem.mkdirSync(dir, { recursive: true });
71
+ }
72
+ }
73
+ function normaliseFeaturePath(featurePath) {
74
+ const segments = validateFeaturePath(featurePath);
75
+ const normalisedSegments = [];
76
+ for (let i = 0; i < segments.length; i++) {
77
+ const segment = segments[i];
78
+ const previousSegment = normalisedSegments[normalisedSegments.length - 1];
79
+ if (previousSegment !== "features" && segment !== "features") {
80
+ normalisedSegments.push("features");
81
+ }
82
+ normalisedSegments.push(segment);
83
+ }
84
+ return normalisedSegments.join("/");
85
+ }
86
+ function getFeatureDir(fileSystem, featureName) {
87
+ const waspRoot = findWaspRoot(fileSystem);
88
+ const normalisedPath = normaliseFeaturePath(featureName);
89
+ return path.join(waspRoot, "src", normalisedPath);
90
+ }
91
+
92
+ // src/common/prisma.ts
93
+ import {
94
+ getSchema
95
+ } from "@mrleebo/prisma-ast";
96
+ import fs2 from "fs";
97
+ import path2 from "path";
98
+
99
+ // src/common/schemas.ts
100
+ import { commandRegistry } from "@ingenyus/swarm";
101
+ import { z } from "zod";
102
+ var commonSchemas = {
103
+ feature: z.string().min(1, "Feature is required").meta({
104
+ description: "The feature directory this component will be generated in"
105
+ }).register(commandRegistry, {
106
+ shortName: "f",
107
+ examples: ["root", "auth", "dashboard/users"],
108
+ helpText: "Can be nested as a logical or relative path, e.g. 'dashboard/users' or 'features/dashboard/features/users'"
109
+ }),
110
+ name: z.string().min(1, "Name is required").meta({ description: "The name of the generated component" }).register(commandRegistry, {
111
+ shortName: "n",
112
+ examples: ["users", "task"],
113
+ helpText: "Will be used for generated files and configuration entries"
114
+ }),
115
+ target: z.string().min(1, "Target directory is required").meta({ description: "The target path of the generated directory" }).register(commandRegistry, {
116
+ shortName: "t",
117
+ examples: ["dashboard/users", "features/dashboard/features/users"],
118
+ helpText: "A logical or relative path, e.g. 'dashboard/users' or 'features/dashboard/features/users'"
119
+ }),
120
+ path: z.string().min(1, "Path is required").meta({ description: "The path that this component will be accessible at" }).register(commandRegistry, {
121
+ shortName: "p",
122
+ examples: ["/api/users/:id", "/api/products"],
123
+ helpText: "Supports Express-style placeholders, e.g. '/api/users/:id'"
124
+ }),
125
+ dataType: z.string().min(1, "Data type is required").meta({ description: "The data type/model name for this operation" }).register(commandRegistry, {
126
+ shortName: "d",
127
+ examples: ["User", "Product", "Task"],
128
+ helpText: "The Wasp entity or model name this operation will interact with"
129
+ }),
130
+ entities: z.array(z.string()).optional().meta({
131
+ description: "The Wasp entities that will be available to this component (optional)"
132
+ }).register(commandRegistry, {
133
+ shortName: "e",
134
+ examples: ["User", "User Task"],
135
+ helpText: "An array of Wasp entity names"
136
+ }),
137
+ force: z.boolean().optional().meta({
138
+ description: "Force overwrite of existing files and configuration entries (optional)"
139
+ }).register(commandRegistry, {
140
+ shortName: "F",
141
+ helpText: "CAUTION: Will overwrite existing files and configuration entries with current parameters"
142
+ }),
143
+ auth: z.boolean().optional().meta({
144
+ description: "Require authentication for this component (optional)"
145
+ }).register(commandRegistry, {
146
+ shortName: "a",
147
+ helpText: "Will generate authentication checks"
148
+ })
149
+ };
150
+
151
+ // src/common/templates.ts
152
+ import { toKebabCase } from "@ingenyus/swarm";
153
+ import { Eta } from "eta";
154
+ import path3 from "path";
155
+ var TemplateUtility = class {
156
+ constructor(fileSystem) {
157
+ this.fileSystem = fileSystem;
158
+ }
159
+ processTemplate(templatePath, replacements) {
160
+ const declarations = Object.keys(replacements).map((key) => `${key}=it.${key}`).join(", ");
161
+ const functionHeader = declarations ? `const ${declarations};` : void 0;
162
+ const templateDir = path3.dirname(templatePath);
163
+ const eta = new Eta({
164
+ autoTrim: false,
165
+ autoEscape: false,
166
+ views: templateDir,
167
+ functionHeader
168
+ });
169
+ const templateName = path3.basename(templatePath).replace(/\.eta$/, "");
170
+ if (this.fileSystem.existsSync(templatePath)) {
171
+ return eta.render(templateName, replacements);
172
+ } else {
173
+ const template = this.fileSystem.readFileSync(templatePath, "utf8");
174
+ return eta.renderString(template, replacements);
175
+ }
176
+ }
177
+ /**
178
+ * Helper method to resolve template paths for concrete generators
179
+ * @param relativePath - The relative path to the template file
180
+ * @param generatorName - The name of the generator (e.g., 'api', 'job')
181
+ * @param currentFileUrl - The import.meta.url from the concrete generator class
182
+ * @returns The full path to the template file
183
+ */
184
+ resolveTemplatePath(relativePath, generatorName, currentFileUrl) {
185
+ const generatorDirName = toKebabCase(generatorName);
186
+ const currentFilePath = new URL(currentFileUrl).pathname;
187
+ const currentFileDir = path3.dirname(currentFilePath);
188
+ const currentFileName = path3.basename(currentFilePath);
189
+ const isInstalledPackage = currentFileDir.includes("node_modules") && currentFileDir.endsWith("/dist") && currentFileName === "index.js";
190
+ const startDir = isInstalledPackage ? currentFileDir : path3.dirname(path3.dirname(currentFileDir));
191
+ return path3.join(
192
+ startDir,
193
+ "generators",
194
+ generatorDirName,
195
+ "templates",
196
+ relativePath
197
+ );
198
+ }
199
+ };
200
+
201
+ // src/generators/feature/feature-generator.ts
202
+ import {
203
+ handleFatalError as handleFatalError2,
204
+ logger as singletonLogger3,
205
+ validateFeaturePath as validateFeaturePath2
206
+ } from "@ingenyus/swarm";
207
+ import path5 from "path";
208
+
209
+ // src/generators/base/wasp-generator.base.ts
210
+ import {
211
+ GeneratorBase,
212
+ logger as singletonLogger2,
213
+ SwarmConfigManager,
214
+ TemplateResolver
215
+ } from "@ingenyus/swarm";
216
+
217
+ // src/generators/config/wasp-config-generator.ts
218
+ import {
219
+ handleFatalError,
220
+ parseHelperMethodDefinition,
221
+ logger as singletonLogger
222
+ } from "@ingenyus/swarm";
223
+ import path4 from "path";
224
+ var WaspConfigGenerator = class {
225
+ constructor(logger = singletonLogger, fileSystem = realFileSystem) {
226
+ this.logger = logger;
227
+ this.fileSystem = fileSystem;
228
+ this.templateUtility = new TemplateUtility(fileSystem);
229
+ }
230
+ path = path4;
231
+ templateUtility;
232
+ /**
233
+ * Gets the template path for feature config templates.
234
+ * Feature config templates are located in the feature generator's templates directory.
235
+ * @param templateName - The name of the template file (e.g., 'feature.wasp.eta')
236
+ * @returns The full path to the template file
237
+ */
238
+ getTemplatePath(templateName) {
239
+ return this.templateUtility.resolveTemplatePath(
240
+ templateName,
241
+ "feature",
242
+ import.meta.url
243
+ );
244
+ }
245
+ /**
246
+ * Generate a TypeScript Wasp config file in a feature directory
247
+ * @param featurePath - The feature directory path
248
+ */
249
+ generate(featurePath) {
250
+ const featureDir = getFeatureDir(this.fileSystem, featurePath);
251
+ if (!this.fileSystem.existsSync(featureDir)) {
252
+ this.fileSystem.mkdirSync(featureDir, { recursive: true });
253
+ }
254
+ const templatePath = this.getTemplatePath("feature.wasp.eta");
255
+ if (!this.fileSystem.existsSync(templatePath)) {
256
+ this.logger.error(`Template not found: ${templatePath}`);
257
+ return;
258
+ }
259
+ const configFilePath = path4.join(featureDir, `feature.wasp.ts`);
260
+ if (this.fileSystem.existsSync(configFilePath)) {
261
+ this.logger.warn(`Feature config already exists: ${configFilePath}`);
262
+ return;
263
+ }
264
+ this.fileSystem.copyFileSync(templatePath, configFilePath);
265
+ this.logger.success(`Generated feature config: ${configFilePath}`);
266
+ }
267
+ /**
268
+ * Updates or creates a feature configuration file with a pre-built declaration.
269
+ * @param featurePath - The path to the feature
270
+ * @param declaration - The pre-built declaration string to add or update
271
+ * @returns The updated feature configuration file
272
+ */
273
+ update(featurePath, declaration) {
274
+ const configDir = getFeatureDir(this.fileSystem, featurePath);
275
+ const configFilePath = path4.join(configDir, `feature.wasp.ts`);
276
+ if (!this.fileSystem.existsSync(configFilePath)) {
277
+ const templatePath = this.getTemplatePath("feature.wasp.eta");
278
+ if (!this.fileSystem.existsSync(templatePath)) {
279
+ handleFatalError(`Feature config template not found: ${templatePath}`);
280
+ }
281
+ this.fileSystem.copyFileSync(templatePath, configFilePath);
282
+ }
283
+ let content = this.fileSystem.readFileSync(configFilePath, "utf8");
284
+ content = this.normaliseSemicolons(content);
285
+ const parsed = parseHelperMethodDefinition(declaration);
286
+ if (!parsed) {
287
+ handleFatalError(`Could not parse definition: ${declaration}`);
288
+ return content;
289
+ }
290
+ const { methodName } = parsed;
291
+ const hadExistingDefinitions = this.hasExistingDefinitions(
292
+ content,
293
+ methodName
294
+ );
295
+ content = this.removeExistingDefinition(content, declaration);
296
+ const hasExistingDefinitions = this.hasExistingDefinitions(
297
+ content,
298
+ methodName
299
+ );
300
+ const lines = content.split("\n");
301
+ const configureFunctionStart = lines.findIndex(
302
+ (line) => line.trim().startsWith("export default function")
303
+ );
304
+ if (configureFunctionStart === -1) {
305
+ handleFatalError("Could not find configure function in feature config");
306
+ }
307
+ const appLineIndex = lines.findIndex(
308
+ (line, index) => index > configureFunctionStart && line.trim() === "app"
309
+ );
310
+ if (appLineIndex === -1) {
311
+ const insertIndex = configureFunctionStart + 1;
312
+ const itemsToInsert = [" app"];
313
+ const comment = this.getMethodComment(methodName);
314
+ itemsToInsert.push(` ${comment}`);
315
+ itemsToInsert.push(declaration.trimEnd());
316
+ lines.splice(insertIndex, 0, ...itemsToInsert);
317
+ } else {
318
+ const { insertIndex, addComment } = this.findGroupInsertionPoint(
319
+ lines,
320
+ methodName,
321
+ declaration,
322
+ hadExistingDefinitions || hasExistingDefinitions
323
+ );
324
+ const newLines = this.insertWithSpacing(
325
+ lines,
326
+ declaration,
327
+ insertIndex,
328
+ methodName,
329
+ addComment
330
+ );
331
+ const normalisedContent2 = this.normaliseSemicolons(newLines.join("\n"));
332
+ this.fileSystem.writeFileSync(configFilePath, normalisedContent2);
333
+ return configFilePath;
334
+ }
335
+ const normalisedContent = this.normaliseSemicolons(lines.join("\n"));
336
+ this.fileSystem.writeFileSync(configFilePath, normalisedContent);
337
+ return configFilePath;
338
+ }
339
+ /**
340
+ * Determines the insertion index for a method name based on alphabetical ordering
341
+ * of existing groups in the configuration file.
342
+ * @param groups - Object containing existing method groups
343
+ * @param methodName - The method name to find insertion index for
344
+ * @returns The insertion index for the method name
345
+ */
346
+ getInsertionIndexForMethod(groups, methodName) {
347
+ const existingMethods = Object.keys(groups).filter(
348
+ (method) => groups[method].length > 0
349
+ );
350
+ const allMethods = [...existingMethods, methodName].sort();
351
+ return allMethods.indexOf(methodName);
352
+ }
353
+ /**
354
+ * Gets the comment text for a method type.
355
+ * @param methodName The method name (e.g., 'addApi')
356
+ * @returns The comment text for the method type
357
+ */
358
+ getMethodComment(methodName) {
359
+ const entityName = methodName.startsWith("add") ? methodName.slice(3) : methodName;
360
+ return `// ${entityName} definitions`;
361
+ }
362
+ /**
363
+ * Finds the correct insertion point for a new configuration item.
364
+ * @param lines - Array of file lines
365
+ * @param methodName - The method name (e.g., 'addApi')
366
+ * @param definition - The definition string to parse for item name
367
+ * @returns Object with insertion index and whether to add a comment
368
+ */
369
+ findGroupInsertionPoint(lines, methodName, definition, hasExistingDefinitionsOfType) {
370
+ const appLineIndex = lines.findIndex((line) => line.trim() === "app");
371
+ if (appLineIndex === -1) {
372
+ return { insertIndex: appLineIndex + 1, addComment: false };
373
+ }
374
+ const methodCalls = [];
375
+ for (let i = appLineIndex + 1; i < lines.length; i++) {
376
+ const line = lines[i].trim();
377
+ if (line.startsWith(".") && line.includes("(")) {
378
+ let methodCallContent = line;
379
+ let j = i;
380
+ let closingParenCount = 0;
381
+ let foundClosingParen = false;
382
+ for (let k = 0; k < methodCallContent.length; k++) {
383
+ if (methodCallContent[k] === "(") closingParenCount++;
384
+ if (methodCallContent[k] === ")") closingParenCount--;
385
+ if (closingParenCount === 0 && methodCallContent[k] === ")") {
386
+ foundClosingParen = true;
387
+ break;
388
+ }
389
+ }
390
+ while (!foundClosingParen && j < lines.length - 1) {
391
+ j++;
392
+ methodCallContent += " " + lines[j].trim();
393
+ for (let k = 0; k < lines[j].length; k++) {
394
+ if (lines[j][k] === "(") closingParenCount++;
395
+ if (lines[j][k] === ")") closingParenCount--;
396
+ if (closingParenCount === 0 && lines[j][k] === ")") {
397
+ foundClosingParen = true;
398
+ break;
399
+ }
400
+ }
401
+ }
402
+ const match = methodCallContent.match(
403
+ /\.(\w+)\([^,]+,\s*['"`]([^'"`]+)['"`]/
404
+ );
405
+ if (match) {
406
+ methodCalls.push({
407
+ lineIndex: i,
408
+ endLineIndex: j,
409
+ methodName: match[1],
410
+ itemName: match[2]
411
+ });
412
+ }
413
+ }
414
+ }
415
+ const groups = {};
416
+ methodCalls.forEach((call) => {
417
+ if (!groups[call.methodName]) {
418
+ groups[call.methodName] = [];
419
+ }
420
+ groups[call.methodName].push({
421
+ lineIndex: call.lineIndex,
422
+ endLineIndex: call.endLineIndex,
423
+ itemName: call.itemName
424
+ });
425
+ });
426
+ const targetGroup = groups[methodName] || [];
427
+ if (targetGroup.length === 0) {
428
+ const targetGroupIndex = this.getInsertionIndexForMethod(
429
+ groups,
430
+ methodName
431
+ );
432
+ const existingMethods = Object.keys(groups).filter((method) => groups[method].length > 0).sort();
433
+ for (let i = targetGroupIndex; i < existingMethods.length; i++) {
434
+ const groupMethod = existingMethods[i];
435
+ if (groups[groupMethod] && groups[groupMethod].length > 0) {
436
+ const firstItem = groups[groupMethod][0];
437
+ let insertIndex = firstItem.lineIndex;
438
+ for (let j = firstItem.lineIndex - 1; j > appLineIndex; j--) {
439
+ const line = lines[j].trim();
440
+ if (line.startsWith("//") && line.includes("definitions")) {
441
+ insertIndex = j;
442
+ break;
443
+ } else if (line.startsWith(".") || line === "") {
444
+ continue;
445
+ } else {
446
+ break;
447
+ }
448
+ }
449
+ return { insertIndex, addComment: !hasExistingDefinitionsOfType };
450
+ }
451
+ }
452
+ for (let i = targetGroupIndex - 1; i >= 0; i--) {
453
+ const groupMethod = existingMethods[i];
454
+ if (groups[groupMethod] && groups[groupMethod].length > 0) {
455
+ const lastItem2 = groups[groupMethod][groups[groupMethod].length - 1];
456
+ return {
457
+ insertIndex: lastItem2.endLineIndex + 1,
458
+ addComment: !hasExistingDefinitionsOfType
459
+ };
460
+ }
461
+ }
462
+ return {
463
+ insertIndex: appLineIndex + 1,
464
+ addComment: !hasExistingDefinitionsOfType
465
+ };
466
+ }
467
+ const parsed = parseHelperMethodDefinition(definition);
468
+ if (!parsed) {
469
+ return { insertIndex: appLineIndex + 1, addComment: false };
470
+ }
471
+ const { firstParam: itemName } = parsed;
472
+ for (let i = 0; i < targetGroup.length; i++) {
473
+ if (itemName.localeCompare(targetGroup[i].itemName) < 0) {
474
+ return { insertIndex: targetGroup[i].lineIndex, addComment: false };
475
+ }
476
+ }
477
+ const lastItem = targetGroup[targetGroup.length - 1];
478
+ return { insertIndex: lastItem.endLineIndex + 1, addComment: false };
479
+ }
480
+ /**
481
+ * Inserts a definition with optional comment header.
482
+ * @param lines - Array of file lines
483
+ * @param declaration - The declaration to insert
484
+ * @param insertIndex - The index where to insert
485
+ * @param methodName - The method name for comment generation
486
+ * @param addComment - Whether to add a comment before the declaration
487
+ * @returns The modified lines array
488
+ */
489
+ insertWithSpacing(lines, declaration, insertIndex, methodName, addComment = false) {
490
+ const newLines = [...lines];
491
+ if (addComment) {
492
+ const comment = this.getMethodComment(methodName);
493
+ newLines.splice(insertIndex, 0, ` ${comment}`);
494
+ insertIndex += 1;
495
+ }
496
+ newLines.splice(insertIndex, 0, declaration.trimEnd());
497
+ return newLines;
498
+ }
499
+ /**
500
+ * Checks if there are any existing definitions of a specific type in the content.
501
+ * @param content - The file content to search
502
+ * @param methodName - The method name to check for (e.g., 'addJob', 'addApi')
503
+ * @returns true if there are existing definitions of this type, false otherwise
504
+ */
505
+ hasExistingDefinitions(content, methodName) {
506
+ const lines = content.split("\n");
507
+ for (const line of lines) {
508
+ if (line.trim().startsWith(`.${methodName}(`)) {
509
+ return true;
510
+ }
511
+ }
512
+ return false;
513
+ }
514
+ /**
515
+ * Removes an existing definition from the content by finding the helper method call
516
+ * and removing the entire method call block.
517
+ * @param content - The file content
518
+ * @param definition - The new definition to find the existing one from
519
+ * @returns The content with the existing definition removed
520
+ */
521
+ removeExistingDefinition(content, definition) {
522
+ const parsed = parseHelperMethodDefinition(definition);
523
+ if (!parsed) {
524
+ return content;
525
+ }
526
+ const { methodName, firstParam } = parsed;
527
+ let contentLines = content.split("\n");
528
+ let openingLineIndex = -1;
529
+ for (let i = 0; i < contentLines.length; i++) {
530
+ const line = contentLines[i];
531
+ if (line.trim().startsWith(`.${methodName}(`)) {
532
+ if (firstParam && line.includes(firstParam)) {
533
+ openingLineIndex = i;
534
+ break;
535
+ }
536
+ }
537
+ }
538
+ if (openingLineIndex === -1) {
539
+ return content;
540
+ }
541
+ let closingLineIndex = -1;
542
+ let parenCount = 0;
543
+ let braceCount = 0;
544
+ let foundOpening = false;
545
+ for (let i = openingLineIndex; i < contentLines.length; i++) {
546
+ const line = contentLines[i];
547
+ for (const char of line) {
548
+ if (char === "(") {
549
+ parenCount++;
550
+ foundOpening = true;
551
+ } else if (char === ")") {
552
+ parenCount--;
553
+ if (foundOpening && parenCount === 0 && braceCount === 0) {
554
+ closingLineIndex = i;
555
+ break;
556
+ }
557
+ } else if (char === "{") {
558
+ braceCount++;
559
+ } else if (char === "}") {
560
+ braceCount--;
561
+ }
562
+ }
563
+ if (closingLineIndex !== -1) {
564
+ break;
565
+ }
566
+ }
567
+ if (closingLineIndex === -1) {
568
+ this.logger.warn(
569
+ "Could not find closing parenthesis for existing definition"
570
+ );
571
+ return content;
572
+ }
573
+ contentLines.splice(
574
+ openingLineIndex,
575
+ closingLineIndex - openingLineIndex + 1
576
+ );
577
+ return contentLines.join("\n");
578
+ }
579
+ /**
580
+ * Adds a definition to the content by finding the appropriate place to insert it.
581
+ * @param content - The current file content
582
+ * @param definition - The definition to add
583
+ * @returns The updated content with the new definition
584
+ */
585
+ addDefinitionToContent(content, definition) {
586
+ const lines = content.split("\n");
587
+ const lastLineIndex = lines.length - 1;
588
+ let insertIndex = lastLineIndex;
589
+ for (let i = lastLineIndex; i >= 0; i--) {
590
+ const line = lines[i].trim();
591
+ if (line && !line.startsWith("}")) {
592
+ insertIndex = i;
593
+ break;
594
+ }
595
+ }
596
+ lines.splice(insertIndex + 1, 0, ` ${definition}`);
597
+ return lines.join("\n");
598
+ }
599
+ /**
600
+ * Normalises semicolons in the config file by removing them from method chain calls
601
+ * while preserving them in other contexts (imports, declarations, etc.).
602
+ * @param content - The file content to normalise
603
+ * @returns The normalised content
604
+ */
605
+ normaliseSemicolons(content) {
606
+ const lines = content.split("\n");
607
+ const configureFunctionStart = lines.findIndex(
608
+ (line) => line.trim().startsWith("export default function")
609
+ );
610
+ if (configureFunctionStart === -1) {
611
+ return content;
612
+ }
613
+ const appLineIndex = lines.findIndex(
614
+ (line, index) => index > configureFunctionStart && line.trim().startsWith("app")
615
+ );
616
+ if (appLineIndex === -1) {
617
+ return content;
618
+ }
619
+ let braceCount = 0;
620
+ let functionEndIndex = lines.length - 1;
621
+ for (let i = configureFunctionStart; i < lines.length; i++) {
622
+ const line = lines[i];
623
+ for (const char of line) {
624
+ if (char === "{") braceCount++;
625
+ if (char === "}") {
626
+ braceCount--;
627
+ if (braceCount === 0) {
628
+ functionEndIndex = i;
629
+ break;
630
+ }
631
+ }
632
+ }
633
+ if (braceCount === 0 && i > configureFunctionStart) {
634
+ break;
635
+ }
636
+ }
637
+ let lastMethodCallIndex = -1;
638
+ for (let i = appLineIndex + 1; i < functionEndIndex; i++) {
639
+ const line = lines[i];
640
+ const trimmed = line.trim();
641
+ if ((trimmed.endsWith(")") || trimmed.endsWith(");")) && !trimmed.startsWith("//")) {
642
+ lines[i] = line.replace(/;\s*$/, "");
643
+ lastMethodCallIndex = i;
644
+ }
645
+ }
646
+ if (lastMethodCallIndex !== -1 && !lines[lastMethodCallIndex].trim().endsWith(";")) {
647
+ lines[lastMethodCallIndex] = lines[lastMethodCallIndex] + ";";
648
+ }
649
+ return lines.join("\n");
650
+ }
651
+ };
652
+
653
+ // src/generators/base/wasp-generator.base.ts
654
+ var WaspGeneratorBase = class extends GeneratorBase {
655
+ constructor(fileSystem = realFileSystem, logger = singletonLogger2) {
656
+ super(fileSystem, logger);
657
+ this.fileSystem = fileSystem;
658
+ this.logger = logger;
659
+ this.configGenerator = new WaspConfigGenerator(logger, fileSystem);
660
+ this.templateUtility = new TemplateUtility(fileSystem);
661
+ this.templateResolver = new TemplateResolver(fileSystem);
662
+ }
663
+ configGenerator;
664
+ templateUtility;
665
+ templateResolver;
666
+ swarmConfig;
667
+ configLoaded = false;
668
+ // Plugin name from swarm.config.json
669
+ pluginName = PLUGIN_NAME;
670
+ async loadSwarmConfig() {
671
+ if (this.configLoaded) return;
672
+ const configManager = new SwarmConfigManager();
673
+ this.swarmConfig = await configManager.loadConfig();
674
+ this.configLoaded = true;
675
+ }
676
+ async getCustomTemplateDir() {
677
+ await this.loadSwarmConfig();
678
+ return this.swarmConfig?.templateDirectory;
679
+ }
680
+ /**
681
+ * Resolves template path with override support
682
+ */
683
+ async getTemplatePath(templateName) {
684
+ const defaultPath = this.getDefaultTemplatePath(templateName);
685
+ const customPath = await this.getCustomTemplateDir();
686
+ if (!customPath) {
687
+ return defaultPath;
688
+ }
689
+ const { path: resolvedPath, isCustom } = this.templateResolver.resolveTemplatePath(
690
+ this.pluginName,
691
+ this.name,
692
+ templateName,
693
+ defaultPath,
694
+ customPath
695
+ );
696
+ if (isCustom) {
697
+ this.logger.info(`Using custom template: ${resolvedPath}`);
698
+ }
699
+ return resolvedPath;
700
+ }
701
+ /**
702
+ * Processes a template and writes the result to a file
703
+ */
704
+ async renderTemplateToFile(templateName, replacements, outputPath, readableFileType, force) {
705
+ const templatePath = await this.getTemplatePath(templateName);
706
+ const fileExists = this.checkFileExists(
707
+ outputPath,
708
+ force,
709
+ readableFileType
710
+ );
711
+ const content = this.templateUtility.processTemplate(
712
+ templatePath,
713
+ replacements
714
+ );
715
+ this.writeFile(outputPath, content, readableFileType, fileExists);
716
+ return fileExists;
717
+ }
718
+ /**
719
+ * Generic existence check with force flag handling
720
+ * Consolidates the pattern used in both file and config checks
721
+ */
722
+ checkExistence(exists, itemDescription, force, errorMessage) {
723
+ if (exists && !force) {
724
+ this.logger.error(`${itemDescription}. Use --force to overwrite`);
725
+ throw new Error(errorMessage || itemDescription);
726
+ }
727
+ return exists;
728
+ }
729
+ /**
730
+ * Checks if a file exists and handles force flag logic
731
+ */
732
+ checkFileExists(filePath, force, fileType) {
733
+ const fileExists = this.fileSystem.existsSync(filePath);
734
+ return this.checkExistence(
735
+ fileExists,
736
+ `${fileType} already exists: ${filePath}`,
737
+ force,
738
+ `${fileType} already exists`
739
+ );
740
+ }
741
+ /**
742
+ * Safely writes a file with proper error handling and logging
743
+ */
744
+ writeFile(filePath, content, fileType, fileExists) {
745
+ this.fileSystem.writeFileSync(filePath, content);
746
+ this.logger.success(
747
+ `${fileExists ? "Overwrote" : "Generated"} ${fileType}: ${filePath}`
748
+ );
749
+ }
750
+ };
751
+
752
+ // src/generators/feature/schema.ts
753
+ import { z as z2 } from "zod";
754
+ var schema = z2.object({
755
+ target: commonSchemas.target
756
+ });
757
+
758
+ // src/generators/feature/feature-generator.ts
759
+ var FeatureGenerator = class extends WaspGeneratorBase {
760
+ constructor(logger = singletonLogger3, fileSystem = realFileSystem) {
761
+ super(fileSystem, logger);
762
+ this.logger = logger;
763
+ this.fileSystem = fileSystem;
764
+ this.name = "feature";
765
+ this.description = "Generates a feature directory containing a Wasp configuration file";
766
+ }
767
+ name;
768
+ description;
769
+ schema = schema;
770
+ getDefaultTemplatePath(templateName) {
771
+ return this.templateUtility.resolveTemplatePath(
772
+ templateName,
773
+ this.name,
774
+ import.meta.url
775
+ );
776
+ }
777
+ /**
778
+ * Generates a feature directory containing a Wasp configuration file
779
+ * @param target - The target path of the generated directory
780
+ */
781
+ async generate(args) {
782
+ const { target } = args;
783
+ const segments = validateFeaturePath2(target);
784
+ const normalisedPath = normaliseFeaturePath(target);
785
+ const sourceRoot = path5.join(findWaspRoot(this.fileSystem), "src");
786
+ if (segments.length > 1) {
787
+ const parentPath = segments.slice(0, -1).join("/");
788
+ const parentNormalisedPath = normaliseFeaturePath(parentPath);
789
+ const parentFeatureDir = path5.join(sourceRoot, parentNormalisedPath);
790
+ if (!this.fileSystem.existsSync(parentFeatureDir)) {
791
+ handleFatalError2(
792
+ `Parent feature '${parentPath}' does not exist. Please create it first.`
793
+ );
794
+ }
795
+ }
796
+ const featureDir = path5.join(sourceRoot, normalisedPath);
797
+ this.fileSystem.mkdirSync(featureDir, { recursive: true });
798
+ this.configGenerator.generate(normalisedPath);
799
+ this.logger.success(`Generated feature: ${normalisedPath}`);
800
+ }
801
+ };
802
+
803
+ // src/generators/base/component-generator.base.ts
804
+ var ComponentGeneratorBase = class extends WaspGeneratorBase {
805
+ constructor(logger = singletonLogger4, fileSystem = realFileSystem, featureDirectoryGenerator = new FeatureGenerator(logger, fileSystem)) {
806
+ super(fileSystem, logger);
807
+ this.logger = logger;
808
+ this.fileSystem = fileSystem;
809
+ this.featureDirectoryGenerator = featureDirectoryGenerator;
810
+ this.featureDirectoryGenerator = featureDirectoryGenerator;
811
+ }
812
+ getDefaultTemplatePath(templateName) {
813
+ return this.templateUtility.resolveTemplatePath(
814
+ templateName,
815
+ this.name,
816
+ import.meta.url
817
+ );
818
+ }
819
+ get name() {
820
+ return toKebabCase2(this.componentType);
821
+ }
822
+ /**
823
+ * Validates that the feature config file exists in the target or ancestor directories
824
+ */
825
+ validateFeatureConfig(featurePath) {
826
+ const normalisedPath = normaliseFeaturePath(featurePath);
827
+ const segments = normalisedPath.split("/");
828
+ for (let i = segments.length; i > 0; i--) {
829
+ const pathSegments = segments.slice(0, i);
830
+ const currentPath = pathSegments.join("/");
831
+ const featureName = pathSegments[pathSegments.length - 1];
832
+ const featureDir = getFeatureDir(this.fileSystem, currentPath);
833
+ const configPath = path6.join(featureDir, `feature.wasp.ts`);
834
+ if (this.fileSystem.existsSync(configPath)) {
835
+ return configPath;
836
+ }
837
+ }
838
+ this.logger.error(
839
+ `Feature config file not found in '${normalisedPath}' or any ancestor directories`
840
+ );
841
+ this.logger.error(
842
+ `Expected to find a feature.wasp.ts config file in one of the feature directories`
843
+ );
844
+ throw new Error("Feature config file not found");
845
+ }
846
+ /**
847
+ * Checks if a config item already exists in the feature config
848
+ */
849
+ checkConfigExists(configPath, methodName, itemName, force) {
850
+ const configContent = this.fileSystem.readFileSync(configPath, "utf8");
851
+ const configExists = hasHelperMethodCall(
852
+ configContent,
853
+ methodName,
854
+ itemName
855
+ );
856
+ return this.checkExistence(
857
+ configExists,
858
+ `${methodName} config already exists in ${configPath}`,
859
+ force,
860
+ `${methodName} config already exists`
861
+ );
862
+ }
863
+ /**
864
+ * Updates the feature config with a new definition
865
+ */
866
+ updateFeatureConfig(featurePath, definition, configPath, configExists, methodName) {
867
+ this.configGenerator.update(featurePath, definition);
868
+ this.logger.success(
869
+ `${configExists ? "Updated" : "Added"} ${methodName} config in: ${configPath}`
870
+ );
871
+ }
872
+ /**
873
+ * Consolidated helper for updating config files with existence check
874
+ * This replaces the duplicated updateConfigFile pattern in concrete generators
875
+ */
876
+ updateConfigWithCheck(configPath, methodName, entityName, definition, featurePath, force) {
877
+ const configExists = this.checkConfigExists(
878
+ configPath,
879
+ methodName,
880
+ entityName,
881
+ force
882
+ );
883
+ if (!configExists || force) {
884
+ this.updateFeatureConfig(
885
+ featurePath,
886
+ definition,
887
+ configPath,
888
+ configExists,
889
+ methodName
890
+ );
891
+ }
892
+ }
893
+ /**
894
+ * Gets the appropriate directory for a feature based on its path.
895
+ * @param fileSystem - The filesystem abstraction
896
+ * @param featurePath - The full feature path
897
+ * @param type - The type of file being generated
898
+ * @returns The target directory and import path
899
+ */
900
+ getFeatureTargetDir(fileSystem, featurePath, type) {
901
+ validateFeaturePath3(featurePath);
902
+ const normalisedPath = normaliseFeaturePath(featurePath);
903
+ const featureDir = getFeatureDir(fileSystem, normalisedPath);
904
+ const typeKey = type.toLowerCase();
905
+ const typeDirectory = TYPE_DIRECTORIES[typeKey];
906
+ const targetDirectory = path6.join(featureDir, typeDirectory);
907
+ const importDirectory = `@src/${normalisedPath}/${typeDirectory}`;
908
+ return { targetDirectory, importDirectory };
909
+ }
910
+ /**
911
+ * Ensures a target directory exists and returns its path
912
+ */
913
+ ensureTargetDirectory(featurePath, type) {
914
+ const { targetDirectory, importDirectory } = this.getFeatureTargetDir(
915
+ this.fileSystem,
916
+ featurePath,
917
+ type
918
+ );
919
+ ensureDirectoryExists(this.fileSystem, targetDirectory);
920
+ return { targetDirectory, importDirectory };
921
+ }
922
+ /**
923
+ * Generate middleware file for API or API namespace
924
+ */
925
+ async generateMiddlewareFile(targetFile, name, force) {
926
+ const replacements = {
927
+ name,
928
+ middlewareType: toCamelCase(this.componentType || "")
929
+ };
930
+ await this.renderTemplateToFile(
931
+ "middleware.eta",
932
+ replacements,
933
+ targetFile,
934
+ "Middleware file",
935
+ force
936
+ );
937
+ }
938
+ };
939
+
940
+ // src/generators/base/operation-generator.base.ts
941
+ import {
942
+ capitalise,
943
+ getPlural,
944
+ handleFatalError as handleFatalError3,
945
+ toPascalCase as toPascalCase2
946
+ } from "@ingenyus/swarm";
947
+
948
+ // src/generators/job/schema.ts
949
+ import { commandRegistry as commandRegistry2 } from "@ingenyus/swarm";
950
+ import { z as z3 } from "zod";
951
+ var cronSchema = z3.string().optional().refine(
952
+ (val) => {
953
+ if (!val) return true;
954
+ const parts = val.trim().split(/\s+/);
955
+ if (parts.length !== 5) return false;
956
+ const [minute, hour, day, month, weekday] = parts;
957
+ const validateCronField = (field, min, max) => {
958
+ if (field === "*") return true;
959
+ const rangeRegex = /^(\d+)(-(\d+))?(,(\d+)(-(\d+))?)*(\/(\d+))?$/;
960
+ if (!rangeRegex.test(field)) return false;
961
+ const items = field.split(",");
962
+ for (const item of items) {
963
+ if (item.includes("/")) {
964
+ const [base, step] = item.split("/");
965
+ const stepNum = parseInt(step, 10);
966
+ if (isNaN(stepNum) || stepNum <= 0) return false;
967
+ if (base === "*") continue;
968
+ const baseNum = parseInt(base, 10);
969
+ if (isNaN(baseNum) || baseNum < min || baseNum > max) return false;
970
+ } else if (item.includes("-")) {
971
+ const [start, end] = item.split("-");
972
+ const startNum = parseInt(start, 10);
973
+ const endNum = parseInt(end, 10);
974
+ if (isNaN(startNum) || isNaN(endNum) || startNum < min || endNum > max || startNum > endNum)
975
+ return false;
976
+ } else {
977
+ const num = parseInt(item, 10);
978
+ if (isNaN(num) || num < min || num > max) return false;
979
+ }
980
+ }
981
+ return true;
982
+ };
983
+ return validateCronField(minute, 0, 59) && validateCronField(hour, 0, 23) && validateCronField(day, 1, 31) && validateCronField(month, 1, 12) && validateCronField(weekday, 0, 6);
984
+ },
985
+ {
986
+ message: 'Cron expression must be a valid five-field format: (minute hour day month weekday), e.g. "0 9 * * *"'
987
+ }
988
+ ).meta({ description: "Cron schedule expression for the job" }).register(commandRegistry2, {
989
+ shortName: "c",
990
+ examples: ["0 9 * * *", "*/15 * * * *", "0 0 1 * *"],
991
+ helpText: "Five-field cron expression: minute hour day month weekday"
992
+ });
993
+ var argsSchema = z3.string().optional().refine(
994
+ (val) => {
995
+ if (!val) return true;
996
+ try {
997
+ const parsed = JSON.parse(val);
998
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed);
999
+ } catch {
1000
+ return false;
1001
+ }
1002
+ },
1003
+ {
1004
+ message: "Args must be a valid JSON object string"
1005
+ }
1006
+ ).meta({ description: "Arguments to pass to the job function when executed" }).register(commandRegistry2, {
1007
+ shortName: "a",
1008
+ examples: ['{"userId": 123}', '{"type": "cleanup", "batchSize": 100}'],
1009
+ helpText: "JSON object string that will be passed to the job function"
1010
+ });
1011
+ var schema2 = z3.object({
1012
+ feature: commonSchemas.feature,
1013
+ name: commonSchemas.name,
1014
+ entities: commonSchemas.entities,
1015
+ cron: cronSchema,
1016
+ args: argsSchema,
1017
+ force: commonSchemas.force
1018
+ });
1019
+
1020
+ // src/generators/job/job-generator.ts
1021
+ var JobGenerator = class extends ComponentGeneratorBase {
1022
+ get componentType() {
1023
+ return CONFIG_TYPES.JOB;
1024
+ }
1025
+ description = "Generates a Wasp Job";
1026
+ schema = schema2;
1027
+ async generate(args) {
1028
+ const jobName = toCamelCase2(args.name);
1029
+ return this.handleGeneratorError(this.componentType, jobName, async () => {
1030
+ const configPath = this.validateFeatureConfig(args.feature);
1031
+ const { targetDirectory } = this.ensureTargetDirectory(
1032
+ args.feature,
1033
+ this.componentType.toLowerCase()
1034
+ );
1035
+ const targetFile = `${targetDirectory}/${jobName}.ts`;
1036
+ await this.generateJobFile(targetFile, jobName, args);
1037
+ this.updateConfigFile(args.feature, jobName, args, configPath);
1038
+ });
1039
+ }
1040
+ async generateJobFile(targetFile, jobName, args) {
1041
+ const jobType = capitalise2(jobName);
1042
+ const entities = args.entities ?? [];
1043
+ let imports = `import type { ${jobType} } from 'wasp/server/jobs';
1044
+ `;
1045
+ if (entities.length > 0) {
1046
+ imports += `import { ${entities.join(", ")} } from 'wasp/entities';
1047
+ `;
1048
+ }
1049
+ const replacements = {
1050
+ imports,
1051
+ jobType,
1052
+ jobName
1053
+ };
1054
+ await this.renderTemplateToFile(
1055
+ "job.eta",
1056
+ replacements,
1057
+ targetFile,
1058
+ "job worker",
1059
+ args.force || false
1060
+ );
1061
+ }
1062
+ updateConfigFile(featurePath, jobName, args, configPath) {
1063
+ const {
1064
+ entities = [],
1065
+ cron = "0 0 * * *",
1066
+ args: executionArgs = "{}",
1067
+ force = false
1068
+ } = args;
1069
+ const definition = this.getDefinition(
1070
+ jobName,
1071
+ entities,
1072
+ cron,
1073
+ executionArgs
1074
+ );
1075
+ this.updateConfigWithCheck(
1076
+ configPath,
1077
+ "job",
1078
+ jobName,
1079
+ definition,
1080
+ featurePath,
1081
+ force
1082
+ );
1083
+ }
1084
+ /**
1085
+ * Generates a job definition for the feature configuration.
1086
+ */
1087
+ getDefinition(jobName, entities, cron, args) {
1088
+ const templatePath = this.getDefaultTemplatePath("config/job.eta");
1089
+ return this.templateUtility.processTemplate(templatePath, {
1090
+ jobName,
1091
+ entities: entities.map((e) => `"${e}"`).join(", "),
1092
+ cron,
1093
+ args
1094
+ });
1095
+ }
1096
+ };
1097
+ export {
1098
+ JobGenerator
1099
+ };