@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,328 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerRemoveCommand = void 0;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const load_soap_config_1 = require("../../config/load-soap-config");
|
|
10
|
+
const write_soap_config_1 = require("../../config/write-soap-config");
|
|
11
|
+
const command_context_1 = require("../../core/command-context");
|
|
12
|
+
const errors_1 = require("../../core/errors");
|
|
13
|
+
const file_writer_1 = require("../../io/file-writer");
|
|
14
|
+
const conflict_policy_1 = require("../../io/conflict-policy");
|
|
15
|
+
const registry_service_1 = require("../../registry/registry.service");
|
|
16
|
+
const naming_1 = require("../../templates/naming");
|
|
17
|
+
const resource_plan_1 = require("../add/resource-plan");
|
|
18
|
+
const route_plan_1 = require("../add/route-plan");
|
|
19
|
+
const common_options_1 = require("../shared/common-options");
|
|
20
|
+
const prompts_1 = require("../../prompts");
|
|
21
|
+
function registerRemoveCommand(program) {
|
|
22
|
+
const remove = program.command("remove").description("Safely remove generated resources and routes.");
|
|
23
|
+
(0, common_options_1.addConflictOption)((0, common_options_1.addInteractiveOption)(remove
|
|
24
|
+
.command("route <resource> <route>")
|
|
25
|
+
.description("Remove a generated route from a SoapJS project.")
|
|
26
|
+
.option("--force", "delete modified generated files", false)
|
|
27
|
+
.option("--yes", "skip interactive confirmation prompts", false)))
|
|
28
|
+
.action(async (resourceInput, routeInput, options, command) => {
|
|
29
|
+
(0, common_options_1.assertInteractiveTerminal)(options);
|
|
30
|
+
const context = (0, command_context_1.getCommandContext)(command);
|
|
31
|
+
const config = await (0, load_soap_config_1.loadSoapConfig)(context.cwd);
|
|
32
|
+
const resource = resolveResource(config, resourceInput);
|
|
33
|
+
const route = resolveRoute(config, resource, routeInput);
|
|
34
|
+
const targetEntries = routeFileEntries(config, resource, route);
|
|
35
|
+
const conflictPolicy = resolveRemoveConflictPolicy(options);
|
|
36
|
+
const preview = await createRemovePreview(config, targetEntries, [`route ${resource.name}/${route.name}`]);
|
|
37
|
+
if (options.interactive) {
|
|
38
|
+
context.output.info(formatRemovePreview(`Remove route ${resource.name}/${route.name}`, preview));
|
|
39
|
+
if (hasModifiedFiles(preview) && conflictPolicy !== "overwrite") {
|
|
40
|
+
throw new errors_1.CliError("Refusing to delete modified generated files. Use --force or --on-conflict overwrite to delete them.");
|
|
41
|
+
}
|
|
42
|
+
if (!options.yes) {
|
|
43
|
+
const confirmed = await new prompts_1.InquirerPromptAdapter().confirm({
|
|
44
|
+
message: "Continue removal?",
|
|
45
|
+
defaultValue: false,
|
|
46
|
+
});
|
|
47
|
+
if (!confirmed) {
|
|
48
|
+
context.output.warn("Route removal aborted.");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const removePlan = await removeTrackedFiles(config, targetEntries, conflictPolicy, context);
|
|
54
|
+
if (removePlan.skipped.length > 0) {
|
|
55
|
+
reportRemoveResult(context, `Skipped route ${resource.name}/${route.name}`, removePlan);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
config.registry.routes = config.registry.routes.filter((entry) => entry !== route);
|
|
59
|
+
removeGeneratedEntries(config, removePlan.deleted);
|
|
60
|
+
await refreshGeneratedIndexes(config, removePlan.conflictPolicy === "overwrite", context, {
|
|
61
|
+
changedResource: resource,
|
|
62
|
+
deletedPaths: removePlan.deleted,
|
|
63
|
+
});
|
|
64
|
+
await (0, write_soap_config_1.writeSoapConfig)(config.root, config, context);
|
|
65
|
+
reportRemoveResult(context, `Removed route ${resource.name}/${route.name}`, removePlan);
|
|
66
|
+
});
|
|
67
|
+
(0, common_options_1.addConflictOption)((0, common_options_1.addInteractiveOption)(remove
|
|
68
|
+
.command("resource <resource>")
|
|
69
|
+
.description("Remove a generated resource from a SoapJS project.")
|
|
70
|
+
.option("--force", "delete modified generated files", false)
|
|
71
|
+
.option("--yes", "skip interactive confirmation prompts", false)))
|
|
72
|
+
.action(async (resourceInput, options, command) => {
|
|
73
|
+
(0, common_options_1.assertInteractiveTerminal)(options);
|
|
74
|
+
const context = (0, command_context_1.getCommandContext)(command);
|
|
75
|
+
const config = await (0, load_soap_config_1.loadSoapConfig)(context.cwd);
|
|
76
|
+
const resource = resolveResource(config, resourceInput);
|
|
77
|
+
const targetEntries = config.registry.generatedFiles.filter((entry) => entry.owner === resource.name);
|
|
78
|
+
const conflictPolicy = resolveRemoveConflictPolicy(options);
|
|
79
|
+
const routeCount = config.registry.routes.filter((entry) => entry.resource === resource.name).length;
|
|
80
|
+
const preview = await createRemovePreview(config, targetEntries, [
|
|
81
|
+
`resource ${resource.name}`,
|
|
82
|
+
`${routeCount} route${routeCount === 1 ? "" : "s"} for ${resource.name}`,
|
|
83
|
+
]);
|
|
84
|
+
if (options.interactive) {
|
|
85
|
+
context.output.info(formatRemovePreview(`Remove resource ${resource.name}`, preview));
|
|
86
|
+
if (hasModifiedFiles(preview) && conflictPolicy !== "overwrite") {
|
|
87
|
+
throw new errors_1.CliError("Refusing to delete modified generated files. Use --force or --on-conflict overwrite to delete them.");
|
|
88
|
+
}
|
|
89
|
+
if (!options.yes) {
|
|
90
|
+
const confirmed = await new prompts_1.InquirerPromptAdapter().confirm({
|
|
91
|
+
message: "Continue removal?",
|
|
92
|
+
defaultValue: false,
|
|
93
|
+
});
|
|
94
|
+
if (!confirmed) {
|
|
95
|
+
context.output.warn("Resource removal aborted.");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const removePlan = await removeTrackedFiles(config, targetEntries, conflictPolicy, context);
|
|
101
|
+
if (removePlan.skipped.length > 0) {
|
|
102
|
+
reportRemoveResult(context, `Skipped resource ${resource.name}`, removePlan);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
config.registry.resources = config.registry.resources.filter((entry) => entry !== resource);
|
|
106
|
+
config.registry.routes = config.registry.routes.filter((entry) => entry.resource !== resource.name);
|
|
107
|
+
removeGeneratedEntries(config, removePlan.deleted);
|
|
108
|
+
await refreshGeneratedIndexes(config, removePlan.conflictPolicy === "overwrite", context, {
|
|
109
|
+
deletedPaths: removePlan.deleted,
|
|
110
|
+
});
|
|
111
|
+
await (0, write_soap_config_1.writeSoapConfig)(config.root, config, context);
|
|
112
|
+
reportRemoveResult(context, `Removed resource ${resource.name}`, removePlan);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
exports.registerRemoveCommand = registerRemoveCommand;
|
|
116
|
+
function resolveResource(config, input) {
|
|
117
|
+
const names = (0, naming_1.createNameVariants)(input);
|
|
118
|
+
const candidates = new Set([input, names.kebabName, singularize(names.kebabName), names.pluralName]);
|
|
119
|
+
const resource = config.registry.resources.find((entry) => candidates.has(entry.name) || candidates.has(entry.path.replace(/^\//, "")));
|
|
120
|
+
if (!resource) {
|
|
121
|
+
throw new errors_1.CliError(`Resource "${input}" was not found in the route registry.`);
|
|
122
|
+
}
|
|
123
|
+
return resource;
|
|
124
|
+
}
|
|
125
|
+
function resolveRoute(config, resource, input) {
|
|
126
|
+
const resourceNames = (0, naming_1.createNameVariants)(resource.name);
|
|
127
|
+
const routeNames = (0, naming_1.createNameVariants)(input);
|
|
128
|
+
const candidates = new Set([
|
|
129
|
+
input,
|
|
130
|
+
routeNames.kebabName,
|
|
131
|
+
stripResourceSuffix(routeNames.kebabName, resourceNames.kebabName),
|
|
132
|
+
stripResourceSuffix(routeNames.kebabName, resourceNames.pluralName),
|
|
133
|
+
]);
|
|
134
|
+
const route = config.registry.routes.find((entry) => entry.resource === resource.name && candidates.has(entry.name));
|
|
135
|
+
if (!route) {
|
|
136
|
+
throw new errors_1.CliError(`Route "${input}" was not found on resource "${resource.name}".`);
|
|
137
|
+
}
|
|
138
|
+
return route;
|
|
139
|
+
}
|
|
140
|
+
function stripResourceSuffix(value, resourceName) {
|
|
141
|
+
const suffix = `-${resourceName}`;
|
|
142
|
+
return value.endsWith(suffix) ? value.slice(0, -suffix.length) : value;
|
|
143
|
+
}
|
|
144
|
+
function resolveRemoveConflictPolicy(options) {
|
|
145
|
+
const policy = (0, conflict_policy_1.resolveConflictPolicy)({
|
|
146
|
+
force: options.force,
|
|
147
|
+
onConflict: options.onConflict,
|
|
148
|
+
skipModified: !options.force,
|
|
149
|
+
});
|
|
150
|
+
if (policy === "new") {
|
|
151
|
+
throw new errors_1.CliError("Conflict policy \"new\" is not supported for remove commands.");
|
|
152
|
+
}
|
|
153
|
+
return policy;
|
|
154
|
+
}
|
|
155
|
+
function singularize(value) {
|
|
156
|
+
return value.endsWith("s") ? value.slice(0, -1) : value;
|
|
157
|
+
}
|
|
158
|
+
async function createRemovePreview(config, entries, registryEntries) {
|
|
159
|
+
const files = await Promise.all(entries.map(async (entry) => {
|
|
160
|
+
const currentHash = await (0, registry_service_1.readFileHash)(path_1.default.join(config.root, entry.path));
|
|
161
|
+
return {
|
|
162
|
+
path: entry.path,
|
|
163
|
+
exists: currentHash !== undefined,
|
|
164
|
+
modified: Boolean(currentHash && currentHash !== entry.hash),
|
|
165
|
+
};
|
|
166
|
+
}));
|
|
167
|
+
return {
|
|
168
|
+
files,
|
|
169
|
+
registryEntries,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function formatRemovePreview(title, preview) {
|
|
173
|
+
const modifiedFiles = preview.files.filter((file) => file.modified);
|
|
174
|
+
const lines = [
|
|
175
|
+
title,
|
|
176
|
+
`Tracked files to delete: ${preview.files.length}`,
|
|
177
|
+
...formatPreviewFiles(preview.files),
|
|
178
|
+
`Modified tracked files: ${modifiedFiles.length}`,
|
|
179
|
+
...formatPreviewFiles(modifiedFiles),
|
|
180
|
+
`Registry entries to remove: ${preview.registryEntries.length}`,
|
|
181
|
+
...preview.registryEntries.map((entry) => `- ${entry}`),
|
|
182
|
+
];
|
|
183
|
+
return lines.join("\n");
|
|
184
|
+
}
|
|
185
|
+
function formatPreviewFiles(files) {
|
|
186
|
+
if (files.length === 0) {
|
|
187
|
+
return ["- none"];
|
|
188
|
+
}
|
|
189
|
+
return files.map((file) => {
|
|
190
|
+
const suffix = file.modified ? " (modified)" : file.exists ? "" : " (missing)";
|
|
191
|
+
return `- ${file.path}${suffix}`;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function hasModifiedFiles(preview) {
|
|
195
|
+
return preview.files.some((file) => file.modified);
|
|
196
|
+
}
|
|
197
|
+
function routeFileEntries(config, resource, route) {
|
|
198
|
+
const resourceNames = (0, naming_1.createNameVariants)(resource.name);
|
|
199
|
+
const routeNames = (0, naming_1.createNameVariants)(route.name);
|
|
200
|
+
const routeFileNames = new Set([
|
|
201
|
+
routeNames.kebabName,
|
|
202
|
+
`${routeNames.kebabName}-${resourceNames.kebabName}`,
|
|
203
|
+
`${routeNames.kebabName}-${resourceNames.pluralName}`,
|
|
204
|
+
]);
|
|
205
|
+
const brunoPath = path_1.default.posix.join(config.api.bruno.collectionPath, resourceNames.pascalName, `${routeNames.pascalName}.bru`);
|
|
206
|
+
return config.registry.generatedFiles.filter((entry) => {
|
|
207
|
+
if (entry.path === brunoPath) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
if (entry.owner !== resource.name || entry.type !== "route") {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
const fileName = path_1.default.posix.basename(entry.path).replace(/\.(controller|contract)\.ts$/, "");
|
|
214
|
+
return routeFileNames.has(fileName);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
async function removeTrackedFiles(config, entries, conflictPolicy, context) {
|
|
218
|
+
const deleted = [];
|
|
219
|
+
const skipped = [];
|
|
220
|
+
const currentHashes = new Map();
|
|
221
|
+
for (const entry of entries) {
|
|
222
|
+
const absolutePath = path_1.default.join(config.root, entry.path);
|
|
223
|
+
const currentHash = await (0, registry_service_1.readFileHash)(absolutePath);
|
|
224
|
+
currentHashes.set(entry.path, currentHash);
|
|
225
|
+
if (currentHash && currentHash !== entry.hash && conflictPolicy !== "overwrite") {
|
|
226
|
+
if (conflictPolicy === "abort") {
|
|
227
|
+
throw new errors_1.CliError(`Refusing to delete modified file ${entry.path}. Use --force or --on-conflict overwrite to delete it.`);
|
|
228
|
+
}
|
|
229
|
+
if (conflictPolicy === "ask") {
|
|
230
|
+
throw new errors_1.CliError(`Interactive conflict resolution is not available yet for ${entry.path}. Use --on-conflict skip, overwrite, or abort.`);
|
|
231
|
+
}
|
|
232
|
+
skipped.push(entry.path);
|
|
233
|
+
context.output.warn(`Skipped modified file ${entry.path}. Use --force to delete it.`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (skipped.length > 0) {
|
|
237
|
+
return { deleted, skipped, conflictPolicy };
|
|
238
|
+
}
|
|
239
|
+
for (const entry of entries) {
|
|
240
|
+
const absolutePath = path_1.default.join(config.root, entry.path);
|
|
241
|
+
const currentHash = currentHashes.get(entry.path);
|
|
242
|
+
if (!currentHash) {
|
|
243
|
+
deleted.push(entry.path);
|
|
244
|
+
}
|
|
245
|
+
else if (context.dryRun) {
|
|
246
|
+
context.output.info(`[dry-run] delete ${absolutePath}`);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
await promises_1.default.unlink(absolutePath);
|
|
250
|
+
await pruneEmptyParents(path_1.default.dirname(absolutePath), config.root);
|
|
251
|
+
}
|
|
252
|
+
if (currentHash) {
|
|
253
|
+
deleted.push(entry.path);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { deleted, skipped, conflictPolicy };
|
|
257
|
+
}
|
|
258
|
+
async function pruneEmptyParents(startPath, root) {
|
|
259
|
+
let current = startPath;
|
|
260
|
+
while (current.startsWith(root) && current !== root) {
|
|
261
|
+
try {
|
|
262
|
+
await promises_1.default.rmdir(current);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
current = path_1.default.dirname(current);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function removeGeneratedEntries(config, deletedPaths) {
|
|
271
|
+
const deleted = new Set(deletedPaths);
|
|
272
|
+
config.registry.generatedFiles = config.registry.generatedFiles.filter((entry) => !deleted.has(entry.path));
|
|
273
|
+
}
|
|
274
|
+
async function refreshGeneratedIndexes(config, force, context, options) {
|
|
275
|
+
const generatedPaths = config.registry.generatedFiles.map((entry) => entry.path);
|
|
276
|
+
const routeIndexResources = routeControllerIndexResources(generatedPaths, config.structure.featuresRoot);
|
|
277
|
+
const files = [
|
|
278
|
+
(0, resource_plan_1.createResourcesFile)(config.registry.resources, config.structure.featuresRoot),
|
|
279
|
+
(0, resource_plan_1.createFeaturesIndexFile)(config.registry.resources, config.project.capabilities.auth),
|
|
280
|
+
(0, resource_plan_1.createControllersFile)(config.registry.resources, config.structure.featuresRoot, config.project.capabilities.auth, routeIndexResources, config.registry.resources.filter((resource) => !routeIndexResources.includes(resource.name)).map((resource) => resource.name)),
|
|
281
|
+
];
|
|
282
|
+
if (options.changedResource && config.registry.resources.includes(options.changedResource)) {
|
|
283
|
+
const routeNames = routeControllerNamesForResource(generatedPaths, config.structure.featuresRoot, options.changedResource.name);
|
|
284
|
+
const indexPath = path_1.default.posix.join(config.structure.featuresRoot, options.changedResource.name, "api", `${options.changedResource.name}.controllers.ts`);
|
|
285
|
+
if (routeNames.length > 0) {
|
|
286
|
+
files.push((0, route_plan_1.createRouteControllersIndexFile)(options.changedResource, config.structure.featuresRoot, routeNames));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
const indexEntry = config.registry.generatedFiles.find((entry) => entry.path === indexPath);
|
|
290
|
+
if (indexEntry) {
|
|
291
|
+
const removePlan = await removeTrackedFiles(config, [indexEntry], force ? "overwrite" : "skip", context);
|
|
292
|
+
removeGeneratedEntries(config, removePlan.deleted);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
await (0, file_writer_1.writePlannedFiles)({
|
|
297
|
+
root: config.root,
|
|
298
|
+
files,
|
|
299
|
+
registry: config.registry,
|
|
300
|
+
force,
|
|
301
|
+
skipModified: !force,
|
|
302
|
+
}, context);
|
|
303
|
+
}
|
|
304
|
+
function routeControllerNamesForResource(generatedPaths, featuresRoot, resourceName) {
|
|
305
|
+
const apiPrefix = `${featuresRoot}/${resourceName}/api/`;
|
|
306
|
+
return Array.from(new Set(generatedPaths
|
|
307
|
+
.filter((filePath) => filePath.startsWith(apiPrefix))
|
|
308
|
+
.map(route_plan_1.routeControllerNameFromPath)
|
|
309
|
+
.filter((name) => Boolean(name))
|
|
310
|
+
.filter((name) => name !== resourceName))).sort();
|
|
311
|
+
}
|
|
312
|
+
function routeControllerIndexResources(generatedPaths, featuresRoot) {
|
|
313
|
+
return Array.from(new Set(generatedPaths
|
|
314
|
+
.map((filePath) => (0, route_plan_1.routeControllerIndexResourceFromPath)(filePath, featuresRoot))
|
|
315
|
+
.filter((name) => Boolean(name)))).sort();
|
|
316
|
+
}
|
|
317
|
+
function reportRemoveResult(context, message, plan) {
|
|
318
|
+
if (plan.skipped.length > 0 && plan.deleted.length === 0) {
|
|
319
|
+
context.output.warn(`${message}; ${plan.skipped.length} modified files left untouched. Use --force to remove them.`);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const suffix = plan.skipped.length > 0 ? ` (${plan.skipped.length} modified files skipped)` : "";
|
|
323
|
+
if (context.dryRun) {
|
|
324
|
+
context.output.success(`${message.replace(/^Removed/, "Would remove")}; would delete ${plan.deleted.length} files${suffix}.`);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
context.output.success(`${message}; deleted ${plan.deleted.length} files${suffix}.`);
|
|
328
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
export interface InteractiveCommandOptions {
|
|
3
|
+
interactive?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface ConflictCommandOptions {
|
|
6
|
+
onConflict?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function addInteractiveOption<T extends Command>(command: T): T;
|
|
9
|
+
export declare function addConflictOption<T extends Command>(command: T): T;
|
|
10
|
+
export declare function assertInteractiveTerminal(options: InteractiveCommandOptions): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertInteractiveTerminal = exports.addConflictOption = exports.addInteractiveOption = void 0;
|
|
4
|
+
const errors_1 = require("../../core/errors");
|
|
5
|
+
const conflict_policy_1 = require("../../io/conflict-policy");
|
|
6
|
+
const terminal_capabilities_1 = require("../../terminal/terminal-capabilities");
|
|
7
|
+
function addInteractiveOption(command) {
|
|
8
|
+
return command.option("-i, --interactive", "run a guided interactive flow", false);
|
|
9
|
+
}
|
|
10
|
+
exports.addInteractiveOption = addInteractiveOption;
|
|
11
|
+
function addConflictOption(command) {
|
|
12
|
+
return command.option("--on-conflict <policy>", `file conflict policy: ${(0, conflict_policy_1.conflictPolicyHelp)()}`);
|
|
13
|
+
}
|
|
14
|
+
exports.addConflictOption = addConflictOption;
|
|
15
|
+
function assertInteractiveTerminal(options) {
|
|
16
|
+
if (!options.interactive) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!(0, terminal_capabilities_1.canRunInteractiveMode)()) {
|
|
20
|
+
throw new errors_1.CliError("Interactive mode requires a TTY. Use explicit flags instead.");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.assertInteractiveTerminal = assertInteractiveTerminal;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerUpdateCommand = void 0;
|
|
4
|
+
const load_soap_config_1 = require("../../config/load-soap-config");
|
|
5
|
+
const write_soap_config_1 = require("../../config/write-soap-config");
|
|
6
|
+
const command_context_1 = require("../../core/command-context");
|
|
7
|
+
const errors_1 = require("../../core/errors");
|
|
8
|
+
const dependency_resolver_1 = require("../../dependencies/dependency-resolver");
|
|
9
|
+
const file_writer_1 = require("../../io/file-writer");
|
|
10
|
+
const bruno_plan_1 = require("../generate/bruno-plan");
|
|
11
|
+
const project_plan_1 = require("../create/project-plan");
|
|
12
|
+
const resource_plan_1 = require("../add/resource-plan");
|
|
13
|
+
const route_plan_1 = require("../add/route-plan");
|
|
14
|
+
const common_options_1 = require("../shared/common-options");
|
|
15
|
+
function registerUpdateCommand(program) {
|
|
16
|
+
const update = program.command("update").description("Update generated SoapJS project configuration.");
|
|
17
|
+
(0, common_options_1.addConflictOption)(update
|
|
18
|
+
.command("config")
|
|
19
|
+
.description("Add project capabilities and generated infrastructure.")
|
|
20
|
+
.option("--add-db <database>", "add database capability: mongo, postgres, mysql, sqlite, redis", collect, [])
|
|
21
|
+
.option("--add-auth <auth>", "add auth capability: jwt, api-key, local", collect, [])
|
|
22
|
+
.option("--add-docs <docs>", "add docs capability: openapi", collect, [])
|
|
23
|
+
.option("--add-contracts <contracts>", "add contract capability: zod", collect, [])
|
|
24
|
+
.option("--add-api-client <client>", "add API client capability: bruno", collect, [])
|
|
25
|
+
.option("--add-realtime <realtime>", "add realtime capability: ws", collect, [])
|
|
26
|
+
.option("--force", "overwrite generated infra files even when modified", false))
|
|
27
|
+
.action(async (options, command) => {
|
|
28
|
+
const context = (0, command_context_1.getCommandContext)(command);
|
|
29
|
+
const config = await (0, load_soap_config_1.loadSoapConfig)(context.cwd);
|
|
30
|
+
const nextCapabilities = cloneCapabilities(config.project.capabilities);
|
|
31
|
+
const changes = [];
|
|
32
|
+
addCapabilities(nextCapabilities.databases, (0, project_plan_1.parseCsvOption)(options.addDb, project_plan_1.databaseOptions), "db", changes);
|
|
33
|
+
addCapabilities(nextCapabilities.auth, (0, project_plan_1.parseCsvOption)(options.addAuth, project_plan_1.authOptions), "auth", changes);
|
|
34
|
+
addCapabilities(nextCapabilities.docs, (0, project_plan_1.parseCsvOption)(options.addDocs, project_plan_1.docsOptions), "docs", changes);
|
|
35
|
+
addCapabilities(nextCapabilities.contracts, (0, project_plan_1.parseCsvOption)(options.addContracts, project_plan_1.contractsOptions), "contracts", changes);
|
|
36
|
+
addCapabilities(nextCapabilities.apiClient, (0, project_plan_1.parseCsvOption)(options.addApiClient, ["bruno"]), "api-client", changes);
|
|
37
|
+
addCapabilities(nextCapabilities.realtime, (0, project_plan_1.parseCsvOption)(options.addRealtime, project_plan_1.realtimeOptions), "realtime", changes);
|
|
38
|
+
if (changes.length === 0) {
|
|
39
|
+
throw new errors_1.CliError("No new capabilities requested. Use --add-db, --add-auth, --add-docs, --add-contracts, --add-api-client, or --add-realtime.");
|
|
40
|
+
}
|
|
41
|
+
config.project.capabilities = nextCapabilities;
|
|
42
|
+
updateApiConfig(config);
|
|
43
|
+
const plan = createPlan(config);
|
|
44
|
+
const files = createUpdateFiles(config, plan);
|
|
45
|
+
if (context.dryRun) {
|
|
46
|
+
context.output.info(`Capabilities: ${changes.join(", ")}`);
|
|
47
|
+
context.output.info(`Files: ${files.length}`);
|
|
48
|
+
}
|
|
49
|
+
await (0, file_writer_1.writePlannedFiles)({
|
|
50
|
+
root: config.root,
|
|
51
|
+
files,
|
|
52
|
+
registry: config.registry,
|
|
53
|
+
force: options.force,
|
|
54
|
+
onConflict: options.onConflict,
|
|
55
|
+
skipModified: !options.force,
|
|
56
|
+
}, context);
|
|
57
|
+
await (0, write_soap_config_1.writeSoapConfig)(config.root, config, context);
|
|
58
|
+
context.output.success(`Updated project capabilities: ${changes.join(", ")}.`);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
exports.registerUpdateCommand = registerUpdateCommand;
|
|
62
|
+
function collect(value, previous) {
|
|
63
|
+
previous.push(value);
|
|
64
|
+
return previous;
|
|
65
|
+
}
|
|
66
|
+
function cloneCapabilities(capabilities) {
|
|
67
|
+
return {
|
|
68
|
+
databases: [...capabilities.databases],
|
|
69
|
+
auth: [...capabilities.auth],
|
|
70
|
+
messaging: [...capabilities.messaging],
|
|
71
|
+
realtime: [...capabilities.realtime],
|
|
72
|
+
telemetry: [...capabilities.telemetry],
|
|
73
|
+
apiClient: [...capabilities.apiClient],
|
|
74
|
+
docs: [...capabilities.docs],
|
|
75
|
+
contracts: [...capabilities.contracts],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function addCapabilities(target, values, label, changes) {
|
|
79
|
+
for (const value of values) {
|
|
80
|
+
if (!target.includes(value)) {
|
|
81
|
+
target.push(value);
|
|
82
|
+
changes.push(`${label}:${value}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function updateApiConfig(config) {
|
|
87
|
+
config.api.bruno.enabled = config.project.capabilities.apiClient.includes("bruno");
|
|
88
|
+
config.api.bruno.collectionPath ||= "bruno";
|
|
89
|
+
config.api.bruno.environment ||= "Local";
|
|
90
|
+
const defaultAuth = config.project.capabilities.auth[0] ?? "none";
|
|
91
|
+
config.api.auth.default = defaultAuth;
|
|
92
|
+
config.api.auth.loginRoute =
|
|
93
|
+
defaultAuth === "jwt" || defaultAuth === "local"
|
|
94
|
+
? {
|
|
95
|
+
method: "POST",
|
|
96
|
+
path: "/auth/login",
|
|
97
|
+
tokenVariable: "accessToken",
|
|
98
|
+
}
|
|
99
|
+
: undefined;
|
|
100
|
+
}
|
|
101
|
+
function createPlan(config) {
|
|
102
|
+
return {
|
|
103
|
+
name: config.project.name,
|
|
104
|
+
root: config.root,
|
|
105
|
+
framework: config.project.framework,
|
|
106
|
+
architecture: config.project.architecture,
|
|
107
|
+
packageManager: config.project.packageManager,
|
|
108
|
+
capabilities: config.project.capabilities,
|
|
109
|
+
zones: config.project.zones,
|
|
110
|
+
dependencies: (0, dependency_resolver_1.resolveDependencies)(config.project.capabilities),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function createUpdateFiles(config, plan) {
|
|
114
|
+
const projectFiles = (0, project_plan_1.createProjectFiles)(plan).filter(shouldUpdateFromProjectPlan);
|
|
115
|
+
const routeControllerIndexes = routeControllerIndexResources(config.registry.generatedFiles.map((file) => file.path), config.structure.featuresRoot);
|
|
116
|
+
const mainControllerResources = config.registry.resources
|
|
117
|
+
.filter((resource) => !routeControllerIndexes.includes(resource.name))
|
|
118
|
+
.map((resource) => resource.name);
|
|
119
|
+
const indexFiles = [
|
|
120
|
+
(0, resource_plan_1.createResourcesFile)(config.registry.resources, config.structure.featuresRoot),
|
|
121
|
+
(0, resource_plan_1.createFeaturesIndexFile)(config.registry.resources, config.project.capabilities.auth),
|
|
122
|
+
(0, resource_plan_1.createControllersFile)(config.registry.resources, config.structure.featuresRoot, config.project.capabilities.auth, routeControllerIndexes, mainControllerResources),
|
|
123
|
+
];
|
|
124
|
+
const brunoFiles = config.api.bruno.enabled ? (0, bruno_plan_1.createBrunoFiles)(config) : [];
|
|
125
|
+
return mergeFiles([...projectFiles, ...indexFiles, ...brunoFiles]);
|
|
126
|
+
}
|
|
127
|
+
function shouldUpdateFromProjectPlan(file) {
|
|
128
|
+
if ([
|
|
129
|
+
"package.json",
|
|
130
|
+
".env.example",
|
|
131
|
+
"README.md",
|
|
132
|
+
"docker-compose.yml",
|
|
133
|
+
"src/index.ts",
|
|
134
|
+
"src/config/config.ts",
|
|
135
|
+
"src/config/dependencies.ts",
|
|
136
|
+
].includes(file.path)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
return (/^src\/config\/[^/]+\.config\.ts$/.test(file.path) ||
|
|
140
|
+
file.path.startsWith("src/common/") ||
|
|
141
|
+
file.path.startsWith("src/features/auth/") ||
|
|
142
|
+
file.path.startsWith("src/features/health/"));
|
|
143
|
+
}
|
|
144
|
+
function mergeFiles(files) {
|
|
145
|
+
const byPath = new Map();
|
|
146
|
+
for (const file of files) {
|
|
147
|
+
byPath.set(file.path, file);
|
|
148
|
+
}
|
|
149
|
+
return Array.from(byPath.values());
|
|
150
|
+
}
|
|
151
|
+
function routeControllerIndexResources(generatedPaths, featuresRoot) {
|
|
152
|
+
return Array.from(new Set(generatedPaths
|
|
153
|
+
.map((filePath) => (0, route_plan_1.routeControllerIndexResourceFromPath)(filePath, featuresRoot))
|
|
154
|
+
.filter((name) => Boolean(name)))).sort();
|
|
155
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AuthPolicy } from "./schemas/types";
|
|
2
|
+
export declare function parseAuthPolicy(value: string | undefined): AuthPolicy | undefined;
|
|
3
|
+
export declare function formatAuthPolicy(policy: AuthPolicy | undefined): string | undefined;
|
|
4
|
+
export declare function createAuthPolicyArgument(policy: AuthPolicy | undefined): string | undefined;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAuthPolicyArgument = exports.formatAuthPolicy = exports.parseAuthPolicy = void 0;
|
|
4
|
+
const errors_1 = require("../core/errors");
|
|
5
|
+
function parseAuthPolicy(value) {
|
|
6
|
+
if (!value || value === "none") {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
if (value === "admin") {
|
|
10
|
+
return { type: "admin" };
|
|
11
|
+
}
|
|
12
|
+
if (value.startsWith("roles:")) {
|
|
13
|
+
const roles = value.slice("roles:".length).split(",").map((role) => role.trim()).filter(Boolean);
|
|
14
|
+
if (roles.length === 0) {
|
|
15
|
+
throw new errors_1.CliError("Auth policy roles must include at least one role.");
|
|
16
|
+
}
|
|
17
|
+
return { type: "roles", roles };
|
|
18
|
+
}
|
|
19
|
+
if (value.startsWith("custom:")) {
|
|
20
|
+
const name = value.slice("custom:".length).trim();
|
|
21
|
+
if (!name) {
|
|
22
|
+
throw new errors_1.CliError("Custom auth policy must include a name.");
|
|
23
|
+
}
|
|
24
|
+
return { type: "custom", name };
|
|
25
|
+
}
|
|
26
|
+
throw new errors_1.CliError(`Invalid auth policy "${value}". Use admin, roles:a,b, custom:name, or none.`);
|
|
27
|
+
}
|
|
28
|
+
exports.parseAuthPolicy = parseAuthPolicy;
|
|
29
|
+
function formatAuthPolicy(policy) {
|
|
30
|
+
if (!policy) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
if (policy.type === "admin") {
|
|
34
|
+
return "admin";
|
|
35
|
+
}
|
|
36
|
+
if (policy.type === "roles") {
|
|
37
|
+
return `roles:${policy.roles.join(",")}`;
|
|
38
|
+
}
|
|
39
|
+
return `custom:${policy.name}`;
|
|
40
|
+
}
|
|
41
|
+
exports.formatAuthPolicy = formatAuthPolicy;
|
|
42
|
+
function createAuthPolicyArgument(policy) {
|
|
43
|
+
if (!policy) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
if (policy.type === "admin") {
|
|
47
|
+
return "{ policy: 'admin' }";
|
|
48
|
+
}
|
|
49
|
+
if (policy.type === "roles") {
|
|
50
|
+
return `{ roles: [${policy.roles.map((role) => `'${role}'`).join(", ")}] }`;
|
|
51
|
+
}
|
|
52
|
+
return `{ policy: '${policy.name}' }`;
|
|
53
|
+
}
|
|
54
|
+
exports.createAuthPolicyArgument = createAuthPolicyArgument;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function findSoapRoot(cwd: string): string | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.findSoapRoot = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function findSoapRoot(cwd) {
|
|
10
|
+
let current = path_1.default.resolve(cwd);
|
|
11
|
+
while (true) {
|
|
12
|
+
if (fs_1.default.existsSync(path_1.default.join(current, ".soap", "project.json"))) {
|
|
13
|
+
return current;
|
|
14
|
+
}
|
|
15
|
+
const parent = path_1.default.dirname(current);
|
|
16
|
+
if (parent === current) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
current = parent;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.findSoapRoot = findSoapRoot;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadSoapConfig = void 0;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const errors_1 = require("../core/errors");
|
|
10
|
+
const find_soap_root_1 = require("./find-soap-root");
|
|
11
|
+
const validation_1 = require("./schemas/validation");
|
|
12
|
+
async function readJson(filePath) {
|
|
13
|
+
const content = await promises_1.default.readFile(filePath, "utf8");
|
|
14
|
+
return JSON.parse(content);
|
|
15
|
+
}
|
|
16
|
+
async function loadSoapConfig(cwd) {
|
|
17
|
+
const root = (0, find_soap_root_1.findSoapRoot)(cwd);
|
|
18
|
+
if (!root) {
|
|
19
|
+
throw new errors_1.CliError("This command must be run inside a SoapJS project. Run `soap create <name>` first.");
|
|
20
|
+
}
|
|
21
|
+
const soapRoot = path_1.default.join(root, ".soap");
|
|
22
|
+
return {
|
|
23
|
+
root,
|
|
24
|
+
project: (0, validation_1.validateProjectConfig)(await readJson(path_1.default.join(soapRoot, "project.json"))),
|
|
25
|
+
structure: (0, validation_1.validateStructureConfig)(await readJson(path_1.default.join(soapRoot, "structure.json"))),
|
|
26
|
+
api: (0, validation_1.validateApiConfig)(await readJson(path_1.default.join(soapRoot, "api.json"))),
|
|
27
|
+
registry: (0, validation_1.validateRegistryConfig)(await readJson(path_1.default.join(soapRoot, "registry.json"))),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
exports.loadSoapConfig = loadSoapConfig;
|