@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,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.promptCreateProject = void 0;
|
|
4
|
+
async function promptCreateProject(prompt, options) {
|
|
5
|
+
const answers = {};
|
|
6
|
+
const preset = options.preset ?? {};
|
|
7
|
+
if (!options.provided.framework) {
|
|
8
|
+
answers.framework = await prompt.select({
|
|
9
|
+
message: "Web framework",
|
|
10
|
+
choices: [{ label: "Express", value: "express" }],
|
|
11
|
+
defaultValue: preset.framework ?? "express",
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
if (!options.provided.architecture) {
|
|
15
|
+
answers.architecture = await prompt.select({
|
|
16
|
+
message: "Architecture pattern",
|
|
17
|
+
choices: [
|
|
18
|
+
{ label: "Regular Clean Architecture", value: "regular" },
|
|
19
|
+
{ label: "CQRS", value: "cqrs" },
|
|
20
|
+
],
|
|
21
|
+
defaultValue: preset.architecture ?? "regular",
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (!options.provided.db) {
|
|
25
|
+
answers.db = await prompt.multiSelect({
|
|
26
|
+
message: "Databases",
|
|
27
|
+
choices: [
|
|
28
|
+
{ label: "MongoDB", value: "mongo" },
|
|
29
|
+
{ label: "PostgreSQL", value: "postgres" },
|
|
30
|
+
{ label: "MySQL", value: "mysql" },
|
|
31
|
+
{ label: "SQLite", value: "sqlite" },
|
|
32
|
+
{ label: "Redis", value: "redis" },
|
|
33
|
+
{ label: "None", value: "none" },
|
|
34
|
+
],
|
|
35
|
+
defaultValues: withNoneDefault(preset.capabilities?.databases),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (!options.provided.auth) {
|
|
39
|
+
answers.auth = await prompt.multiSelect({
|
|
40
|
+
message: "Auth strategies",
|
|
41
|
+
choices: [
|
|
42
|
+
{ label: "JWT", value: "jwt" },
|
|
43
|
+
{ label: "API Key", value: "api-key" },
|
|
44
|
+
{ label: "Local", value: "local" },
|
|
45
|
+
{ label: "None", value: "none" },
|
|
46
|
+
],
|
|
47
|
+
defaultValues: withNoneDefault(preset.capabilities?.auth),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (!options.provided.messaging) {
|
|
51
|
+
answers.messaging = await prompt.multiSelect({
|
|
52
|
+
message: "Messaging",
|
|
53
|
+
choices: [
|
|
54
|
+
{ label: "In-memory", value: "in-memory" },
|
|
55
|
+
{ label: "Kafka/Redpanda", value: "kafka" },
|
|
56
|
+
{ label: "None", value: "none" },
|
|
57
|
+
],
|
|
58
|
+
defaultValues: withDefault(preset.capabilities?.messaging, ["in-memory"]),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (!options.provided.realtime) {
|
|
62
|
+
answers.realtime = await prompt.multiSelect({
|
|
63
|
+
message: "Realtime",
|
|
64
|
+
choices: [
|
|
65
|
+
{ label: "WebSocket", value: "ws" },
|
|
66
|
+
{ label: "None", value: "none" },
|
|
67
|
+
],
|
|
68
|
+
defaultValues: withNoneDefault(preset.capabilities?.realtime),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (!options.provided.telemetry) {
|
|
72
|
+
answers.telemetry = await prompt.multiSelect({
|
|
73
|
+
message: "Telemetry",
|
|
74
|
+
choices: [
|
|
75
|
+
{ label: "Logs", value: "logs" },
|
|
76
|
+
{ label: "OTel noop", value: "otel-noop" },
|
|
77
|
+
{ label: "None", value: "none" },
|
|
78
|
+
],
|
|
79
|
+
defaultValues: withDefault(preset.capabilities?.telemetry, ["logs"]),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (!options.provided.docs) {
|
|
83
|
+
answers.docs = await prompt.multiSelect({
|
|
84
|
+
message: "Docs",
|
|
85
|
+
choices: [
|
|
86
|
+
{ label: "OpenAPI", value: "openapi" },
|
|
87
|
+
{ label: "None", value: "none" },
|
|
88
|
+
],
|
|
89
|
+
defaultValues: withNoneDefault(preset.capabilities?.docs),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (!options.provided.contracts) {
|
|
93
|
+
answers.contracts = await prompt.multiSelect({
|
|
94
|
+
message: "Contracts",
|
|
95
|
+
choices: [
|
|
96
|
+
{ label: "Zod", value: "zod" },
|
|
97
|
+
{ label: "None", value: "none" },
|
|
98
|
+
],
|
|
99
|
+
defaultValues: withNoneDefault(preset.capabilities?.contracts),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (!options.provided.apiClient) {
|
|
103
|
+
answers.apiClient = await prompt.multiSelect({
|
|
104
|
+
message: "API client",
|
|
105
|
+
choices: [
|
|
106
|
+
{ label: "Bruno", value: "bruno" },
|
|
107
|
+
{ label: "None", value: "none" },
|
|
108
|
+
],
|
|
109
|
+
defaultValues: withNoneDefault(preset.capabilities?.apiClient),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (!options.provided.zones) {
|
|
113
|
+
answers.zones = await prompt.multiSelect({
|
|
114
|
+
message: "API zones",
|
|
115
|
+
choices: [
|
|
116
|
+
{ label: "Public", value: "public" },
|
|
117
|
+
{ label: "Private", value: "private" },
|
|
118
|
+
{ label: "Admin", value: "admin" },
|
|
119
|
+
],
|
|
120
|
+
defaultValues: withDefault(preset.zones, ["public", "private", "admin"]),
|
|
121
|
+
required: true,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (!options.provided.packageManager) {
|
|
125
|
+
answers.packageManager = await prompt.select({
|
|
126
|
+
message: "Package manager",
|
|
127
|
+
choices: [
|
|
128
|
+
{ label: "npm", value: "npm" },
|
|
129
|
+
{ label: "pnpm", value: "pnpm" },
|
|
130
|
+
{ label: "yarn", value: "yarn" },
|
|
131
|
+
{ label: "bun", value: "bun" },
|
|
132
|
+
],
|
|
133
|
+
defaultValue: preset.packageManager ?? "npm",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (!options.provided.install) {
|
|
137
|
+
answers.install = await prompt.confirm({
|
|
138
|
+
message: "Install dependencies",
|
|
139
|
+
defaultValue: false,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (!options.provided.gitInit) {
|
|
143
|
+
answers.gitInit = await prompt.confirm({
|
|
144
|
+
message: "Initialize git repository",
|
|
145
|
+
defaultValue: false,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return answers;
|
|
149
|
+
}
|
|
150
|
+
exports.promptCreateProject = promptCreateProject;
|
|
151
|
+
function withNoneDefault(values) {
|
|
152
|
+
return values && values.length > 0 ? values : [];
|
|
153
|
+
}
|
|
154
|
+
function withDefault(values, fallback) {
|
|
155
|
+
return values && values.length > 0 ? values : fallback;
|
|
156
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BrunoGenerationAnalysis } from "../commands/generate/bruno-analysis";
|
|
2
|
+
import { PromptAdapter } from "./prompt-adapter";
|
|
3
|
+
export type GenerateBrunoMode = "missing" | "all" | "e2e" | "abort";
|
|
4
|
+
export interface GenerateBrunoPromptAnswers {
|
|
5
|
+
mode: GenerateBrunoMode;
|
|
6
|
+
}
|
|
7
|
+
export declare function promptGenerateBruno(prompt: PromptAdapter, analysis: BrunoGenerationAnalysis): Promise<GenerateBrunoPromptAnswers>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.promptGenerateBruno = void 0;
|
|
4
|
+
async function promptGenerateBruno(prompt, analysis) {
|
|
5
|
+
const mode = await prompt.select({
|
|
6
|
+
message: "Bruno generation mode",
|
|
7
|
+
choices: [
|
|
8
|
+
{
|
|
9
|
+
label: `Generate missing only (${analysis.missingCount})`,
|
|
10
|
+
value: "missing",
|
|
11
|
+
disabled: analysis.missingCount === 0 ? "No missing Bruno files" : undefined,
|
|
12
|
+
},
|
|
13
|
+
{ label: "Regenerate all unmodified generated files", value: "all" },
|
|
14
|
+
{ label: "Generate E2E flow", value: "e2e" },
|
|
15
|
+
{ label: "Abort", value: "abort" },
|
|
16
|
+
],
|
|
17
|
+
defaultValue: analysis.missingCount > 0 ? "missing" : "all",
|
|
18
|
+
});
|
|
19
|
+
return { mode };
|
|
20
|
+
}
|
|
21
|
+
exports.promptGenerateBruno = promptGenerateBruno;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./prompt.types";
|
|
2
|
+
export * from "./prompt-adapter";
|
|
3
|
+
export * from "./inquirer-prompt-adapter";
|
|
4
|
+
export * from "./mock-prompt-adapter";
|
|
5
|
+
export * from "./create-project.prompt";
|
|
6
|
+
export * from "./add-resource.prompt";
|
|
7
|
+
export * from "./add-route.prompt";
|
|
8
|
+
export * from "./generate-bruno.prompt";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./prompt.types"), exports);
|
|
18
|
+
__exportStar(require("./prompt-adapter"), exports);
|
|
19
|
+
__exportStar(require("./inquirer-prompt-adapter"), exports);
|
|
20
|
+
__exportStar(require("./mock-prompt-adapter"), exports);
|
|
21
|
+
__exportStar(require("./create-project.prompt"), exports);
|
|
22
|
+
__exportStar(require("./add-resource.prompt"), exports);
|
|
23
|
+
__exportStar(require("./add-route.prompt"), exports);
|
|
24
|
+
__exportStar(require("./generate-bruno.prompt"), exports);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PromptAdapter } from "./prompt-adapter";
|
|
2
|
+
import { ConfirmQuestion, InputQuestion, MultiSelectQuestion, SelectQuestion } from "./prompt.types";
|
|
3
|
+
export declare class InquirerPromptAdapter implements PromptAdapter {
|
|
4
|
+
input(question: InputQuestion): Promise<string>;
|
|
5
|
+
confirm(question: ConfirmQuestion): Promise<boolean>;
|
|
6
|
+
select<T extends string>(question: SelectQuestion<T>): Promise<T>;
|
|
7
|
+
multiSelect<T extends string>(question: MultiSelectQuestion<T>): Promise<T[]>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InquirerPromptAdapter = void 0;
|
|
4
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
5
|
+
class InquirerPromptAdapter {
|
|
6
|
+
async input(question) {
|
|
7
|
+
return (0, prompts_1.input)({
|
|
8
|
+
message: question.message,
|
|
9
|
+
default: question.defaultValue,
|
|
10
|
+
validate: question.required ? (value) => value.trim().length > 0 || "Value is required." : undefined,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
async confirm(question) {
|
|
14
|
+
return (0, prompts_1.confirm)({
|
|
15
|
+
message: question.message,
|
|
16
|
+
default: question.defaultValue,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async select(question) {
|
|
20
|
+
return (0, prompts_1.select)({
|
|
21
|
+
message: question.message,
|
|
22
|
+
choices: question.choices.map(toInquirerChoice),
|
|
23
|
+
default: question.defaultValue,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async multiSelect(question) {
|
|
27
|
+
const selected = await (0, prompts_1.checkbox)({
|
|
28
|
+
message: question.message,
|
|
29
|
+
choices: question.choices.map((choice) => ({
|
|
30
|
+
...toInquirerChoice(choice),
|
|
31
|
+
checked: question.defaultValues?.includes(choice.value),
|
|
32
|
+
})),
|
|
33
|
+
required: question.required,
|
|
34
|
+
});
|
|
35
|
+
return normalizeNoneSelection(selected);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.InquirerPromptAdapter = InquirerPromptAdapter;
|
|
39
|
+
function normalizeNoneSelection(values) {
|
|
40
|
+
if (values.length <= 1 || !values.includes("none")) {
|
|
41
|
+
return values;
|
|
42
|
+
}
|
|
43
|
+
return values.filter((value) => value !== "none");
|
|
44
|
+
}
|
|
45
|
+
function toInquirerChoice(choice) {
|
|
46
|
+
return {
|
|
47
|
+
name: choice.label,
|
|
48
|
+
value: choice.value,
|
|
49
|
+
description: choice.description,
|
|
50
|
+
disabled: choice.disabled,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PromptAdapter } from "./prompt-adapter";
|
|
2
|
+
import { ConfirmQuestion, InputQuestion, MultiSelectQuestion, SelectQuestion } from "./prompt.types";
|
|
3
|
+
type MockAnswer = string | boolean | string[];
|
|
4
|
+
export declare class MockPromptAdapter implements PromptAdapter {
|
|
5
|
+
private readonly answers;
|
|
6
|
+
constructor(answers?: MockAnswer[]);
|
|
7
|
+
input(question: InputQuestion): Promise<string>;
|
|
8
|
+
confirm(question: ConfirmQuestion): Promise<boolean>;
|
|
9
|
+
select<T extends string>(question: SelectQuestion<T>): Promise<T>;
|
|
10
|
+
multiSelect<T extends string>(question: MultiSelectQuestion<T>): Promise<T[]>;
|
|
11
|
+
private nextAnswer;
|
|
12
|
+
}
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MockPromptAdapter = void 0;
|
|
4
|
+
class MockPromptAdapter {
|
|
5
|
+
answers;
|
|
6
|
+
constructor(answers = []) {
|
|
7
|
+
this.answers = [...answers];
|
|
8
|
+
}
|
|
9
|
+
async input(question) {
|
|
10
|
+
const answer = this.nextAnswer();
|
|
11
|
+
if (answer === undefined) {
|
|
12
|
+
return question.defaultValue ?? "";
|
|
13
|
+
}
|
|
14
|
+
if (typeof answer !== "string") {
|
|
15
|
+
throw new Error(`Mock prompt answer for "${question.message}" must be a string.`);
|
|
16
|
+
}
|
|
17
|
+
return answer;
|
|
18
|
+
}
|
|
19
|
+
async confirm(question) {
|
|
20
|
+
const answer = this.nextAnswer();
|
|
21
|
+
if (answer === undefined) {
|
|
22
|
+
return question.defaultValue ?? false;
|
|
23
|
+
}
|
|
24
|
+
if (typeof answer !== "boolean") {
|
|
25
|
+
throw new Error(`Mock prompt answer for "${question.message}" must be a boolean.`);
|
|
26
|
+
}
|
|
27
|
+
return answer;
|
|
28
|
+
}
|
|
29
|
+
async select(question) {
|
|
30
|
+
const answer = this.nextAnswer();
|
|
31
|
+
if (answer === undefined) {
|
|
32
|
+
if (question.defaultValue !== undefined) {
|
|
33
|
+
return question.defaultValue;
|
|
34
|
+
}
|
|
35
|
+
const firstChoice = question.choices.find((choice) => !choice.disabled);
|
|
36
|
+
if (firstChoice) {
|
|
37
|
+
return firstChoice.value;
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`Mock prompt has no selectable answer for "${question.message}".`);
|
|
40
|
+
}
|
|
41
|
+
if (typeof answer !== "string") {
|
|
42
|
+
throw new Error(`Mock prompt answer for "${question.message}" must be a string.`);
|
|
43
|
+
}
|
|
44
|
+
return answer;
|
|
45
|
+
}
|
|
46
|
+
async multiSelect(question) {
|
|
47
|
+
const answer = this.nextAnswer();
|
|
48
|
+
if (answer === undefined) {
|
|
49
|
+
return [...(question.defaultValues ?? [])];
|
|
50
|
+
}
|
|
51
|
+
if (!Array.isArray(answer)) {
|
|
52
|
+
throw new Error(`Mock prompt answer for "${question.message}" must be a string array.`);
|
|
53
|
+
}
|
|
54
|
+
return answer;
|
|
55
|
+
}
|
|
56
|
+
nextAnswer() {
|
|
57
|
+
return this.answers.shift();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.MockPromptAdapter = MockPromptAdapter;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ConfirmQuestion, InputQuestion, MultiSelectQuestion, SelectQuestion } from "./prompt.types";
|
|
2
|
+
export interface PromptAdapter {
|
|
3
|
+
input(question: InputQuestion): Promise<string>;
|
|
4
|
+
confirm(question: ConfirmQuestion): Promise<boolean>;
|
|
5
|
+
select<T extends string>(question: SelectQuestion<T>): Promise<T>;
|
|
6
|
+
multiSelect<T extends string>(question: MultiSelectQuestion<T>): Promise<T[]>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface PromptChoice<T extends string> {
|
|
2
|
+
value: T;
|
|
3
|
+
label: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
disabled?: boolean | string;
|
|
6
|
+
}
|
|
7
|
+
export interface InputQuestion {
|
|
8
|
+
message: string;
|
|
9
|
+
defaultValue?: string;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface ConfirmQuestion {
|
|
13
|
+
message: string;
|
|
14
|
+
defaultValue?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface SelectQuestion<T extends string> {
|
|
17
|
+
message: string;
|
|
18
|
+
choices: Array<PromptChoice<T>>;
|
|
19
|
+
defaultValue?: T;
|
|
20
|
+
}
|
|
21
|
+
export interface MultiSelectQuestion<T extends string> {
|
|
22
|
+
message: string;
|
|
23
|
+
choices: Array<PromptChoice<T>>;
|
|
24
|
+
defaultValues?: T[];
|
|
25
|
+
required?: boolean;
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CommandContext } from "../core/command-context";
|
|
2
|
+
import { GeneratedFileEntry, GeneratedFileType, SoapRegistryConfig } from "../config/schemas/types";
|
|
3
|
+
export interface RegisterFileOptions {
|
|
4
|
+
root: string;
|
|
5
|
+
relativePath: string;
|
|
6
|
+
type: GeneratedFileType;
|
|
7
|
+
owner?: string;
|
|
8
|
+
registry: SoapRegistryConfig;
|
|
9
|
+
}
|
|
10
|
+
export declare function hashContent(content: string): string;
|
|
11
|
+
export declare function readFileHash(filePath: string): Promise<string | undefined>;
|
|
12
|
+
export declare function upsertGeneratedFile(registry: SoapRegistryConfig, entry: Omit<GeneratedFileEntry, "generatedAt"> & {
|
|
13
|
+
generatedAt?: string;
|
|
14
|
+
}): void;
|
|
15
|
+
export declare function assertCanWriteGeneratedFile(root: string, relativePath: string, registry: SoapRegistryConfig, options?: {
|
|
16
|
+
force?: boolean;
|
|
17
|
+
writeNew?: boolean;
|
|
18
|
+
}): Promise<string>;
|
|
19
|
+
export declare function registerGeneratedFile(options: RegisterFileOptions, content: string, context: CommandContext): Promise<void>;
|
|
@@ -0,0 +1,68 @@
|
|
|
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.registerGeneratedFile = exports.assertCanWriteGeneratedFile = exports.upsertGeneratedFile = exports.readFileHash = exports.hashContent = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const errors_1 = require("../core/errors");
|
|
11
|
+
function hashContent(content) {
|
|
12
|
+
return crypto_1.default.createHash("sha256").update(content).digest("hex");
|
|
13
|
+
}
|
|
14
|
+
exports.hashContent = hashContent;
|
|
15
|
+
async function readFileHash(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
return hashContent(await promises_1.default.readFile(filePath, "utf8"));
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error.code === "ENOENT") {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.readFileHash = readFileHash;
|
|
27
|
+
function upsertGeneratedFile(registry, entry) {
|
|
28
|
+
const index = registry.generatedFiles.findIndex((item) => item.path === entry.path);
|
|
29
|
+
const next = {
|
|
30
|
+
...entry,
|
|
31
|
+
generatedAt: entry.generatedAt ?? new Date().toISOString(),
|
|
32
|
+
};
|
|
33
|
+
if (index >= 0) {
|
|
34
|
+
registry.generatedFiles[index] = next;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
registry.generatedFiles.push(next);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.upsertGeneratedFile = upsertGeneratedFile;
|
|
41
|
+
async function assertCanWriteGeneratedFile(root, relativePath, registry, options = {}) {
|
|
42
|
+
const targetPath = path_1.default.join(root, relativePath);
|
|
43
|
+
const entry = registry.generatedFiles.find((item) => item.path === relativePath);
|
|
44
|
+
const currentHash = await readFileHash(targetPath);
|
|
45
|
+
if (!currentHash || options.force) {
|
|
46
|
+
return targetPath;
|
|
47
|
+
}
|
|
48
|
+
if (entry && entry.hash === currentHash) {
|
|
49
|
+
return targetPath;
|
|
50
|
+
}
|
|
51
|
+
if (options.writeNew) {
|
|
52
|
+
return `${targetPath}.new`;
|
|
53
|
+
}
|
|
54
|
+
throw new errors_1.CliError(`Refusing to overwrite modified file ${relativePath}. Use --force to overwrite or --write-new to write ${relativePath}.new.`);
|
|
55
|
+
}
|
|
56
|
+
exports.assertCanWriteGeneratedFile = assertCanWriteGeneratedFile;
|
|
57
|
+
async function registerGeneratedFile(options, content, context) {
|
|
58
|
+
upsertGeneratedFile(options.registry, {
|
|
59
|
+
path: options.relativePath,
|
|
60
|
+
type: options.type,
|
|
61
|
+
owner: options.owner,
|
|
62
|
+
hash: hashContent(content),
|
|
63
|
+
});
|
|
64
|
+
if (context.dryRun) {
|
|
65
|
+
context.output.info(`[dry-run] register ${options.relativePath}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.registerGeneratedFile = registerGeneratedFile;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ApiZone, AuthCapability, DatabaseCapability, SoapConfig } from "../config/schemas/types";
|
|
2
|
+
import { CommandInputResolver } from "./resolver.types";
|
|
3
|
+
export interface AddResourceInput {
|
|
4
|
+
crud?: boolean;
|
|
5
|
+
db?: "none" | DatabaseCapability;
|
|
6
|
+
auth?: "none" | AuthCapability | "jwt";
|
|
7
|
+
zone?: ApiZone;
|
|
8
|
+
}
|
|
9
|
+
export interface AddResourceResult {
|
|
10
|
+
crud: boolean;
|
|
11
|
+
db: "none" | DatabaseCapability;
|
|
12
|
+
auth: "none" | "jwt" | "api-key";
|
|
13
|
+
zone: ApiZone;
|
|
14
|
+
}
|
|
15
|
+
export declare class AddResourceResolver implements CommandInputResolver<AddResourceInput, AddResourceInput, SoapConfig, AddResourceResult> {
|
|
16
|
+
resolve(input: {
|
|
17
|
+
flags: AddResourceInput;
|
|
18
|
+
promptAnswers?: AddResourceInput;
|
|
19
|
+
projectConfig?: SoapConfig;
|
|
20
|
+
preset?: Partial<AddResourceResult>;
|
|
21
|
+
}): AddResourceResult;
|
|
22
|
+
}
|
|
23
|
+
export declare const addResourceResolver: AddResourceResolver;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addResourceResolver = exports.AddResourceResolver = void 0;
|
|
4
|
+
const errors_1 = require("../core/errors");
|
|
5
|
+
class AddResourceResolver {
|
|
6
|
+
resolve(input) {
|
|
7
|
+
const config = requireProjectConfig(input.projectConfig);
|
|
8
|
+
const flags = input.flags;
|
|
9
|
+
const promptAnswers = input.promptAnswers ?? {};
|
|
10
|
+
const preset = input.preset ?? {};
|
|
11
|
+
const db = pick(flags.db, promptAnswers.db, preset.db, firstOrNone(config.project.capabilities.databases));
|
|
12
|
+
const auth = normalizeRouteAuth(pick(flags.auth, promptAnswers.auth, preset.auth, firstAuthForRoutes(config.project.capabilities.auth)));
|
|
13
|
+
const zone = pick(flags.zone, promptAnswers.zone, preset.zone, defaultZone(config));
|
|
14
|
+
assertCapability("database", db, ["none", ...config.project.capabilities.databases]);
|
|
15
|
+
assertCapability("auth", auth, enabledRouteAuthValues(config.project.capabilities.auth));
|
|
16
|
+
assertCapability("zone", zone, config.project.zones);
|
|
17
|
+
return {
|
|
18
|
+
crud: Boolean(pick(flags.crud, promptAnswers.crud, preset.crud, false)),
|
|
19
|
+
db,
|
|
20
|
+
auth,
|
|
21
|
+
zone,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.AddResourceResolver = AddResourceResolver;
|
|
26
|
+
function requireProjectConfig(config) {
|
|
27
|
+
if (!config) {
|
|
28
|
+
throw new errors_1.CliError("Project config is required to resolve resource input.");
|
|
29
|
+
}
|
|
30
|
+
return config;
|
|
31
|
+
}
|
|
32
|
+
function pick(...values) {
|
|
33
|
+
const value = values.find((item) => item !== undefined);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function firstOrNone(values) {
|
|
37
|
+
return values[0] ?? "none";
|
|
38
|
+
}
|
|
39
|
+
function firstAuthForRoutes(values) {
|
|
40
|
+
if (values.includes("jwt") || values.includes("local")) {
|
|
41
|
+
return "jwt";
|
|
42
|
+
}
|
|
43
|
+
return values[0] ?? "none";
|
|
44
|
+
}
|
|
45
|
+
function normalizeRouteAuth(value) {
|
|
46
|
+
if (value === "local") {
|
|
47
|
+
return "jwt";
|
|
48
|
+
}
|
|
49
|
+
if (value === "jwt" || value === "api-key" || value === "none") {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
throw new errors_1.CliError(`Auth "${value}" cannot protect routes. Use jwt, api-key, or none.`);
|
|
53
|
+
}
|
|
54
|
+
function enabledRouteAuthValues(values) {
|
|
55
|
+
const allowed = ["none"];
|
|
56
|
+
if (values.includes("jwt") || values.includes("local")) {
|
|
57
|
+
allowed.push("jwt");
|
|
58
|
+
}
|
|
59
|
+
if (values.includes("api-key")) {
|
|
60
|
+
allowed.push("api-key");
|
|
61
|
+
}
|
|
62
|
+
return allowed;
|
|
63
|
+
}
|
|
64
|
+
function defaultZone(config) {
|
|
65
|
+
return config.project.zones.includes("public") ? "public" : config.project.zones[0] ?? "public";
|
|
66
|
+
}
|
|
67
|
+
function assertCapability(label, value, allowed) {
|
|
68
|
+
if (!allowed.includes(value)) {
|
|
69
|
+
const allowedValues = allowed.length > 0 ? allowed.join(", ") : "none";
|
|
70
|
+
throw new errors_1.CliError(`${label} "${value}" is not enabled for this project. Allowed values: ${allowedValues}.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.addResourceResolver = new AddResourceResolver();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ApiZone, AuthCapability, ResourceRegistryEntry, SoapConfig } from "../config/schemas/types";
|
|
2
|
+
import { RouteMethod } from "../commands/add/route-plan";
|
|
3
|
+
import { CommandInputResolver } from "./resolver.types";
|
|
4
|
+
export interface AddRouteInput {
|
|
5
|
+
method?: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
useCase?: string;
|
|
8
|
+
command?: string;
|
|
9
|
+
query?: string;
|
|
10
|
+
auth?: "none" | AuthCapability | "jwt";
|
|
11
|
+
zone?: ApiZone;
|
|
12
|
+
}
|
|
13
|
+
export interface AddRouteConfig {
|
|
14
|
+
project: SoapConfig;
|
|
15
|
+
resource: ResourceRegistryEntry;
|
|
16
|
+
}
|
|
17
|
+
export interface AddRouteResult {
|
|
18
|
+
method: RouteMethod;
|
|
19
|
+
path?: string;
|
|
20
|
+
useCase?: string;
|
|
21
|
+
command?: string;
|
|
22
|
+
query?: string;
|
|
23
|
+
auth: "none" | "jwt" | "api-key";
|
|
24
|
+
zone: ApiZone;
|
|
25
|
+
}
|
|
26
|
+
export declare class AddRouteResolver implements CommandInputResolver<AddRouteInput, AddRouteInput, AddRouteConfig, AddRouteResult> {
|
|
27
|
+
resolve(input: {
|
|
28
|
+
flags: AddRouteInput;
|
|
29
|
+
promptAnswers?: AddRouteInput;
|
|
30
|
+
projectConfig?: AddRouteConfig;
|
|
31
|
+
preset?: Partial<AddRouteResult>;
|
|
32
|
+
}): AddRouteResult;
|
|
33
|
+
}
|
|
34
|
+
export declare const addRouteResolver: AddRouteResolver;
|