@soapjs/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nvmrc +1 -0
- package/LICENSE +21 -0
- package/README.md +360 -0
- package/build/cli.d.ts +3 -0
- package/build/cli.js +50 -0
- package/build/commands/add/add.command.d.ts +2 -0
- package/build/commands/add/add.command.js +709 -0
- package/build/commands/add/command-plan.d.ts +15 -0
- package/build/commands/add/command-plan.js +182 -0
- package/build/commands/add/entity-plan.d.ts +7 -0
- package/build/commands/add/entity-plan.js +106 -0
- package/build/commands/add/event-plan.d.ts +8 -0
- package/build/commands/add/event-plan.js +59 -0
- package/build/commands/add/query-plan.d.ts +10 -0
- package/build/commands/add/query-plan.js +156 -0
- package/build/commands/add/repository-plan.d.ts +11 -0
- package/build/commands/add/repository-plan.js +252 -0
- package/build/commands/add/resource-plan.d.ts +52 -0
- package/build/commands/add/resource-plan.js +2031 -0
- package/build/commands/add/route-plan.d.ts +24 -0
- package/build/commands/add/route-plan.js +256 -0
- package/build/commands/add/socket-plan.d.ts +9 -0
- package/build/commands/add/socket-plan.js +81 -0
- package/build/commands/add/use-case-plan.d.ts +7 -0
- package/build/commands/add/use-case-plan.js +86 -0
- package/build/commands/check/check.command.d.ts +2 -0
- package/build/commands/check/check.command.js +113 -0
- package/build/commands/create/create.command.d.ts +2 -0
- package/build/commands/create/create.command.js +234 -0
- package/build/commands/create/project-plan.d.ts +44 -0
- package/build/commands/create/project-plan.js +1430 -0
- package/build/commands/doctor/doctor.command.d.ts +2 -0
- package/build/commands/doctor/doctor.command.js +38 -0
- package/build/commands/generate/bruno-analysis.d.ts +19 -0
- package/build/commands/generate/bruno-analysis.js +51 -0
- package/build/commands/generate/bruno-plan.d.ts +6 -0
- package/build/commands/generate/bruno-plan.js +326 -0
- package/build/commands/generate/generate.command.d.ts +2 -0
- package/build/commands/generate/generate.command.js +130 -0
- package/build/commands/info/info.command.d.ts +2 -0
- package/build/commands/info/info.command.js +26 -0
- package/build/commands/remove/remove.command.d.ts +2 -0
- package/build/commands/remove/remove.command.js +328 -0
- package/build/commands/shared/common-options.d.ts +10 -0
- package/build/commands/shared/common-options.js +23 -0
- package/build/commands/update/update.command.d.ts +2 -0
- package/build/commands/update/update.command.js +155 -0
- package/build/config/auth-policy.d.ts +4 -0
- package/build/config/auth-policy.js +54 -0
- package/build/config/find-soap-root.d.ts +1 -0
- package/build/config/find-soap-root.js +22 -0
- package/build/config/load-soap-config.d.ts +2 -0
- package/build/config/load-soap-config.js +30 -0
- package/build/config/schemas/types.d.ts +127 -0
- package/build/config/schemas/types.js +2 -0
- package/build/config/schemas/validation.d.ts +5 -0
- package/build/config/schemas/validation.js +130 -0
- package/build/config/soap-config.service.d.ts +4 -0
- package/build/config/soap-config.service.js +24 -0
- package/build/config/write-soap-config.d.ts +8 -0
- package/build/config/write-soap-config.js +25 -0
- package/build/core/command-context.d.ts +20 -0
- package/build/core/command-context.js +30 -0
- package/build/core/errors.d.ts +6 -0
- package/build/core/errors.js +23 -0
- package/build/core/output.d.ts +12 -0
- package/build/core/output.js +30 -0
- package/build/core/result.d.ts +9 -0
- package/build/core/result.js +11 -0
- package/build/dependencies/dependency-resolver.d.ts +6 -0
- package/build/dependencies/dependency-resolver.js +68 -0
- package/build/dependencies/package-manager.d.ts +7 -0
- package/build/dependencies/package-manager.js +54 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +9 -0
- package/build/io/conflict-policy.d.ts +10 -0
- package/build/io/conflict-policy.js +32 -0
- package/build/io/file-writer.d.ts +19 -0
- package/build/io/file-writer.js +65 -0
- package/build/io/format-file.d.ts +1 -0
- package/build/io/format-file.js +13 -0
- package/build/presets/create-presets.d.ts +4 -0
- package/build/presets/create-presets.js +97 -0
- package/build/presets/index.d.ts +2 -0
- package/build/presets/index.js +18 -0
- package/build/presets/preset.types.d.ts +6 -0
- package/build/presets/preset.types.js +2 -0
- package/build/prompts/add-resource.prompt.d.ts +13 -0
- package/build/prompts/add-resource.prompt.js +80 -0
- package/build/prompts/add-route.prompt.d.ts +16 -0
- package/build/prompts/add-route.prompt.js +140 -0
- package/build/prompts/create-project.prompt.d.ts +11 -0
- package/build/prompts/create-project.prompt.js +156 -0
- package/build/prompts/generate-bruno.prompt.d.ts +7 -0
- package/build/prompts/generate-bruno.prompt.js +21 -0
- package/build/prompts/index.d.ts +8 -0
- package/build/prompts/index.js +24 -0
- package/build/prompts/inquirer-prompt-adapter.d.ts +8 -0
- package/build/prompts/inquirer-prompt-adapter.js +52 -0
- package/build/prompts/mock-prompt-adapter.d.ts +13 -0
- package/build/prompts/mock-prompt-adapter.js +60 -0
- package/build/prompts/prompt-adapter.d.ts +7 -0
- package/build/prompts/prompt-adapter.js +2 -0
- package/build/prompts/prompt.types.d.ts +26 -0
- package/build/prompts/prompt.types.js +2 -0
- package/build/registry/registry.service.d.ts +19 -0
- package/build/registry/registry.service.js +68 -0
- package/build/resolvers/add-resource.resolver.d.ts +23 -0
- package/build/resolvers/add-resource.resolver.js +73 -0
- package/build/resolvers/add-route.resolver.d.ts +34 -0
- package/build/resolvers/add-route.resolver.js +83 -0
- package/build/resolvers/create-config.resolver.d.ts +32 -0
- package/build/resolvers/create-config.resolver.js +57 -0
- package/build/resolvers/generate-bruno.resolver.d.ts +17 -0
- package/build/resolvers/generate-bruno.resolver.js +23 -0
- package/build/resolvers/index.d.ts +5 -0
- package/build/resolvers/index.js +21 -0
- package/build/resolvers/resolver.types.d.ts +8 -0
- package/build/resolvers/resolver.types.js +2 -0
- package/build/summary/create-summary.d.ts +2 -0
- package/build/summary/create-summary.js +24 -0
- package/build/summary/index.d.ts +1 -0
- package/build/summary/index.js +17 -0
- package/build/templates/naming.d.ts +11 -0
- package/build/templates/naming.js +30 -0
- package/build/templates/template-context.d.ts +6 -0
- package/build/templates/template-context.js +2 -0
- package/build/templates/template-engine.d.ts +1 -0
- package/build/templates/template-engine.js +10 -0
- package/build/templates/template-resolver.d.ts +2 -0
- package/build/templates/template-resolver.js +17 -0
- package/build/terminal/terminal-capabilities.d.ts +6 -0
- package/build/terminal/terminal-capabilities.js +14 -0
- package/docs/adr/0001-soap-cli-project-aware-generator.md +108 -0
- package/docs/cli/add-resource.md +127 -0
- package/docs/cli/add-route.md +79 -0
- package/docs/cli/bruno.md +58 -0
- package/docs/cli/create.md +73 -0
- package/docs/cli/index.md +92 -0
- package/docs/cli/interactive-mode.md +61 -0
- package/docs/cli/remove.md +45 -0
- package/docs/guides/auth.md +90 -0
- package/docs/guides/cqrs-events-realtime.md +100 -0
- package/docs/guides/index.md +24 -0
- package/docs/guides/quality-and-safety.md +88 -0
- package/docs/guides/regular-api.md +119 -0
- package/docs/guides/storage.md +101 -0
- package/docs/plans/interactive-mode-plan.md +601 -0
- package/package.json +44 -0
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerAddCommand = void 0;
|
|
4
|
+
const command_context_1 = require("../../core/command-context");
|
|
5
|
+
const errors_1 = require("../../core/errors");
|
|
6
|
+
const load_soap_config_1 = require("../../config/load-soap-config");
|
|
7
|
+
const write_soap_config_1 = require("../../config/write-soap-config");
|
|
8
|
+
const file_writer_1 = require("../../io/file-writer");
|
|
9
|
+
const naming_1 = require("../../templates/naming");
|
|
10
|
+
const auth_policy_1 = require("../../config/auth-policy");
|
|
11
|
+
const bruno_plan_1 = require("../generate/bruno-plan");
|
|
12
|
+
const command_plan_1 = require("./command-plan");
|
|
13
|
+
const entity_plan_1 = require("./entity-plan");
|
|
14
|
+
const event_plan_1 = require("./event-plan");
|
|
15
|
+
const query_plan_1 = require("./query-plan");
|
|
16
|
+
const repository_plan_1 = require("./repository-plan");
|
|
17
|
+
const resource_plan_1 = require("./resource-plan");
|
|
18
|
+
const route_plan_1 = require("./route-plan");
|
|
19
|
+
const socket_plan_1 = require("./socket-plan");
|
|
20
|
+
const use_case_plan_1 = require("./use-case-plan");
|
|
21
|
+
const common_options_1 = require("../shared/common-options");
|
|
22
|
+
const add_resource_resolver_1 = require("../../resolvers/add-resource.resolver");
|
|
23
|
+
const add_route_resolver_1 = require("../../resolvers/add-route.resolver");
|
|
24
|
+
const prompts_1 = require("../../prompts");
|
|
25
|
+
function registerAddCommand(program) {
|
|
26
|
+
const add = program.command("add").description("Add resources, routes, and project components.");
|
|
27
|
+
(0, common_options_1.addConflictOption)((0, common_options_1.addInteractiveOption)(add
|
|
28
|
+
.command("resource <name>")
|
|
29
|
+
.description("Add a resource to an existing SoapJS project.")
|
|
30
|
+
.option("--crud", "generate CRUD route placeholders", false)
|
|
31
|
+
.option("--db <database>", "resource storage target: none, mongo, postgres, mysql, sqlite, redis")
|
|
32
|
+
.option("--auth <auth>", "resource auth strategy: none, jwt, api-key, local")
|
|
33
|
+
.option("--zone <zone>", "API zone: public, private, admin", "public")
|
|
34
|
+
.option("--policy <policy>", "auth policy: admin, roles:a,b, custom:name, none")
|
|
35
|
+
.option("--field <field>", "resource field metadata as name:type or name:type:optional", collect, [])
|
|
36
|
+
.option("--crud-route <route>", "CRUD route override as operation:method:path[:auth][:zone][:bruno|no-bruno]", collect, [])
|
|
37
|
+
.option("--dry-run", "print the expanded resource plan without writing files", false)
|
|
38
|
+
.option("--bruno", "generate Bruno requests when Bruno is enabled", false)
|
|
39
|
+
.option("--enable-bruno", "enable Bruno API client before adding the resource", false)
|
|
40
|
+
.option("--yes", "run the expanded resource plan without prompting", false)
|
|
41
|
+
.option("--force", "overwrite generated files even when modified", false)
|
|
42
|
+
.option("--write-new", "write modified generated files as .new", false)))
|
|
43
|
+
.action(async (name, options, command) => {
|
|
44
|
+
(0, common_options_1.assertInteractiveTerminal)(options);
|
|
45
|
+
const rootContext = (0, command_context_1.getCommandContext)(command);
|
|
46
|
+
const context = { ...rootContext, dryRun: rootContext.dryRun || Boolean(options.dryRun) };
|
|
47
|
+
const config = await (0, load_soap_config_1.loadSoapConfig)(context.cwd);
|
|
48
|
+
const names = (0, naming_1.createNameVariants)(name);
|
|
49
|
+
if (config.registry.resources.some((resource) => resource.name === names.kebabName)) {
|
|
50
|
+
throw new errors_1.CliError(`Resource "${names.kebabName}" already exists.`);
|
|
51
|
+
}
|
|
52
|
+
const prompt = options.interactive ? new prompts_1.InquirerPromptAdapter() : undefined;
|
|
53
|
+
const promptAnswers = prompt
|
|
54
|
+
? await (0, prompts_1.promptAddResource)(prompt, {
|
|
55
|
+
config,
|
|
56
|
+
provided: createProvidedAddResourceOptions(command),
|
|
57
|
+
})
|
|
58
|
+
: undefined;
|
|
59
|
+
if (options.enableBruno || promptAnswers?.enableBruno) {
|
|
60
|
+
enableBruno(config);
|
|
61
|
+
}
|
|
62
|
+
const resolved = add_resource_resolver_1.addResourceResolver.resolve({
|
|
63
|
+
flags: createExplicitAddResourceFlags(options, command),
|
|
64
|
+
promptAnswers,
|
|
65
|
+
projectConfig: config,
|
|
66
|
+
});
|
|
67
|
+
const crudRoutes = (0, resource_plan_1.parseCrudRouteMatrix)(options.crudRoute);
|
|
68
|
+
const policy = (0, auth_policy_1.parseAuthPolicy)(options.policy);
|
|
69
|
+
assertAuthPolicyAllowed(policy, resolved.auth);
|
|
70
|
+
assertCrudRouteMatrixCapabilities(crudRoutes, resolved.auth, config.project.capabilities.auth, config.project.zones);
|
|
71
|
+
const resourcePlan = {
|
|
72
|
+
name,
|
|
73
|
+
crud: resolved.crud,
|
|
74
|
+
db: resolved.db,
|
|
75
|
+
auth: resolved.auth,
|
|
76
|
+
zone: resolved.zone,
|
|
77
|
+
featuresRoot: config.structure.featuresRoot,
|
|
78
|
+
architecture: config.project.architecture,
|
|
79
|
+
contracts: config.project.capabilities.contracts.includes("zod") ? "zod" : "plain",
|
|
80
|
+
fields: (0, resource_plan_1.parseResourceFieldDefinitions)(options.field),
|
|
81
|
+
crudRoutes,
|
|
82
|
+
policy,
|
|
83
|
+
};
|
|
84
|
+
const planningSummary = (0, resource_plan_1.createResourceAddPlanningSummary)({
|
|
85
|
+
...resourcePlan,
|
|
86
|
+
architecture: config.project.architecture,
|
|
87
|
+
});
|
|
88
|
+
if (context.dryRun || promptAnswers?.dryRunFirst) {
|
|
89
|
+
context.output.info((0, resource_plan_1.formatResourceAddPlanningSummary)(planningSummary));
|
|
90
|
+
context.output.success(`Planned resource ${names.kebabName} at /${names.pluralName}`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (options.interactive) {
|
|
94
|
+
context.output.info((0, resource_plan_1.formatResourceAddPlanningSummary)(planningSummary));
|
|
95
|
+
if (!options.yes) {
|
|
96
|
+
const confirmed = await prompt.confirm({
|
|
97
|
+
message: "Add resource?",
|
|
98
|
+
defaultValue: true,
|
|
99
|
+
});
|
|
100
|
+
if (!confirmed) {
|
|
101
|
+
context.output.warn("Resource generation aborted.");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const resource = (0, resource_plan_1.createResourceEntry)({
|
|
107
|
+
...resourcePlan,
|
|
108
|
+
});
|
|
109
|
+
config.registry.resources.push(resource);
|
|
110
|
+
config.registry.routes.push(...createRouteEntries(resource, resolved.auth, resolved.zone, crudRoutes));
|
|
111
|
+
const generatedPaths = config.registry.generatedFiles.map((file) => file.path);
|
|
112
|
+
const existingRouteControllerIndexes = routeControllerIndexResources(generatedPaths, config.structure.featuresRoot, "");
|
|
113
|
+
const usesCrudControllerIndex = resolved.crud;
|
|
114
|
+
const routeControllerIndexes = usesCrudControllerIndex
|
|
115
|
+
? [...existingRouteControllerIndexes, resource.name]
|
|
116
|
+
: existingRouteControllerIndexes;
|
|
117
|
+
const mainControllerResources = config.registry.resources
|
|
118
|
+
.filter((entry) => !routeControllerIndexes.includes(entry.name))
|
|
119
|
+
.map((entry) => entry.name);
|
|
120
|
+
const cqrsConfigFile = config.project.architecture === "cqrs" && resolved.crud
|
|
121
|
+
? (0, command_plan_1.createCqrsConfigFile)(config.structure.featuresRoot, {
|
|
122
|
+
commands: [
|
|
123
|
+
...config.registry.generatedFiles
|
|
124
|
+
.map((file) => (0, command_plan_1.commandFeatureFromPath)(file.path, config.structure.featuresRoot))
|
|
125
|
+
.filter((feature) => Boolean(feature)),
|
|
126
|
+
resource.name,
|
|
127
|
+
],
|
|
128
|
+
queries: [
|
|
129
|
+
...config.registry.generatedFiles
|
|
130
|
+
.map((file) => (0, query_plan_1.queryFeatureFromPath)(file.path, config.structure.featuresRoot))
|
|
131
|
+
.filter((feature) => Boolean(feature)),
|
|
132
|
+
resource.name,
|
|
133
|
+
],
|
|
134
|
+
})
|
|
135
|
+
: undefined;
|
|
136
|
+
const brunoEnabled = config.project.capabilities.apiClient.includes("bruno") || config.api.bruno.enabled;
|
|
137
|
+
const shouldGenerateBruno = options.bruno || promptAnswers?.bruno || (brunoEnabled && !options.interactive);
|
|
138
|
+
const brunoFiles = shouldGenerateBruno
|
|
139
|
+
? (0, bruno_plan_1.createBrunoFiles)(config)
|
|
140
|
+
: [];
|
|
141
|
+
const files = [
|
|
142
|
+
...(0, resource_plan_1.createResourceFiles)(resourcePlan),
|
|
143
|
+
(0, resource_plan_1.createResourcesFile)(config.registry.resources, config.structure.featuresRoot),
|
|
144
|
+
(0, resource_plan_1.createFeaturesIndexFile)(config.registry.resources, config.project.capabilities.auth),
|
|
145
|
+
(0, resource_plan_1.createControllersFile)(config.registry.resources, config.structure.featuresRoot, config.project.capabilities.auth, routeControllerIndexes, mainControllerResources),
|
|
146
|
+
...(cqrsConfigFile ? [cqrsConfigFile] : []),
|
|
147
|
+
...brunoFiles,
|
|
148
|
+
];
|
|
149
|
+
await (0, file_writer_1.writePlannedFiles)({
|
|
150
|
+
root: config.root,
|
|
151
|
+
files,
|
|
152
|
+
registry: config.registry,
|
|
153
|
+
force: options.force,
|
|
154
|
+
writeNew: options.writeNew,
|
|
155
|
+
onConflict: options.onConflict,
|
|
156
|
+
}, context);
|
|
157
|
+
await (0, write_soap_config_1.writeSoapConfig)(config.root, config, context);
|
|
158
|
+
context.output.success(`Added resource ${names.kebabName} at ${resource.path}`);
|
|
159
|
+
});
|
|
160
|
+
(0, common_options_1.addConflictOption)((0, common_options_1.addInteractiveOption)(add
|
|
161
|
+
.command("route [resource] [name]")
|
|
162
|
+
.description("Add a route to an existing SoapJS project.")
|
|
163
|
+
.option("--method <method>", "HTTP method: get, post, put, patch, delete, head, options", "get")
|
|
164
|
+
.option("--path <path>", "absolute path under the resource path, or relative path segment")
|
|
165
|
+
.option("--use-case <useCase>", "application use case to call from this route")
|
|
166
|
+
.option("--command <command>", "CQRS command to dispatch from this route")
|
|
167
|
+
.option("--query <query>", "CQRS query to dispatch from this route")
|
|
168
|
+
.option("--auth <auth>", "route auth strategy: none, jwt, api-key, local")
|
|
169
|
+
.option("--zone <zone>", "API zone: public, private, admin")
|
|
170
|
+
.option("--policy <policy>", "auth policy: admin, roles:a,b, custom:name, none")
|
|
171
|
+
.option("--bruno", "generate Bruno requests when Bruno is enabled", false)
|
|
172
|
+
.option("--force", "overwrite generated files even when modified", false)
|
|
173
|
+
.option("--write-new", "write modified generated files as .new", false)))
|
|
174
|
+
.action(async (resourceName, name, options, command) => {
|
|
175
|
+
(0, common_options_1.assertInteractiveTerminal)(options);
|
|
176
|
+
const context = (0, command_context_1.getCommandContext)(command);
|
|
177
|
+
const config = await (0, load_soap_config_1.loadSoapConfig)(context.cwd);
|
|
178
|
+
if (!options.interactive && (!resourceName || !name)) {
|
|
179
|
+
throw new errors_1.CliError("Resource and route name are required. Use `soap add route <resource> <name>`.");
|
|
180
|
+
}
|
|
181
|
+
if (options.interactive && config.registry.resources.length === 0) {
|
|
182
|
+
throw new errors_1.CliError("No resources found. Run `soap add resource <name>` first.");
|
|
183
|
+
}
|
|
184
|
+
const initialResourceNames = resourceName ? (0, naming_1.createNameVariants)(resourceName) : undefined;
|
|
185
|
+
const initialResource = initialResourceNames
|
|
186
|
+
? config.registry.resources.find((entry) => entry.name === initialResourceNames.kebabName)
|
|
187
|
+
: undefined;
|
|
188
|
+
if (resourceName && !initialResource) {
|
|
189
|
+
throw new errors_1.CliError(`Resource "${initialResourceNames.kebabName}" does not exist. Run \`soap add resource ${initialResourceNames.kebabName}\` first.`);
|
|
190
|
+
}
|
|
191
|
+
const prompt = options.interactive ? new prompts_1.InquirerPromptAdapter() : undefined;
|
|
192
|
+
const promptAnswers = prompt
|
|
193
|
+
? await (0, prompts_1.promptAddRoute)(prompt, {
|
|
194
|
+
config,
|
|
195
|
+
resource: initialResource,
|
|
196
|
+
resourceName,
|
|
197
|
+
routeName: name,
|
|
198
|
+
provided: createProvidedAddRouteOptions(command, Boolean(resourceName), Boolean(name)),
|
|
199
|
+
})
|
|
200
|
+
: undefined;
|
|
201
|
+
const resolvedResourceName = promptAnswers?.resourceName ?? initialResource.name;
|
|
202
|
+
const resolvedName = promptAnswers?.name ?? name;
|
|
203
|
+
const resourceNames = (0, naming_1.createNameVariants)(resolvedResourceName);
|
|
204
|
+
const routeNames = (0, naming_1.createNameVariants)(resolvedName);
|
|
205
|
+
const resource = config.registry.resources.find((entry) => entry.name === resourceNames.kebabName);
|
|
206
|
+
if (!resource) {
|
|
207
|
+
throw new errors_1.CliError(`Resource "${resourceNames.kebabName}" does not exist. Run \`soap add resource ${resourceNames.kebabName}\` first.`);
|
|
208
|
+
}
|
|
209
|
+
if (config.registry.routes.some((route) => route.resource === resource.name && route.name === routeNames.kebabName)) {
|
|
210
|
+
throw new errors_1.CliError(`Route "${routeNames.kebabName}" already exists on resource "${resource.name}".`);
|
|
211
|
+
}
|
|
212
|
+
const resolved = add_route_resolver_1.addRouteResolver.resolve({
|
|
213
|
+
flags: createExplicitAddRouteFlags(options, command),
|
|
214
|
+
promptAnswers,
|
|
215
|
+
projectConfig: { project: config, resource },
|
|
216
|
+
});
|
|
217
|
+
const policy = (0, auth_policy_1.parseAuthPolicy)(options.policy);
|
|
218
|
+
assertAuthPolicyAllowed(policy, resolved.auth);
|
|
219
|
+
const route = (0, route_plan_1.createRouteEntry)({
|
|
220
|
+
resource,
|
|
221
|
+
name: resolvedName,
|
|
222
|
+
method: resolved.method,
|
|
223
|
+
path: resolved.path,
|
|
224
|
+
useCase: resolved.useCase,
|
|
225
|
+
command: resolved.command,
|
|
226
|
+
query: resolved.query,
|
|
227
|
+
auth: resolved.auth,
|
|
228
|
+
zone: resolved.zone,
|
|
229
|
+
policy,
|
|
230
|
+
featuresRoot: config.structure.featuresRoot,
|
|
231
|
+
contracts: config.project.capabilities.contracts.includes("zod") ? "zod" : "plain",
|
|
232
|
+
});
|
|
233
|
+
config.registry.routes.push(route);
|
|
234
|
+
const routePlan = {
|
|
235
|
+
resource,
|
|
236
|
+
name: resolvedName,
|
|
237
|
+
method: resolved.method,
|
|
238
|
+
path: resolved.path,
|
|
239
|
+
useCase: resolved.useCase,
|
|
240
|
+
command: resolved.command,
|
|
241
|
+
query: resolved.query,
|
|
242
|
+
auth: resolved.auth,
|
|
243
|
+
zone: resolved.zone,
|
|
244
|
+
policy,
|
|
245
|
+
featuresRoot: config.structure.featuresRoot,
|
|
246
|
+
contracts: config.project.capabilities.contracts.includes("zod") ? "zod" : "plain",
|
|
247
|
+
};
|
|
248
|
+
const controllerFile = (0, route_plan_1.createRouteControllerFile)(routePlan);
|
|
249
|
+
const contractFile = (0, route_plan_1.createRouteContractFile)(routePlan);
|
|
250
|
+
const routeControllerNames = routeControllerNamesForResource(config.registry.generatedFiles.map((file) => file.path), config.structure.featuresRoot, resource.name, controllerFile.path);
|
|
251
|
+
const routeControllerIndex = (0, route_plan_1.createRouteControllersIndexFile)(resource, config.structure.featuresRoot, routeControllerNames);
|
|
252
|
+
const controllerIndexes = routeControllerIndexResources(config.registry.generatedFiles.map((file) => file.path), config.structure.featuresRoot, resource.name);
|
|
253
|
+
const controllersFile = (0, resource_plan_1.createControllersFile)(config.registry.resources, config.structure.featuresRoot, config.project.capabilities.auth, controllerIndexes);
|
|
254
|
+
const brunoEnabled = config.project.capabilities.apiClient.includes("bruno") || config.api.bruno.enabled;
|
|
255
|
+
const brunoFiles = options.bruno || promptAnswers?.bruno
|
|
256
|
+
? (0, bruno_plan_1.createBrunoFiles)(config)
|
|
257
|
+
: [];
|
|
258
|
+
await (0, file_writer_1.writePlannedFiles)({
|
|
259
|
+
root: config.root,
|
|
260
|
+
files: [
|
|
261
|
+
controllerFile,
|
|
262
|
+
contractFile,
|
|
263
|
+
routeControllerIndex,
|
|
264
|
+
controllersFile,
|
|
265
|
+
...(brunoEnabled ? brunoFiles : []),
|
|
266
|
+
],
|
|
267
|
+
registry: config.registry,
|
|
268
|
+
force: options.force,
|
|
269
|
+
writeNew: options.writeNew,
|
|
270
|
+
onConflict: options.onConflict,
|
|
271
|
+
}, context);
|
|
272
|
+
await (0, write_soap_config_1.writeSoapConfig)(config.root, config, context);
|
|
273
|
+
context.output.success(`Added route ${route.method} ${route.path}`);
|
|
274
|
+
});
|
|
275
|
+
add
|
|
276
|
+
.command("repository <name>")
|
|
277
|
+
.description("Add a repository port and database adapter to a feature.")
|
|
278
|
+
.requiredOption("--feature <feature>", "feature that owns the repository")
|
|
279
|
+
.requiredOption("--db <database>", "database adapter: mongo, postgres, mysql, sqlite")
|
|
280
|
+
.option("--force", "overwrite generated files even when modified", false)
|
|
281
|
+
.option("--write-new", "write modified generated files as .new", false)
|
|
282
|
+
.action(async (name, options, command) => {
|
|
283
|
+
const context = (0, command_context_1.getCommandContext)(command);
|
|
284
|
+
const config = await (0, load_soap_config_1.loadSoapConfig)(context.cwd);
|
|
285
|
+
const names = (0, naming_1.createNameVariants)(name);
|
|
286
|
+
const db = normalizeRepositoryDb(options.db);
|
|
287
|
+
assertCapability("database", db, config.project.capabilities.databases);
|
|
288
|
+
const files = (0, repository_plan_1.createRepositoryFiles)({
|
|
289
|
+
name,
|
|
290
|
+
feature: options.feature,
|
|
291
|
+
db,
|
|
292
|
+
featuresRoot: config.structure.featuresRoot,
|
|
293
|
+
});
|
|
294
|
+
await (0, file_writer_1.writePlannedFiles)({
|
|
295
|
+
root: config.root,
|
|
296
|
+
files,
|
|
297
|
+
registry: config.registry,
|
|
298
|
+
force: options.force,
|
|
299
|
+
writeNew: options.writeNew,
|
|
300
|
+
}, context);
|
|
301
|
+
await (0, write_soap_config_1.writeSoapConfig)(config.root, config, context);
|
|
302
|
+
context.output.success(`Added ${db} repository ${names.kebabName}`);
|
|
303
|
+
});
|
|
304
|
+
add
|
|
305
|
+
.command("entity <name>")
|
|
306
|
+
.description("Add a domain entity to a feature.")
|
|
307
|
+
.requiredOption("--feature <feature>", "feature that owns the entity")
|
|
308
|
+
.option("--force", "overwrite generated files even when modified", false)
|
|
309
|
+
.option("--write-new", "write modified generated files as .new", false)
|
|
310
|
+
.action(async (name, options, command) => {
|
|
311
|
+
const context = (0, command_context_1.getCommandContext)(command);
|
|
312
|
+
const config = await (0, load_soap_config_1.loadSoapConfig)(context.cwd);
|
|
313
|
+
const names = (0, naming_1.createNameVariants)(name);
|
|
314
|
+
const files = (0, entity_plan_1.createEntityFiles)({
|
|
315
|
+
name,
|
|
316
|
+
feature: options.feature,
|
|
317
|
+
featuresRoot: config.structure.featuresRoot,
|
|
318
|
+
});
|
|
319
|
+
await (0, file_writer_1.writePlannedFiles)({
|
|
320
|
+
root: config.root,
|
|
321
|
+
files,
|
|
322
|
+
registry: config.registry,
|
|
323
|
+
force: options.force,
|
|
324
|
+
writeNew: options.writeNew,
|
|
325
|
+
}, context);
|
|
326
|
+
await (0, write_soap_config_1.writeSoapConfig)(config.root, config, context);
|
|
327
|
+
context.output.success(`Added entity ${names.kebabName}`);
|
|
328
|
+
});
|
|
329
|
+
add
|
|
330
|
+
.command("use-case <name>")
|
|
331
|
+
.description("Add an application use case to a feature.")
|
|
332
|
+
.requiredOption("--feature <feature>", "feature that owns the use case")
|
|
333
|
+
.option("--force", "overwrite generated files even when modified", false)
|
|
334
|
+
.option("--write-new", "write modified generated files as .new", false)
|
|
335
|
+
.action(async (name, options, command) => {
|
|
336
|
+
const context = (0, command_context_1.getCommandContext)(command);
|
|
337
|
+
const config = await (0, load_soap_config_1.loadSoapConfig)(context.cwd);
|
|
338
|
+
const names = (0, naming_1.createNameVariants)(name);
|
|
339
|
+
const files = (0, use_case_plan_1.createUseCaseFiles)({
|
|
340
|
+
name,
|
|
341
|
+
feature: options.feature,
|
|
342
|
+
featuresRoot: config.structure.featuresRoot,
|
|
343
|
+
});
|
|
344
|
+
await (0, file_writer_1.writePlannedFiles)({
|
|
345
|
+
root: config.root,
|
|
346
|
+
files,
|
|
347
|
+
registry: config.registry,
|
|
348
|
+
force: options.force,
|
|
349
|
+
writeNew: options.writeNew,
|
|
350
|
+
}, context);
|
|
351
|
+
await (0, write_soap_config_1.writeSoapConfig)(config.root, config, context);
|
|
352
|
+
context.output.success(`Added use case ${names.kebabName}`);
|
|
353
|
+
});
|
|
354
|
+
add
|
|
355
|
+
.command("command <name>")
|
|
356
|
+
.description("Add a CQRS command and command handler to a feature.")
|
|
357
|
+
.requiredOption("--feature <feature>", "feature that owns the command")
|
|
358
|
+
.option("--force", "overwrite generated files even when modified", false)
|
|
359
|
+
.option("--write-new", "write modified generated files as .new", false)
|
|
360
|
+
.action(async (name, options, command) => {
|
|
361
|
+
const context = (0, command_context_1.getCommandContext)(command);
|
|
362
|
+
const config = await (0, load_soap_config_1.loadSoapConfig)(context.cwd);
|
|
363
|
+
const names = (0, naming_1.createNameVariants)(name);
|
|
364
|
+
const feature = (0, naming_1.createNameVariants)(options.feature).kebabName;
|
|
365
|
+
if (config.project.architecture !== "cqrs") {
|
|
366
|
+
throw new errors_1.CliError("CQRS commands require a project created with `--architecture cqrs`.");
|
|
367
|
+
}
|
|
368
|
+
const commandFiles = (0, command_plan_1.createCommandFiles)({
|
|
369
|
+
name,
|
|
370
|
+
feature,
|
|
371
|
+
featuresRoot: config.structure.featuresRoot,
|
|
372
|
+
});
|
|
373
|
+
const commandNames = commandNamesForFeature(config.registry.generatedFiles.map((file) => file.path), config.structure.featuresRoot, feature, names.kebabName);
|
|
374
|
+
const commandIndex = (0, command_plan_1.createCommandIndexFile)(feature, config.structure.featuresRoot, commandNames);
|
|
375
|
+
const cqrsFeatures = commandFeatures(config.registry.generatedFiles.map((file) => file.path), config.structure.featuresRoot, feature);
|
|
376
|
+
const cqrsConfig = (0, command_plan_1.createCqrsConfigFile)(config.structure.featuresRoot, {
|
|
377
|
+
commands: cqrsFeatures,
|
|
378
|
+
queries: queryFeatures(config.registry.generatedFiles.map((file) => file.path), config.structure.featuresRoot),
|
|
379
|
+
});
|
|
380
|
+
await (0, file_writer_1.writePlannedFiles)({
|
|
381
|
+
root: config.root,
|
|
382
|
+
files: [...commandFiles, commandIndex, cqrsConfig],
|
|
383
|
+
registry: config.registry,
|
|
384
|
+
force: options.force,
|
|
385
|
+
writeNew: options.writeNew,
|
|
386
|
+
}, context);
|
|
387
|
+
await (0, write_soap_config_1.writeSoapConfig)(config.root, config, context);
|
|
388
|
+
context.output.success(`Added command ${names.kebabName}`);
|
|
389
|
+
});
|
|
390
|
+
add
|
|
391
|
+
.command("query <name>")
|
|
392
|
+
.description("Add a CQRS query and query handler to a feature.")
|
|
393
|
+
.requiredOption("--feature <feature>", "feature that owns the query")
|
|
394
|
+
.option("--force", "overwrite generated files even when modified", false)
|
|
395
|
+
.option("--write-new", "write modified generated files as .new", false)
|
|
396
|
+
.action(async (name, options, command) => {
|
|
397
|
+
const context = (0, command_context_1.getCommandContext)(command);
|
|
398
|
+
const config = await (0, load_soap_config_1.loadSoapConfig)(context.cwd);
|
|
399
|
+
const names = (0, naming_1.createNameVariants)(name);
|
|
400
|
+
const feature = (0, naming_1.createNameVariants)(options.feature).kebabName;
|
|
401
|
+
if (config.project.architecture !== "cqrs") {
|
|
402
|
+
throw new errors_1.CliError("CQRS queries require a project created with `--architecture cqrs`.");
|
|
403
|
+
}
|
|
404
|
+
const queryFiles = (0, query_plan_1.createQueryFiles)({
|
|
405
|
+
name,
|
|
406
|
+
feature,
|
|
407
|
+
featuresRoot: config.structure.featuresRoot,
|
|
408
|
+
});
|
|
409
|
+
const queryNames = queryNamesForFeature(config.registry.generatedFiles.map((file) => file.path), config.structure.featuresRoot, feature, names.kebabName);
|
|
410
|
+
const queryIndex = (0, query_plan_1.createQueryIndexFile)(feature, config.structure.featuresRoot, queryNames);
|
|
411
|
+
const cqrsConfig = (0, command_plan_1.createCqrsConfigFile)(config.structure.featuresRoot, {
|
|
412
|
+
commands: commandFeatures(config.registry.generatedFiles.map((file) => file.path), config.structure.featuresRoot),
|
|
413
|
+
queries: queryFeatures(config.registry.generatedFiles.map((file) => file.path), config.structure.featuresRoot, feature),
|
|
414
|
+
});
|
|
415
|
+
await (0, file_writer_1.writePlannedFiles)({
|
|
416
|
+
root: config.root,
|
|
417
|
+
files: [...queryFiles, queryIndex, cqrsConfig],
|
|
418
|
+
registry: config.registry,
|
|
419
|
+
force: options.force,
|
|
420
|
+
writeNew: options.writeNew,
|
|
421
|
+
}, context);
|
|
422
|
+
await (0, write_soap_config_1.writeSoapConfig)(config.root, config, context);
|
|
423
|
+
context.output.success(`Added query ${names.kebabName}`);
|
|
424
|
+
});
|
|
425
|
+
add
|
|
426
|
+
.command("event <name>")
|
|
427
|
+
.description("Add a domain event to a feature.")
|
|
428
|
+
.requiredOption("--feature <feature>", "feature that owns the event")
|
|
429
|
+
.option("--force", "overwrite generated files even when modified", false)
|
|
430
|
+
.option("--write-new", "write modified generated files as .new", false)
|
|
431
|
+
.action(async (name, options, command) => {
|
|
432
|
+
const context = (0, command_context_1.getCommandContext)(command);
|
|
433
|
+
const config = await (0, load_soap_config_1.loadSoapConfig)(context.cwd);
|
|
434
|
+
if (config.project.capabilities.messaging.length === 0) {
|
|
435
|
+
throw new errors_1.CliError("Messaging is not enabled for this project.");
|
|
436
|
+
}
|
|
437
|
+
const files = (0, event_plan_1.createEventFiles)({
|
|
438
|
+
name,
|
|
439
|
+
feature: options.feature,
|
|
440
|
+
architecture: config.project.architecture,
|
|
441
|
+
featuresRoot: config.structure.featuresRoot,
|
|
442
|
+
});
|
|
443
|
+
await (0, file_writer_1.writePlannedFiles)({
|
|
444
|
+
root: config.root,
|
|
445
|
+
files,
|
|
446
|
+
registry: config.registry,
|
|
447
|
+
force: options.force,
|
|
448
|
+
writeNew: options.writeNew,
|
|
449
|
+
}, context);
|
|
450
|
+
await (0, write_soap_config_1.writeSoapConfig)(config.root, config, context);
|
|
451
|
+
context.output.success(`Added event ${(0, naming_1.createNameVariants)(name).kebabName}`);
|
|
452
|
+
});
|
|
453
|
+
add
|
|
454
|
+
.command("socket <name>")
|
|
455
|
+
.description("Add a WebSocket message handler to a feature.")
|
|
456
|
+
.requiredOption("--feature <feature>", "feature that owns the socket handler")
|
|
457
|
+
.option("--auth <auth>", "socket auth strategy placeholder: none, jwt, api-key, local", "none")
|
|
458
|
+
.option("--force", "overwrite generated files even when modified", false)
|
|
459
|
+
.option("--write-new", "write modified generated files as .new", false)
|
|
460
|
+
.action(async (name, options, command) => {
|
|
461
|
+
const context = (0, command_context_1.getCommandContext)(command);
|
|
462
|
+
const config = await (0, load_soap_config_1.loadSoapConfig)(context.cwd);
|
|
463
|
+
if (!config.project.capabilities.realtime.includes("ws")) {
|
|
464
|
+
throw new errors_1.CliError("WebSocket support is not enabled. Create the project with `--realtime ws` first.");
|
|
465
|
+
}
|
|
466
|
+
const auth = normalizeRouteAuth(options.auth ?? "none");
|
|
467
|
+
assertCapability("auth", auth, enabledRouteAuthValues(config.project.capabilities.auth));
|
|
468
|
+
const files = await (0, socket_plan_1.createSocketFiles)(config.root, {
|
|
469
|
+
name,
|
|
470
|
+
feature: options.feature,
|
|
471
|
+
auth,
|
|
472
|
+
featuresRoot: config.structure.featuresRoot,
|
|
473
|
+
});
|
|
474
|
+
await (0, file_writer_1.writePlannedFiles)({
|
|
475
|
+
root: config.root,
|
|
476
|
+
files,
|
|
477
|
+
registry: config.registry,
|
|
478
|
+
force: options.force,
|
|
479
|
+
writeNew: options.writeNew,
|
|
480
|
+
}, context);
|
|
481
|
+
await (0, write_soap_config_1.writeSoapConfig)(config.root, config, context);
|
|
482
|
+
context.output.success(`Added socket ${(0, naming_1.createNameVariants)(name).kebabName}`);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
exports.registerAddCommand = registerAddCommand;
|
|
486
|
+
function firstOrNone(values) {
|
|
487
|
+
return values[0] ?? "none";
|
|
488
|
+
}
|
|
489
|
+
function firstAuthForRoutes(values) {
|
|
490
|
+
if (values.includes("jwt") || values.includes("local")) {
|
|
491
|
+
return "jwt";
|
|
492
|
+
}
|
|
493
|
+
return values[0] ?? "none";
|
|
494
|
+
}
|
|
495
|
+
function normalizeRouteAuth(value) {
|
|
496
|
+
if (value === "local") {
|
|
497
|
+
return "jwt";
|
|
498
|
+
}
|
|
499
|
+
if (value === "jwt" || value === "api-key" || value === "none") {
|
|
500
|
+
return value;
|
|
501
|
+
}
|
|
502
|
+
throw new errors_1.CliError(`Auth "${value}" cannot protect routes. Use jwt, api-key, or none.`);
|
|
503
|
+
}
|
|
504
|
+
function normalizeRepositoryDb(value) {
|
|
505
|
+
if (value === "mongo" || value === "postgres" || value === "mysql" || value === "sqlite") {
|
|
506
|
+
return value;
|
|
507
|
+
}
|
|
508
|
+
throw new errors_1.CliError("Repository database must be mongo, postgres, mysql, or sqlite.");
|
|
509
|
+
}
|
|
510
|
+
function enabledRouteAuthValues(values) {
|
|
511
|
+
const allowed = ["none"];
|
|
512
|
+
if (values.includes("jwt") || values.includes("local")) {
|
|
513
|
+
allowed.push("jwt");
|
|
514
|
+
}
|
|
515
|
+
if (values.includes("api-key")) {
|
|
516
|
+
allowed.push("api-key");
|
|
517
|
+
}
|
|
518
|
+
return allowed;
|
|
519
|
+
}
|
|
520
|
+
function assertCapability(label, value, allowed) {
|
|
521
|
+
if (!allowed.includes(value)) {
|
|
522
|
+
const allowedValues = allowed.length > 0 ? allowed.join(", ") : "none";
|
|
523
|
+
throw new errors_1.CliError(`${label} "${value}" is not enabled for this project. Allowed values: ${allowedValues}.`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function createRouteEntries(resource, auth, zone, matrix = {}) {
|
|
527
|
+
const now = new Date().toISOString();
|
|
528
|
+
const routes = [
|
|
529
|
+
["list", "GET", resource.path],
|
|
530
|
+
["get", "GET", `${resource.path}/:id`],
|
|
531
|
+
];
|
|
532
|
+
if (resource.crud) {
|
|
533
|
+
routes.push(["create", "POST", resource.path], ["update", "PUT", `${resource.path}/:id`], ["delete", "DELETE", `${resource.path}/:id`]);
|
|
534
|
+
}
|
|
535
|
+
return routes.map(([name, defaultMethod, defaultPath]) => {
|
|
536
|
+
const override = matrix[name];
|
|
537
|
+
return {
|
|
538
|
+
resource: resource.name,
|
|
539
|
+
name,
|
|
540
|
+
method: (override?.method ?? defaultMethod).toUpperCase(),
|
|
541
|
+
path: resolveCrudRoutePath(resource.path, override?.path ?? defaultPath),
|
|
542
|
+
auth: normalizeRouteAuth(override?.auth ?? auth),
|
|
543
|
+
zone: override?.zone ?? zone,
|
|
544
|
+
policy: override?.policy ?? resource.policy,
|
|
545
|
+
bruno: override?.bruno ?? true,
|
|
546
|
+
generatedAt: now,
|
|
547
|
+
};
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
function assertCrudRouteMatrixCapabilities(matrix, defaultAuth, authCapabilities, zones) {
|
|
551
|
+
for (const config of Object.values(matrix)) {
|
|
552
|
+
if (!config) {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
const routeAuth = normalizeRouteAuth(config.auth ?? defaultAuth);
|
|
556
|
+
assertCapability("auth", routeAuth, enabledRouteAuthValues(authCapabilities));
|
|
557
|
+
assertAuthPolicyAllowed(config.policy, routeAuth);
|
|
558
|
+
if (config.zone) {
|
|
559
|
+
assertCapability("zone", config.zone, zones);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function assertAuthPolicyAllowed(policy, auth) {
|
|
564
|
+
if (!policy) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
if (normalizeRouteAuth(auth) === "none") {
|
|
568
|
+
throw new errors_1.CliError("Auth policy requires route auth. Use --auth jwt or --auth api-key.");
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function resolveCrudRoutePath(resourcePath, routePath) {
|
|
572
|
+
if (routePath === resourcePath || routePath.startsWith(`${resourcePath}/`)) {
|
|
573
|
+
return routePath;
|
|
574
|
+
}
|
|
575
|
+
if (routePath === "/") {
|
|
576
|
+
return resourcePath;
|
|
577
|
+
}
|
|
578
|
+
return `${resourcePath}${routePath.startsWith("/") ? routePath : `/${routePath}`}`;
|
|
579
|
+
}
|
|
580
|
+
function normalizeRouteMethod(value) {
|
|
581
|
+
const method = value.toLowerCase();
|
|
582
|
+
if (!route_plan_1.routeMethods.includes(method)) {
|
|
583
|
+
throw new errors_1.CliError(`Unsupported HTTP method "${value}". Allowed values: ${route_plan_1.routeMethods.join(", ")}.`);
|
|
584
|
+
}
|
|
585
|
+
return method;
|
|
586
|
+
}
|
|
587
|
+
function assertSingleRouteTarget(options) {
|
|
588
|
+
const targets = [options.useCase, options.command, options.query].filter(Boolean);
|
|
589
|
+
if (targets.length > 1) {
|
|
590
|
+
throw new errors_1.CliError("Route target options are mutually exclusive. Use only one of --use-case, --command, or --query.");
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function createProvidedAddResourceOptions(command) {
|
|
594
|
+
return {
|
|
595
|
+
crud: isCliOption(command, "crud"),
|
|
596
|
+
db: isCliOption(command, "db"),
|
|
597
|
+
auth: isCliOption(command, "auth"),
|
|
598
|
+
zone: isCliOption(command, "zone"),
|
|
599
|
+
bruno: isCliOption(command, "bruno"),
|
|
600
|
+
enableBruno: isCliOption(command, "enableBruno"),
|
|
601
|
+
dryRunFirst: isCliOption(command, "dryRun"),
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function createExplicitAddResourceFlags(options, command) {
|
|
605
|
+
return {
|
|
606
|
+
crud: isCliOption(command, "crud") ? options.crud : undefined,
|
|
607
|
+
db: isCliOption(command, "db") ? options.db : undefined,
|
|
608
|
+
auth: isCliOption(command, "auth") ? options.auth : undefined,
|
|
609
|
+
zone: isCliOption(command, "zone") ? options.zone : undefined,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function createProvidedAddRouteOptions(command, hasResourceName, hasRouteName) {
|
|
613
|
+
return {
|
|
614
|
+
resourceName: hasResourceName,
|
|
615
|
+
name: hasRouteName,
|
|
616
|
+
method: isCliOption(command, "method"),
|
|
617
|
+
path: isCliOption(command, "path"),
|
|
618
|
+
useCase: isCliOption(command, "useCase"),
|
|
619
|
+
command: isCliOption(command, "command"),
|
|
620
|
+
query: isCliOption(command, "query"),
|
|
621
|
+
auth: isCliOption(command, "auth"),
|
|
622
|
+
zone: isCliOption(command, "zone"),
|
|
623
|
+
bruno: isCliOption(command, "bruno"),
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function createExplicitAddRouteFlags(options, command) {
|
|
627
|
+
return {
|
|
628
|
+
method: isCliOption(command, "method") ? options.method : undefined,
|
|
629
|
+
path: isCliOption(command, "path") ? options.path : undefined,
|
|
630
|
+
useCase: isCliOption(command, "useCase") ? options.useCase : undefined,
|
|
631
|
+
command: isCliOption(command, "command") ? options.command : undefined,
|
|
632
|
+
query: isCliOption(command, "query") ? options.query : undefined,
|
|
633
|
+
auth: isCliOption(command, "auth") ? options.auth : undefined,
|
|
634
|
+
zone: isCliOption(command, "zone") ? options.zone : undefined,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function isCliOption(command, name) {
|
|
638
|
+
return command.getOptionValueSource(name) === "cli";
|
|
639
|
+
}
|
|
640
|
+
function enableBruno(config) {
|
|
641
|
+
if (!config.project.capabilities.apiClient.includes("bruno")) {
|
|
642
|
+
config.project.capabilities.apiClient.push("bruno");
|
|
643
|
+
}
|
|
644
|
+
config.api.bruno.enabled = true;
|
|
645
|
+
config.api.bruno.collectionPath ||= "bruno";
|
|
646
|
+
config.api.bruno.environment ||= "Local";
|
|
647
|
+
}
|
|
648
|
+
function routeControllerNamesForResource(generatedPaths, featuresRoot, resourceName, nextControllerPath) {
|
|
649
|
+
const apiPrefix = `${featuresRoot}/${resourceName}/api/`;
|
|
650
|
+
const routeControllerPaths = generatedPaths
|
|
651
|
+
.filter((filePath) => filePath.startsWith(apiPrefix))
|
|
652
|
+
.map(route_plan_1.routeControllerNameFromPath)
|
|
653
|
+
.filter((name) => Boolean(name))
|
|
654
|
+
.filter((name) => name !== resourceName);
|
|
655
|
+
const nextName = (0, route_plan_1.routeControllerNameFromPath)(nextControllerPath);
|
|
656
|
+
if (nextName) {
|
|
657
|
+
routeControllerPaths.push(nextName);
|
|
658
|
+
}
|
|
659
|
+
return Array.from(new Set(routeControllerPaths));
|
|
660
|
+
}
|
|
661
|
+
function routeControllerIndexResources(generatedPaths, featuresRoot, nextResourceName) {
|
|
662
|
+
const resources = generatedPaths
|
|
663
|
+
.map((filePath) => (0, route_plan_1.routeControllerIndexResourceFromPath)(filePath, featuresRoot))
|
|
664
|
+
.filter((name) => Boolean(name));
|
|
665
|
+
if (nextResourceName) {
|
|
666
|
+
resources.push(nextResourceName);
|
|
667
|
+
}
|
|
668
|
+
return Array.from(new Set(resources));
|
|
669
|
+
}
|
|
670
|
+
function commandNamesForFeature(generatedPaths, featuresRoot, feature, nextCommandName) {
|
|
671
|
+
const commandsPrefix = `${featuresRoot}/${feature}/application/commands/`;
|
|
672
|
+
const names = generatedPaths
|
|
673
|
+
.filter((filePath) => filePath.startsWith(commandsPrefix))
|
|
674
|
+
.map(command_plan_1.commandNameFromPath)
|
|
675
|
+
.filter((name) => Boolean(name));
|
|
676
|
+
names.push(nextCommandName);
|
|
677
|
+
return Array.from(new Set(names));
|
|
678
|
+
}
|
|
679
|
+
function commandFeatures(generatedPaths, featuresRoot, nextFeature) {
|
|
680
|
+
const features = generatedPaths
|
|
681
|
+
.map((filePath) => (0, command_plan_1.commandFeatureFromPath)(filePath, featuresRoot))
|
|
682
|
+
.filter((name) => Boolean(name));
|
|
683
|
+
if (nextFeature) {
|
|
684
|
+
features.push(nextFeature);
|
|
685
|
+
}
|
|
686
|
+
return Array.from(new Set(features));
|
|
687
|
+
}
|
|
688
|
+
function queryNamesForFeature(generatedPaths, featuresRoot, feature, nextQueryName) {
|
|
689
|
+
const queriesPrefix = `${featuresRoot}/${feature}/application/queries/`;
|
|
690
|
+
const names = generatedPaths
|
|
691
|
+
.filter((filePath) => filePath.startsWith(queriesPrefix))
|
|
692
|
+
.map(query_plan_1.queryNameFromPath)
|
|
693
|
+
.filter((name) => Boolean(name));
|
|
694
|
+
names.push(nextQueryName);
|
|
695
|
+
return Array.from(new Set(names));
|
|
696
|
+
}
|
|
697
|
+
function queryFeatures(generatedPaths, featuresRoot, nextFeature) {
|
|
698
|
+
const features = generatedPaths
|
|
699
|
+
.map((filePath) => (0, query_plan_1.queryFeatureFromPath)(filePath, featuresRoot))
|
|
700
|
+
.filter((name) => Boolean(name));
|
|
701
|
+
if (nextFeature) {
|
|
702
|
+
features.push(nextFeature);
|
|
703
|
+
}
|
|
704
|
+
return Array.from(new Set(features));
|
|
705
|
+
}
|
|
706
|
+
function collect(value, previous) {
|
|
707
|
+
previous.push(value);
|
|
708
|
+
return previous;
|
|
709
|
+
}
|