@swovohq/fuel 0.2.2-alpha.9 → 0.2.2-alpha.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -69
- package/dist/bin/fuel.js +77 -31
- package/dist/bin/fuel.js.map +1 -1
- package/dist/cleanup/finalize.d.ts.map +1 -1
- package/dist/cleanup/finalize.js +11 -3
- package/dist/cleanup/finalize.js.map +1 -1
- package/dist/commands/config-verify.d.ts.map +1 -1
- package/dist/commands/config-verify.js +14 -6
- package/dist/commands/config-verify.js.map +1 -1
- package/dist/commands/create-app.d.ts.map +1 -1
- package/dist/commands/create-app.js +41 -7
- package/dist/commands/create-app.js.map +1 -1
- package/dist/commands/infra-deploy.d.ts +15 -1
- package/dist/commands/infra-deploy.d.ts.map +1 -1
- package/dist/commands/infra-deploy.js +164 -18
- package/dist/commands/infra-deploy.js.map +1 -1
- package/dist/commands/infra-destroy.d.ts.map +1 -1
- package/dist/commands/infra-destroy.js +3 -3
- package/dist/commands/infra-destroy.js.map +1 -1
- package/dist/commands/infra-init.d.ts +91 -0
- package/dist/commands/infra-init.d.ts.map +1 -1
- package/dist/commands/infra-init.js +644 -113
- package/dist/commands/infra-init.js.map +1 -1
- package/dist/commands/infra-pull.d.ts +8 -0
- package/dist/commands/infra-pull.d.ts.map +1 -0
- package/dist/commands/infra-pull.js +81 -0
- package/dist/commands/infra-pull.js.map +1 -0
- package/dist/commands/list-modules.d.ts.map +1 -1
- package/dist/commands/list-modules.js +10 -3
- package/dist/commands/list-modules.js.map +1 -1
- package/dist/engines/convention.d.ts +2 -2
- package/dist/engines/convention.d.ts.map +1 -1
- package/dist/engines/convention.js +8 -6
- package/dist/engines/convention.js.map +1 -1
- package/dist/engines/dependencies.d.ts +2 -0
- package/dist/engines/dependencies.d.ts.map +1 -1
- package/dist/engines/dependencies.js.map +1 -1
- package/dist/engines/monorepo.d.ts.map +1 -1
- package/dist/engines/monorepo.js.map +1 -1
- package/dist/engines/template-source.d.ts.map +1 -1
- package/dist/engines/template-source.js +3 -1
- package/dist/engines/template-source.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infra/azure-defaults.d.ts +49 -0
- package/dist/infra/azure-defaults.d.ts.map +1 -0
- package/dist/infra/azure-defaults.js +52 -0
- package/dist/infra/azure-defaults.js.map +1 -0
- package/dist/infra/azure-keyvault-client.d.ts +4 -0
- package/dist/infra/azure-keyvault-client.d.ts.map +1 -0
- package/dist/infra/azure-keyvault-client.js +57 -0
- package/dist/infra/azure-keyvault-client.js.map +1 -0
- package/dist/infra/azure-rbac.d.ts +11 -0
- package/dist/infra/azure-rbac.d.ts.map +1 -0
- package/dist/infra/azure-rbac.js +49 -0
- package/dist/infra/azure-rbac.js.map +1 -0
- package/dist/infra/azure-state-storage.d.ts +20 -0
- package/dist/infra/azure-state-storage.d.ts.map +1 -0
- package/dist/infra/azure-state-storage.js +229 -0
- package/dist/infra/azure-state-storage.js.map +1 -0
- package/dist/infra/config-loader.d.ts.map +1 -1
- package/dist/infra/config-loader.js.map +1 -1
- package/dist/infra/config-writer.js +1 -1
- package/dist/infra/credentials.d.ts +2 -2
- package/dist/infra/credentials.d.ts.map +1 -1
- package/dist/infra/credentials.js +8 -4
- package/dist/infra/credentials.js.map +1 -1
- package/dist/infra/dependency-checker.d.ts.map +1 -1
- package/dist/infra/dependency-checker.js.map +1 -1
- package/dist/infra/deployment-detect.d.ts +9 -0
- package/dist/infra/deployment-detect.d.ts.map +1 -0
- package/dist/infra/deployment-detect.js +53 -0
- package/dist/infra/deployment-detect.js.map +1 -0
- package/dist/infra/git-client.d.ts.map +1 -1
- package/dist/infra/git-client.js +6 -10
- package/dist/infra/git-client.js.map +1 -1
- package/dist/infra/github-client.d.ts +7 -0
- package/dist/infra/github-client.d.ts.map +1 -1
- package/dist/infra/github-client.js +47 -7
- package/dist/infra/github-client.js.map +1 -1
- package/dist/infra/github-secrets-client.d.ts.map +1 -1
- package/dist/infra/github-secrets-client.js +2 -2
- package/dist/infra/github-secrets-client.js.map +1 -1
- package/dist/infra/github-token-link.d.ts +4 -0
- package/dist/infra/github-token-link.d.ts.map +1 -0
- package/dist/infra/github-token-link.js +32 -0
- package/dist/infra/github-token-link.js.map +1 -0
- package/dist/infra/orchestrator.d.ts +4 -1
- package/dist/infra/orchestrator.d.ts.map +1 -1
- package/dist/infra/orchestrator.js +218 -63
- package/dist/infra/orchestrator.js.map +1 -1
- package/dist/infra/port-utils.d.ts.map +1 -1
- package/dist/infra/port-utils.js.map +1 -1
- package/dist/infra/preflight.d.ts.map +1 -1
- package/dist/infra/preflight.js +25 -11
- package/dist/infra/preflight.js.map +1 -1
- package/dist/infra/remote-secret.d.ts +14 -0
- package/dist/infra/remote-secret.d.ts.map +1 -0
- package/dist/infra/remote-secret.js +42 -0
- package/dist/infra/remote-secret.js.map +1 -0
- package/dist/infra/s3-state-bucket.d.ts.map +1 -1
- package/dist/infra/s3-state-bucket.js +8 -4
- package/dist/infra/s3-state-bucket.js.map +1 -1
- package/dist/infra/secrets-manager-client.d.ts.map +1 -1
- package/dist/infra/secrets-manager-client.js +8 -5
- package/dist/infra/secrets-manager-client.js.map +1 -1
- package/dist/infra/subprocess.js +1 -1
- package/dist/infra/template-engine.d.ts +14 -5
- package/dist/infra/template-engine.d.ts.map +1 -1
- package/dist/infra/template-engine.js +157 -39
- package/dist/infra/template-engine.js.map +1 -1
- package/dist/infra/tofu-runner.d.ts +4 -3
- package/dist/infra/tofu-runner.d.ts.map +1 -1
- package/dist/infra/tofu-runner.js +40 -34
- package/dist/infra/tofu-runner.js.map +1 -1
- package/dist/infra/types.d.ts +241 -17
- package/dist/infra/types.d.ts.map +1 -1
- package/dist/infra/types.js +419 -22
- package/dist/infra/types.js.map +1 -1
- package/dist/infra-templates/TODO-azure-prod.md +127 -0
- package/dist/infra-templates/modules/azure-container-app/main.tf +124 -0
- package/dist/infra-templates/modules/azure-container-app/outputs.tf +24 -0
- package/dist/infra-templates/modules/azure-container-app/variables.tf +80 -0
- package/dist/infra-templates/modules/azure-container-apps-environment/main.tf +18 -0
- package/dist/infra-templates/modules/azure-container-apps-environment/outputs.tf +14 -0
- package/dist/infra-templates/modules/azure-container-apps-environment/variables.tf +24 -0
- package/dist/infra-templates/modules/azure-container-registry/main.tf +10 -0
- package/dist/infra-templates/modules/azure-container-registry/outputs.tf +14 -0
- package/dist/infra-templates/modules/azure-container-registry/variables.tf +20 -0
- package/dist/infra-templates/modules/azure-front-door/main.tf +80 -0
- package/dist/infra-templates/modules/azure-front-door/outputs.tf +20 -0
- package/dist/infra-templates/modules/azure-front-door/variables.tf +26 -0
- package/dist/infra-templates/modules/azure-github-oidc/main.tf +48 -0
- package/dist/infra-templates/modules/azure-github-oidc/outputs.tf +9 -0
- package/dist/infra-templates/modules/azure-github-oidc/variables.tf +69 -0
- package/dist/infra-templates/modules/azure-key-vault/main.tf +21 -0
- package/dist/infra-templates/modules/azure-key-vault/outputs.tf +11 -0
- package/dist/infra-templates/modules/azure-key-vault/variables.tf +43 -0
- package/dist/infra-templates/modules/azure-monitor/main.tf +29 -0
- package/dist/infra-templates/modules/azure-monitor/outputs.tf +17 -0
- package/dist/infra-templates/modules/azure-monitor/variables.tf +32 -0
- package/dist/infra-templates/modules/azure-network/main.tf +134 -0
- package/dist/infra-templates/modules/azure-network/outputs.tf +44 -0
- package/dist/infra-templates/modules/azure-network/variables.tf +56 -0
- package/dist/infra-templates/modules/azure-postgresql-flexible/main.tf +34 -0
- package/dist/infra-templates/modules/azure-postgresql-flexible/outputs.tf +20 -0
- package/dist/infra-templates/modules/azure-postgresql-flexible/variables.tf +54 -0
- package/dist/infra-templates/modules/azure-redis-cache/main.tf +32 -0
- package/dist/infra-templates/modules/azure-redis-cache/outputs.tf +15 -0
- package/dist/infra-templates/modules/azure-redis-cache/variables.tf +30 -0
- package/dist/infra-templates/modules/azure-resource-group/main.tf +5 -0
- package/dist/infra-templates/modules/azure-resource-group/outputs.tf +11 -0
- package/dist/infra-templates/modules/azure-resource-group/variables.tf +16 -0
- package/dist/infra-templates/modules/azure-sql-database/main.tf +63 -0
- package/dist/infra-templates/modules/azure-sql-database/outputs.tf +20 -0
- package/dist/infra-templates/modules/azure-sql-database/variables.tf +78 -0
- package/dist/infra-templates/modules/azure-storage-account/main.tf +13 -0
- package/dist/infra-templates/modules/azure-storage-account/outputs.tf +16 -0
- package/dist/infra-templates/modules/azure-storage-account/variables.tf +38 -0
- package/dist/infra-templates/modules/cloud-watch/event-bridge.tf +15 -0
- package/dist/infra-templates/modules/cloud-watch/lambda.tf +84 -0
- package/dist/infra-templates/modules/cloud-watch/lambda_function.zip +0 -0
- package/dist/infra-templates/modules/cloud-watch/lambda_function_deploy.zip +0 -0
- package/dist/infra-templates/modules/cloud-watch/main.tf +156 -0
- package/dist/infra-templates/modules/cloud-watch/sns.tf +3 -0
- package/dist/infra-templates/modules/cloud-watch/variables.tf +38 -0
- package/dist/infra-templates/modules/ecs-core/alb.tf +126 -0
- package/dist/infra-templates/modules/ecs-core/cloudwatch.tf +48 -0
- package/dist/infra-templates/modules/ecs-core/iam.tf +95 -0
- package/dist/infra-templates/modules/ecs-core/main.tf +12 -0
- package/dist/infra-templates/modules/ecs-core/outputs.tf +44 -0
- package/dist/infra-templates/modules/ecs-core/secrets.tf +9 -0
- package/dist/infra-templates/modules/ecs-core/security_groups.tf +73 -0
- package/dist/infra-templates/modules/ecs-core/variables.tf +96 -0
- package/dist/infra-templates/modules/ecs-service/ecs_autoscaling.tf +51 -0
- package/dist/infra-templates/modules/ecs-service/ecs_service.tf +39 -0
- package/dist/infra-templates/modules/ecs-service/ecs_task_definition.tf +56 -0
- package/dist/infra-templates/modules/ecs-service/variables.tf +170 -0
- package/dist/infra-templates/modules/elastic-container-registry/main.tf +32 -0
- package/dist/infra-templates/modules/elastic-container-registry/outputs.tf +9 -0
- package/dist/infra-templates/modules/elastic-container-registry/variables.tf +25 -0
- package/dist/infra-templates/modules/github-oidc-role/main.tf +46 -0
- package/dist/infra-templates/modules/github-oidc-role/outputs.tf +4 -0
- package/dist/infra-templates/modules/github-oidc-role/variables.tf +11 -0
- package/dist/infra-templates/modules/guardduty/main.tf +8 -0
- package/dist/infra-templates/modules/iam/main.tf +205 -0
- package/dist/infra-templates/modules/iam/outputs.tf +3 -0
- package/dist/infra-templates/modules/iam/variables.tf +4 -0
- package/dist/infra-templates/modules/random-key/main.tf +11 -0
- package/dist/infra-templates/modules/random-key/outputs.tf +4 -0
- package/dist/infra-templates/modules/random-key/variables.tf +10 -0
- package/dist/infra-templates/modules/redis/main.tf +47 -0
- package/dist/infra-templates/modules/redis/outputs.tf +8 -0
- package/dist/infra-templates/modules/redis/variables.tf +20 -0
- package/dist/infra-templates/modules/relational-database-service/main.tf +44 -0
- package/dist/infra-templates/modules/relational-database-service/outputs.tf +13 -0
- package/dist/infra-templates/modules/relational-database-service/security-group.tf +15 -0
- package/dist/infra-templates/modules/relational-database-service/variables.tf +50 -0
- package/dist/infra-templates/modules/secret/main.tf +14 -0
- package/dist/infra-templates/modules/secret/outputs.tf +3 -0
- package/dist/infra-templates/modules/secret/variables.tf +8 -0
- package/dist/infra-templates/modules/simple-storage-service/main.tf +28 -0
- package/dist/infra-templates/modules/simple-storage-service/variables.tf +4 -0
- package/dist/infra-templates/modules/vpc/data.tf +2 -0
- package/dist/infra-templates/modules/vpc/main.tf +76 -0
- package/dist/infra-templates/modules/vpc/outputs.tf +23 -0
- package/dist/infra-templates/modules/vpc/variables.tf +5 -0
- package/dist/infra-templates/package.json +6 -0
- package/dist/infra-templates/templates/azure/MANUAL_DEPLOY.md +168 -0
- package/dist/infra-templates/templates/azure/backend.tf +8 -0
- package/dist/infra-templates/templates/azure/data.tf +21 -0
- package/dist/infra-templates/templates/azure/default.tfvars +0 -0
- package/dist/infra-templates/templates/azure/locals.tf +116 -0
- package/dist/infra-templates/templates/azure/main.tf +421 -0
- package/dist/infra-templates/templates/azure/outputs.tf +40 -0
- package/dist/infra-templates/templates/azure/providers.tf +65 -0
- package/dist/infra-templates/templates/azure/variables.tf +69 -0
- package/dist/infra-templates/templates/azure/workflows/build_and_deploy.yml +155 -0
- package/dist/infra-templates/templates/azure/workflows/tests.yml +63 -0
- package/dist/infra-templates/templates/ecs/README.md +235 -0
- package/dist/infra-templates/templates/ecs/backend.tf +7 -0
- package/dist/infra-templates/templates/ecs/data.tf +6 -0
- package/dist/infra-templates/templates/ecs/default.tfvars +1 -0
- package/dist/infra-templates/templates/ecs/locals.tf +97 -0
- package/dist/infra-templates/templates/ecs/main.tf +185 -0
- package/dist/infra-templates/templates/ecs/outputs.tf +42 -0
- package/dist/infra-templates/templates/ecs/providers.tf +21 -0
- package/dist/infra-templates/templates/ecs/variables.tf +105 -0
- package/dist/infra-templates/templates/ecs/workflows/build_and_deploy.yml +188 -0
- package/dist/infra-templates/templates/ecs/workflows/tests.yml +65 -0
- package/package.json +10 -2
- package/schema/fuel.schema.json +570 -91
|
@@ -36,8 +36,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.CONFIG_TEMPLATE = void 0;
|
|
39
|
+
exports.CONFIG_TEMPLATE_AZURE = exports.CONFIG_TEMPLATE = void 0;
|
|
40
40
|
exports.applyInfraInitAnswers = applyInfraInitAnswers;
|
|
41
|
+
exports.applyAzureInfraInitAnswers = applyAzureInfraInitAnswers;
|
|
41
42
|
exports.infraInit = infraInit;
|
|
42
43
|
const fs = __importStar(require("fs-extra"));
|
|
43
44
|
const path = __importStar(require("path"));
|
|
@@ -47,16 +48,26 @@ const promises_1 = require("readline/promises");
|
|
|
47
48
|
const child_process_1 = require("child_process");
|
|
48
49
|
const util_1 = require("util");
|
|
49
50
|
const https = __importStar(require("https"));
|
|
51
|
+
const identity_1 = require("@azure/identity");
|
|
52
|
+
const arm_resources_1 = require("@azure/arm-resources");
|
|
53
|
+
const azure_defaults_1 = require("../infra/azure-defaults");
|
|
54
|
+
const github_client_1 = require("../infra/github-client");
|
|
55
|
+
const github_token_link_1 = require("../infra/github-token-link");
|
|
50
56
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
51
57
|
function httpsGetJson(url, headers) {
|
|
52
58
|
return new Promise((resolve, reject) => {
|
|
53
59
|
const req = https.get(url, { headers }, (res) => {
|
|
54
60
|
let body = '';
|
|
55
|
-
res.on('data', (chunk) => {
|
|
61
|
+
res.on('data', (chunk) => {
|
|
62
|
+
body += chunk.toString();
|
|
63
|
+
});
|
|
56
64
|
res.on('end', () => resolve({ status: res.statusCode ?? 0, body }));
|
|
57
65
|
});
|
|
58
66
|
req.on('error', reject);
|
|
59
|
-
req.setTimeout(10000, () => {
|
|
67
|
+
req.setTimeout(10000, () => {
|
|
68
|
+
req.destroy();
|
|
69
|
+
reject(new Error('Request timed out'));
|
|
70
|
+
});
|
|
60
71
|
});
|
|
61
72
|
}
|
|
62
73
|
exports.CONFIG_TEMPLATE = {
|
|
@@ -80,7 +91,7 @@ exports.CONFIG_TEMPLATE = {
|
|
|
80
91
|
db_instance_type: 'db.t3.small',
|
|
81
92
|
db_allocated_storage: 30,
|
|
82
93
|
variables: {},
|
|
83
|
-
workers: []
|
|
94
|
+
workers: []
|
|
84
95
|
},
|
|
85
96
|
{
|
|
86
97
|
name: 'my-app-web',
|
|
@@ -93,10 +104,59 @@ exports.CONFIG_TEMPLATE = {
|
|
|
93
104
|
cpu_units: 1024,
|
|
94
105
|
memory_units: 2048,
|
|
95
106
|
build_env: {
|
|
96
|
-
NEXT_PUBLIC_API_HOST: 'https://api.example.com'
|
|
107
|
+
NEXT_PUBLIC_API_HOST: 'https://api.example.com'
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
};
|
|
112
|
+
exports.CONFIG_TEMPLATE_AZURE = {
|
|
113
|
+
name: 'my-app',
|
|
114
|
+
deployment: 'azure',
|
|
115
|
+
slack_webhook_url: '',
|
|
116
|
+
location: 'eastus2',
|
|
117
|
+
monitor: { daily_data_cap_gb: 1, retention_days: 90, dashboard_count: 0 },
|
|
118
|
+
front_door: { sku_name: 'Standard_AzureFrontDoor' },
|
|
119
|
+
storage_account: {
|
|
120
|
+
account_kind: 'BlockBlobStorage',
|
|
121
|
+
account_tier: 'Premium',
|
|
122
|
+
account_replication_type: 'LRS',
|
|
123
|
+
access_tier: 'Hot'
|
|
124
|
+
},
|
|
125
|
+
key_vault: { sku_name: 'standard' },
|
|
126
|
+
apps: [
|
|
127
|
+
{
|
|
128
|
+
name: 'my-app-api',
|
|
129
|
+
technology: 'nest',
|
|
130
|
+
dockerfile: 'apps/api/Dockerfile',
|
|
131
|
+
health_check_path: '/health',
|
|
132
|
+
domain: 'api.example.com',
|
|
133
|
+
port: 3000,
|
|
134
|
+
branch: 'main',
|
|
135
|
+
database: { enabled: true, engine: 'mssql', sku_name: 'GP_S_Gen5_4' },
|
|
136
|
+
redis: { enabled: false, sku_name: 'Balanced_B0' },
|
|
137
|
+
container_app: {
|
|
138
|
+
cpu: 1,
|
|
139
|
+
memory: '2Gi',
|
|
140
|
+
concurrent_requests: 20,
|
|
141
|
+
min_replicas: 0,
|
|
142
|
+
max_replicas: 3
|
|
97
143
|
},
|
|
144
|
+
variables: {},
|
|
145
|
+
workers: []
|
|
98
146
|
},
|
|
99
|
-
|
|
147
|
+
{
|
|
148
|
+
name: 'my-app-web',
|
|
149
|
+
technology: 'react',
|
|
150
|
+
dockerfile: 'apps/web/Dockerfile',
|
|
151
|
+
health_check_path: '/health',
|
|
152
|
+
domain: 'app.example.com',
|
|
153
|
+
port: 3000,
|
|
154
|
+
branch: 'main',
|
|
155
|
+
build_env: {
|
|
156
|
+
NEXT_PUBLIC_API_HOST: 'https://api.example.com'
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
]
|
|
100
160
|
};
|
|
101
161
|
// --- Private prompt helpers ---
|
|
102
162
|
async function ask(rl, label, defaultVal) {
|
|
@@ -134,6 +194,14 @@ async function askSelect(rl, label, options, defaultIdx = 1) {
|
|
|
134
194
|
console.log(` Please enter a number between 1 and ${options.length}.`);
|
|
135
195
|
}
|
|
136
196
|
}
|
|
197
|
+
/** Prompts which cloud provider to provision infrastructure for. */
|
|
198
|
+
async function askCloudChoice(rl) {
|
|
199
|
+
console.log(' Cloud provider');
|
|
200
|
+
console.log(' ────────────────────────────────────────────────────');
|
|
201
|
+
const choice = await askSelect(rl, 'Deploy to', ['AWS (ECS)', 'Azure (Container Apps)'], 1);
|
|
202
|
+
console.log('');
|
|
203
|
+
return choice === 'AWS (ECS)' ? 'aws' : 'azure';
|
|
204
|
+
}
|
|
137
205
|
/** Prompts for KEY=VALUE pairs until the user submits an empty line. */
|
|
138
206
|
async function askKeyValues(rl, label) {
|
|
139
207
|
console.log(`\n ${label} (KEY=VALUE format, empty line to finish):`);
|
|
@@ -152,18 +220,73 @@ async function askKeyValues(rl, label) {
|
|
|
152
220
|
}
|
|
153
221
|
return result;
|
|
154
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Opens a GitHub token-creation page with the required scope already checked — the user
|
|
225
|
+
* never has to know or look up which permissions the CLI needs — then collects the pasted
|
|
226
|
+
* token and validates it immediately so a wrong/incomplete token surfaces right away instead
|
|
227
|
+
* of deep into a later step (repo creation, push, branch protection).
|
|
228
|
+
*/
|
|
229
|
+
async function askGitHubToken(rl) {
|
|
230
|
+
const url = (0, github_token_link_1.buildGitHubTokenUrl)();
|
|
231
|
+
console.log(chalk_1.default.cyan('\n Create a GitHub Personal Access Token'));
|
|
232
|
+
console.log(` ${url}`);
|
|
233
|
+
console.log(' The "repo" scope is already selected — just click "Generate token" at the bottom of the page.\n');
|
|
234
|
+
(0, github_token_link_1.openInBrowser)(url);
|
|
235
|
+
while (true) {
|
|
236
|
+
const token = await ask(rl, 'Paste the generated token (GITHUB_TOKEN)');
|
|
237
|
+
try {
|
|
238
|
+
await (0, github_client_1.validateGitHubToken)(token);
|
|
239
|
+
console.log(chalk_1.default.green(' ✓') + ' Token has the required permissions');
|
|
240
|
+
return { token };
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
console.log(chalk_1.default.yellow(` ⚠ ${err instanceof Error ? err.message : String(err)}`));
|
|
244
|
+
const retry = await askYesNo(rl, ' Try pasting the token again?', true);
|
|
245
|
+
if (!retry) {
|
|
246
|
+
throw new Error('GitHub token setup aborted — a validated token is required to continue.');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/** Lists the orgs available to the given token and lets the user pick one, falling back to manual entry. */
|
|
252
|
+
async function askGitHubOrganizationFor(rl, token) {
|
|
253
|
+
try {
|
|
254
|
+
const { status, body } = await httpsGetJson('https://api.github.com/user/orgs', {
|
|
255
|
+
Authorization: `Bearer ${token}`,
|
|
256
|
+
'User-Agent': 'fuel-cli',
|
|
257
|
+
Accept: 'application/vnd.github+json'
|
|
258
|
+
});
|
|
259
|
+
if (status === 200) {
|
|
260
|
+
const orgs = JSON.parse(body);
|
|
261
|
+
if (orgs.length === 1) {
|
|
262
|
+
console.log(` GitHub Org: ${chalk_1.default.green(orgs[0].login)} (only org available to this account)`);
|
|
263
|
+
return orgs[0].login;
|
|
264
|
+
}
|
|
265
|
+
if (orgs.length > 1) {
|
|
266
|
+
return askSelect(rl, 'GitHub organization', orgs.map((o) => o.login), 1);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// fall through to manual entry
|
|
272
|
+
}
|
|
273
|
+
return ask(rl, 'GITHUB_ORGANIZATION');
|
|
274
|
+
}
|
|
155
275
|
/** Calls GET /user on the GitHub API to verify the token. Returns login + email on success or `{ error: string }` on failure. */
|
|
156
276
|
async function tryGetGithubUser(credentials) {
|
|
157
277
|
try {
|
|
158
278
|
const { status, body } = await httpsGetJson('https://api.github.com/user', {
|
|
159
279
|
Authorization: `Bearer ${credentials.GITHUB_TOKEN}`,
|
|
160
280
|
'User-Agent': 'fuel-cli',
|
|
161
|
-
Accept: 'application/vnd.github+json'
|
|
281
|
+
Accept: 'application/vnd.github+json'
|
|
162
282
|
});
|
|
163
283
|
const data = JSON.parse(body);
|
|
164
284
|
if (status !== 200)
|
|
165
285
|
return { error: data.message ?? `HTTP ${status}` };
|
|
166
|
-
return {
|
|
286
|
+
return {
|
|
287
|
+
login: data.login ?? '(unknown)',
|
|
288
|
+
email: data.email ?? '(private)'
|
|
289
|
+
};
|
|
167
290
|
}
|
|
168
291
|
catch (err) {
|
|
169
292
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
@@ -178,7 +301,7 @@ async function tryGetGithubOrg(credentials) {
|
|
|
178
301
|
const { status, body } = await httpsGetJson(`https://api.github.com/orgs/${encodeURIComponent(org)}`, {
|
|
179
302
|
Authorization: `Bearer ${credentials.GITHUB_TOKEN}`,
|
|
180
303
|
'User-Agent': 'fuel-cli',
|
|
181
|
-
Accept: 'application/vnd.github+json'
|
|
304
|
+
Accept: 'application/vnd.github+json'
|
|
182
305
|
});
|
|
183
306
|
const data = JSON.parse(body);
|
|
184
307
|
if (status !== 200)
|
|
@@ -197,9 +320,9 @@ async function tryGetAwsAccountId(credentials) {
|
|
|
197
320
|
...process.env,
|
|
198
321
|
AWS_ACCESS_KEY_ID: credentials.AWS_ACCESS_KEY_ID,
|
|
199
322
|
AWS_SECRET_ACCESS_KEY: credentials.AWS_SECRET_ACCESS_KEY,
|
|
200
|
-
AWS_REGION: credentials.AWS_REGION
|
|
323
|
+
AWS_REGION: credentials.AWS_REGION
|
|
201
324
|
},
|
|
202
|
-
timeout: 10000
|
|
325
|
+
timeout: 10000
|
|
203
326
|
});
|
|
204
327
|
const accountId = JSON.parse(stdout).Account ?? '';
|
|
205
328
|
return { accountId };
|
|
@@ -207,7 +330,10 @@ async function tryGetAwsAccountId(credentials) {
|
|
|
207
330
|
catch (err) {
|
|
208
331
|
const e = err;
|
|
209
332
|
const raw = e.stderr ?? e.message ?? String(err);
|
|
210
|
-
const firstLine = raw
|
|
333
|
+
const firstLine = raw
|
|
334
|
+
.split('\n')
|
|
335
|
+
.map((l) => l.trim())
|
|
336
|
+
.find((l) => l.length > 0) ?? raw;
|
|
211
337
|
return { error: firstLine };
|
|
212
338
|
}
|
|
213
339
|
}
|
|
@@ -219,23 +345,39 @@ async function tryGetAcmCertificate(arn, credentials) {
|
|
|
219
345
|
...process.env,
|
|
220
346
|
AWS_ACCESS_KEY_ID: credentials.AWS_ACCESS_KEY_ID,
|
|
221
347
|
AWS_SECRET_ACCESS_KEY: credentials.AWS_SECRET_ACCESS_KEY,
|
|
222
|
-
AWS_REGION: credentials.AWS_REGION
|
|
348
|
+
AWS_REGION: credentials.AWS_REGION
|
|
223
349
|
},
|
|
224
|
-
timeout: 10000
|
|
350
|
+
timeout: 10000
|
|
225
351
|
});
|
|
226
352
|
const cert = JSON.parse(stdout).Certificate;
|
|
227
353
|
return {
|
|
228
354
|
domainName: cert?.DomainName ?? '(unknown)',
|
|
229
|
-
status: cert?.Status ?? '(unknown)'
|
|
355
|
+
status: cert?.Status ?? '(unknown)'
|
|
230
356
|
};
|
|
231
357
|
}
|
|
232
358
|
catch (err) {
|
|
233
359
|
const e = err;
|
|
234
360
|
const raw = e.stderr ?? e.message ?? String(err);
|
|
235
|
-
const firstLine = raw
|
|
361
|
+
const firstLine = raw
|
|
362
|
+
.split('\n')
|
|
363
|
+
.map((l) => l.trim())
|
|
364
|
+
.find((l) => l.length > 0) ?? raw;
|
|
236
365
|
return { error: firstLine };
|
|
237
366
|
}
|
|
238
367
|
}
|
|
368
|
+
/** Verifies Azure credentials by acquiring a token and listing resource groups (cheap, works with just Reader access). */
|
|
369
|
+
async function tryGetAzureAccountInfo(credentials) {
|
|
370
|
+
try {
|
|
371
|
+
const credential = new identity_1.ClientSecretCredential(credentials.AZURE_TENANT_ID, credentials.AZURE_CLIENT_ID, credentials.AZURE_CLIENT_SECRET);
|
|
372
|
+
const client = new arm_resources_1.ResourceManagementClient(credential, credentials.AZURE_SUBSCRIPTION_ID);
|
|
373
|
+
const iterator = client.resourceGroups.list();
|
|
374
|
+
await iterator.next();
|
|
375
|
+
return { subscriptionId: credentials.AZURE_SUBSCRIPTION_ID };
|
|
376
|
+
}
|
|
377
|
+
catch (err) {
|
|
378
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
379
|
+
}
|
|
380
|
+
}
|
|
239
381
|
/** Displays credential verification results and throws if any check failed. */
|
|
240
382
|
function assertCredentialResults(stsResult, ghUserResult, ghOrgResult) {
|
|
241
383
|
const failures = [];
|
|
@@ -250,6 +392,20 @@ function assertCredentialResults(stsResult, ghUserResult, ghOrgResult) {
|
|
|
250
392
|
failures.map((f) => ` • ${f}`).join('\n'));
|
|
251
393
|
}
|
|
252
394
|
}
|
|
395
|
+
/** Displays Azure credential verification results and throws if any check failed. */
|
|
396
|
+
function assertAzureCredentialResults(azResult, ghUserResult, ghOrgResult) {
|
|
397
|
+
const failures = [];
|
|
398
|
+
if ('error' in azResult)
|
|
399
|
+
failures.push(`Azure credentials: ${azResult.error}`);
|
|
400
|
+
if ('error' in ghUserResult)
|
|
401
|
+
failures.push(`GitHub token: ${ghUserResult.error}`);
|
|
402
|
+
if ('error' in ghOrgResult)
|
|
403
|
+
failures.push(`GitHub org: ${ghOrgResult.error}`);
|
|
404
|
+
if (failures.length > 0) {
|
|
405
|
+
throw new Error('Credential verification failed:\n' +
|
|
406
|
+
failures.map((f) => ` • ${f}`).join('\n'));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
253
409
|
/** Derives a sensible domain default: cliswovotest-api.swovo.com (production), cliswovotest-api-dev.swovo.com (others) */
|
|
254
410
|
function defaultDomain(projectName, suffix, env, baseDomain) {
|
|
255
411
|
const label = suffix === 'web' ? 'app' : suffix;
|
|
@@ -270,12 +426,12 @@ async function collectAnswers(rl) {
|
|
|
270
426
|
const credFilePath = path.resolve('.fuel-credentials');
|
|
271
427
|
if (await fs.pathExists(credFilePath)) {
|
|
272
428
|
try {
|
|
273
|
-
const existing = await fs.readJson(credFilePath);
|
|
429
|
+
const existing = (await fs.readJson(credFilePath));
|
|
274
430
|
console.log('\n Found existing .fuel-credentials');
|
|
275
431
|
const [stsResult, ghUserResult, ghOrgResult] = await Promise.all([
|
|
276
432
|
tryGetAwsAccountId(existing),
|
|
277
433
|
tryGetGithubUser(existing),
|
|
278
|
-
tryGetGithubOrg(existing)
|
|
434
|
+
tryGetGithubOrg(existing)
|
|
279
435
|
]);
|
|
280
436
|
if ('accountId' in stsResult) {
|
|
281
437
|
console.log(` AWS Account: ${chalk_1.default.green(stsResult.accountId)}`);
|
|
@@ -308,7 +464,8 @@ async function collectAnswers(rl) {
|
|
|
308
464
|
credentials = existing;
|
|
309
465
|
}
|
|
310
466
|
catch (e) {
|
|
311
|
-
if (e instanceof Error &&
|
|
467
|
+
if (e instanceof Error &&
|
|
468
|
+
e.message.startsWith('Credential verification failed'))
|
|
312
469
|
throw e;
|
|
313
470
|
console.log(' Could not read .fuel-credentials — prompting for credentials.');
|
|
314
471
|
}
|
|
@@ -317,14 +474,15 @@ async function collectAnswers(rl) {
|
|
|
317
474
|
credentials.AWS_ACCESS_KEY_ID = await ask(rl, 'AWS_ACCESS_KEY_ID');
|
|
318
475
|
credentials.AWS_SECRET_ACCESS_KEY = await ask(rl, 'AWS_SECRET_ACCESS_KEY');
|
|
319
476
|
credentials.AWS_REGION = await ask(rl, 'AWS_REGION', 'us-east-1');
|
|
320
|
-
|
|
477
|
+
const gh = await askGitHubToken(rl);
|
|
478
|
+
credentials.GITHUB_TOKEN = gh.token;
|
|
321
479
|
credentials.GITHUB_USERNAME = await ask(rl, 'GITHUB_USERNAME');
|
|
322
|
-
credentials.GITHUB_ORGANIZATION = await
|
|
480
|
+
credentials.GITHUB_ORGANIZATION = await askGitHubOrganizationFor(rl, gh.token);
|
|
323
481
|
console.log('\n Verifying credentials...');
|
|
324
482
|
const [stsResult, ghUserResult, ghOrgResult] = await Promise.all([
|
|
325
483
|
tryGetAwsAccountId(credentials),
|
|
326
484
|
tryGetGithubUser(credentials),
|
|
327
|
-
tryGetGithubOrg(credentials)
|
|
485
|
+
tryGetGithubOrg(credentials)
|
|
328
486
|
]);
|
|
329
487
|
if ('accountId' in stsResult) {
|
|
330
488
|
console.log(` AWS Account: ${chalk_1.default.green(stsResult.accountId)}`);
|
|
@@ -401,9 +559,18 @@ async function collectAnswers(rl) {
|
|
|
401
559
|
const cpuUnits = await askInt(rl, 'CPU units', 1024);
|
|
402
560
|
const memoryUnits = await askInt(rl, 'Memory (MB)', 2048);
|
|
403
561
|
appTypes.push({
|
|
404
|
-
suffix: 'api',
|
|
405
|
-
|
|
406
|
-
|
|
562
|
+
suffix: 'api',
|
|
563
|
+
dockerfile,
|
|
564
|
+
port,
|
|
565
|
+
healthCheckPath,
|
|
566
|
+
postgres,
|
|
567
|
+
redis,
|
|
568
|
+
cpuUnits,
|
|
569
|
+
memoryUnits,
|
|
570
|
+
dbInstanceType,
|
|
571
|
+
dbAllocatedStorage,
|
|
572
|
+
redisInstanceType,
|
|
573
|
+
variables: {}
|
|
407
574
|
});
|
|
408
575
|
}
|
|
409
576
|
if (includeWeb) {
|
|
@@ -414,10 +581,18 @@ async function collectAnswers(rl) {
|
|
|
414
581
|
const cpuUnits = await askInt(rl, 'CPU units', 1024);
|
|
415
582
|
const memoryUnits = await askInt(rl, 'Memory (MB)', 2048);
|
|
416
583
|
appTypes.push({
|
|
417
|
-
suffix: 'web',
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
584
|
+
suffix: 'web',
|
|
585
|
+
dockerfile,
|
|
586
|
+
port,
|
|
587
|
+
healthCheckPath,
|
|
588
|
+
postgres: false,
|
|
589
|
+
redis: false,
|
|
590
|
+
cpuUnits,
|
|
591
|
+
memoryUnits,
|
|
592
|
+
dbInstanceType: 'db.t3.small',
|
|
593
|
+
dbAllocatedStorage: 30,
|
|
594
|
+
redisInstanceType: 'cache.t3.small',
|
|
595
|
+
variables: {}
|
|
421
596
|
});
|
|
422
597
|
}
|
|
423
598
|
console.log('');
|
|
@@ -427,7 +602,7 @@ async function collectAnswers(rl) {
|
|
|
427
602
|
const envPresets = {
|
|
428
603
|
'production only': ['production'],
|
|
429
604
|
'dev + production': ['dev', 'production'],
|
|
430
|
-
'dev + staging + production': ['dev', 'staging', 'production']
|
|
605
|
+
'dev + staging + production': ['dev', 'staging', 'production']
|
|
431
606
|
};
|
|
432
607
|
const envChoice = await askSelect(rl, 'Environments', Object.keys(envPresets), 3);
|
|
433
608
|
const environments = envPresets[envChoice];
|
|
@@ -441,7 +616,9 @@ async function collectAnswers(rl) {
|
|
|
441
616
|
for (const appType of appTypes) {
|
|
442
617
|
domains[appType.suffix] = {};
|
|
443
618
|
for (const env of environments) {
|
|
444
|
-
const suggestion = baseDomain
|
|
619
|
+
const suggestion = baseDomain
|
|
620
|
+
? defaultDomain(projectName, appType.suffix, env, baseDomain)
|
|
621
|
+
: '';
|
|
445
622
|
const appFullName = `${projectName}-${appType.suffix}-${env}`;
|
|
446
623
|
domains[appType.suffix][env] = await ask(rl, appFullName, suggestion || undefined);
|
|
447
624
|
}
|
|
@@ -460,7 +637,212 @@ async function collectAnswers(rl) {
|
|
|
460
637
|
}
|
|
461
638
|
}
|
|
462
639
|
console.log('');
|
|
463
|
-
return {
|
|
640
|
+
return {
|
|
641
|
+
credentials,
|
|
642
|
+
projectName,
|
|
643
|
+
ssl,
|
|
644
|
+
slackWebhookUrl,
|
|
645
|
+
appTypes,
|
|
646
|
+
environments,
|
|
647
|
+
baseDomain,
|
|
648
|
+
domains
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
// --- collectAzureAnswers ---
|
|
652
|
+
async function collectAzureAnswers(rl) {
|
|
653
|
+
// --- Credentials ---
|
|
654
|
+
console.log(' Credentials');
|
|
655
|
+
console.log(' ────────────────────────────────────────────────────');
|
|
656
|
+
let credentials = {};
|
|
657
|
+
let useExistingCreds = false;
|
|
658
|
+
const credFilePath = path.resolve('.fuel-credentials');
|
|
659
|
+
if (await fs.pathExists(credFilePath)) {
|
|
660
|
+
try {
|
|
661
|
+
const existing = (await fs.readJson(credFilePath));
|
|
662
|
+
console.log('\n Found existing .fuel-credentials');
|
|
663
|
+
const [azResult, ghUserResult, ghOrgResult] = await Promise.all([
|
|
664
|
+
tryGetAzureAccountInfo(existing),
|
|
665
|
+
tryGetGithubUser(existing),
|
|
666
|
+
tryGetGithubOrg(existing)
|
|
667
|
+
]);
|
|
668
|
+
if ('subscriptionId' in azResult) {
|
|
669
|
+
console.log(` Azure Subscription: ${chalk_1.default.green(azResult.subscriptionId)}`);
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
console.log(' ' + chalk_1.default.yellow('⚠') + ' Azure credentials invalid:');
|
|
673
|
+
console.log(` ${azResult.error}`);
|
|
674
|
+
}
|
|
675
|
+
if ('login' in ghUserResult) {
|
|
676
|
+
console.log(` GitHub User: ${chalk_1.default.green(ghUserResult.login)}`);
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
console.log(' ' + chalk_1.default.yellow('⚠') + ' GitHub token invalid:');
|
|
680
|
+
console.log(` ${ghUserResult.error}`);
|
|
681
|
+
}
|
|
682
|
+
if ('id' in ghOrgResult) {
|
|
683
|
+
console.log(` GitHub Org: ${chalk_1.default.green(ghOrgResult.login)} (ID: ${ghOrgResult.id})`);
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
console.log(' ' + chalk_1.default.yellow('⚠') + ' Could not verify org:');
|
|
687
|
+
console.log(` ${ghOrgResult.error}`);
|
|
688
|
+
}
|
|
689
|
+
assertAzureCredentialResults(azResult, ghUserResult, ghOrgResult);
|
|
690
|
+
console.log('');
|
|
691
|
+
useExistingCreds = await askYesNo(rl, 'Use these credentials?', true);
|
|
692
|
+
if (useExistingCreds)
|
|
693
|
+
credentials = existing;
|
|
694
|
+
}
|
|
695
|
+
catch (e) {
|
|
696
|
+
if (e instanceof Error &&
|
|
697
|
+
e.message.startsWith('Credential verification failed'))
|
|
698
|
+
throw e;
|
|
699
|
+
console.log(' Could not read .fuel-credentials — prompting for credentials.');
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (!useExistingCreds) {
|
|
703
|
+
credentials.AZURE_SUBSCRIPTION_ID = await ask(rl, 'AZURE_SUBSCRIPTION_ID');
|
|
704
|
+
credentials.AZURE_TENANT_ID = await ask(rl, 'AZURE_TENANT_ID');
|
|
705
|
+
credentials.AZURE_CLIENT_ID = await ask(rl, 'AZURE_CLIENT_ID');
|
|
706
|
+
credentials.AZURE_CLIENT_SECRET = await ask(rl, 'AZURE_CLIENT_SECRET');
|
|
707
|
+
const gh = await askGitHubToken(rl);
|
|
708
|
+
credentials.GITHUB_TOKEN = gh.token;
|
|
709
|
+
credentials.GITHUB_USERNAME = await ask(rl, 'GITHUB_USERNAME');
|
|
710
|
+
credentials.GITHUB_ORGANIZATION = await askGitHubOrganizationFor(rl, gh.token);
|
|
711
|
+
console.log('\n Verifying credentials...');
|
|
712
|
+
const [azResult, ghUserResult, ghOrgResult] = await Promise.all([
|
|
713
|
+
tryGetAzureAccountInfo(credentials),
|
|
714
|
+
tryGetGithubUser(credentials),
|
|
715
|
+
tryGetGithubOrg(credentials)
|
|
716
|
+
]);
|
|
717
|
+
if ('subscriptionId' in azResult) {
|
|
718
|
+
console.log(` Azure Subscription: ${chalk_1.default.green(azResult.subscriptionId)}`);
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
console.log(' ' + chalk_1.default.yellow('⚠') + ' Azure credentials invalid:');
|
|
722
|
+
console.log(` ${azResult.error}`);
|
|
723
|
+
}
|
|
724
|
+
if ('login' in ghUserResult) {
|
|
725
|
+
console.log(` GitHub User: ${chalk_1.default.green(ghUserResult.login)}`);
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
console.log(' ' + chalk_1.default.yellow('⚠') + ' GitHub token invalid:');
|
|
729
|
+
console.log(` ${ghUserResult.error}`);
|
|
730
|
+
}
|
|
731
|
+
if ('id' in ghOrgResult) {
|
|
732
|
+
console.log(` GitHub Org: ${chalk_1.default.green(ghOrgResult.login)} (ID: ${ghOrgResult.id})`);
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
console.log(' ' + chalk_1.default.yellow('⚠') + ' Could not verify org:');
|
|
736
|
+
console.log(` ${ghOrgResult.error}`);
|
|
737
|
+
}
|
|
738
|
+
assertAzureCredentialResults(azResult, ghUserResult, ghOrgResult);
|
|
739
|
+
}
|
|
740
|
+
console.log('');
|
|
741
|
+
// --- Project ---
|
|
742
|
+
console.log(' Project');
|
|
743
|
+
console.log(' ────────────────────────────────────────────────────');
|
|
744
|
+
const projectName = await ask(rl, 'Project name', 'my-app');
|
|
745
|
+
const location = await ask(rl, 'Azure region', 'eastus2');
|
|
746
|
+
const slackWebhookUrl = await ask(rl, 'Slack webhook URL for deploy/alarm notifications (leave empty to skip)', '');
|
|
747
|
+
console.log('');
|
|
748
|
+
// --- App configuration ---
|
|
749
|
+
console.log(' App configuration');
|
|
750
|
+
console.log(' ────────────────────────────────────────────────────');
|
|
751
|
+
const appChoice = await askSelect(rl, 'Include which apps?', ['API only (NestJS)', 'Web only (React/Next.js)', 'API + Web'], 3);
|
|
752
|
+
const includeApi = appChoice !== 'Web only (React/Next.js)';
|
|
753
|
+
const includeWeb = appChoice !== 'API only (NestJS)';
|
|
754
|
+
const appTypes = [];
|
|
755
|
+
if (includeApi) {
|
|
756
|
+
console.log('\n API (NestJS):');
|
|
757
|
+
const dockerfile = await ask(rl, 'Dockerfile', 'apps/api/Dockerfile');
|
|
758
|
+
const port = await askInt(rl, 'Port', 3000);
|
|
759
|
+
const healthCheckPath = await ask(rl, 'Health check path', '/health');
|
|
760
|
+
const databaseEnabled = await askYesNo(rl, 'Include a database?', true);
|
|
761
|
+
let databaseEngine = 'mssql';
|
|
762
|
+
if (databaseEnabled) {
|
|
763
|
+
const engineChoice = await askSelect(rl, ' Database engine', ['Azure SQL Database (mssql)', 'PostgreSQL Flexible Server (postgres)'], 1);
|
|
764
|
+
databaseEngine = engineChoice.includes('mssql') ? 'mssql' : 'postgres';
|
|
765
|
+
}
|
|
766
|
+
const redisEnabled = await askYesNo(rl, 'Include Redis?', false);
|
|
767
|
+
appTypes.push({
|
|
768
|
+
suffix: 'api',
|
|
769
|
+
dockerfile,
|
|
770
|
+
port,
|
|
771
|
+
healthCheckPath,
|
|
772
|
+
databaseEnabled,
|
|
773
|
+
databaseEngine,
|
|
774
|
+
redisEnabled,
|
|
775
|
+
variables: {}
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
if (includeWeb) {
|
|
779
|
+
console.log('\n Web (React/Next.js):');
|
|
780
|
+
const dockerfile = await ask(rl, 'Dockerfile', 'apps/web/Dockerfile');
|
|
781
|
+
const port = await askInt(rl, 'Port', 3000);
|
|
782
|
+
const healthCheckPath = await ask(rl, 'Health check path', '/health');
|
|
783
|
+
appTypes.push({
|
|
784
|
+
suffix: 'web',
|
|
785
|
+
dockerfile,
|
|
786
|
+
port,
|
|
787
|
+
healthCheckPath,
|
|
788
|
+
databaseEnabled: false,
|
|
789
|
+
databaseEngine: 'mssql',
|
|
790
|
+
redisEnabled: false,
|
|
791
|
+
variables: {}
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
console.log('');
|
|
795
|
+
// --- Environments ---
|
|
796
|
+
console.log(' Environments');
|
|
797
|
+
console.log(' ────────────────────────────────────────────────────');
|
|
798
|
+
const envPresets = {
|
|
799
|
+
'production only': ['production'],
|
|
800
|
+
'dev + production': ['dev', 'production'],
|
|
801
|
+
'dev + staging + production': ['dev', 'staging', 'production']
|
|
802
|
+
};
|
|
803
|
+
const envChoice = await askSelect(rl, 'Environments', Object.keys(envPresets), 3);
|
|
804
|
+
const environments = envPresets[envChoice];
|
|
805
|
+
console.log('');
|
|
806
|
+
// --- Base domain + per-app domains ---
|
|
807
|
+
console.log(' Domains');
|
|
808
|
+
console.log(' ────────────────────────────────────────────────────');
|
|
809
|
+
const baseDomain = await ask(rl, 'Base domain');
|
|
810
|
+
console.log('');
|
|
811
|
+
const domains = {};
|
|
812
|
+
for (const appType of appTypes) {
|
|
813
|
+
domains[appType.suffix] = {};
|
|
814
|
+
for (const env of environments) {
|
|
815
|
+
const suggestion = baseDomain
|
|
816
|
+
? defaultDomain(projectName, appType.suffix, env, baseDomain)
|
|
817
|
+
: '';
|
|
818
|
+
const appFullName = `${projectName}-${appType.suffix}-${env}`;
|
|
819
|
+
domains[appType.suffix][env] = await ask(rl, appFullName, suggestion || undefined);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
console.log('');
|
|
823
|
+
// --- Environment variables (per app × per environment) ---
|
|
824
|
+
console.log(' Environment variables');
|
|
825
|
+
console.log(' ────────────────────────────────────────────────────');
|
|
826
|
+
for (const appType of appTypes) {
|
|
827
|
+
const isApi = appType.suffix === 'api';
|
|
828
|
+
for (const env of environments) {
|
|
829
|
+
const label = isApi
|
|
830
|
+
? `${projectName}-api-${env} runtime variables (e.g. TWILIO_TOKEN=xxx)`
|
|
831
|
+
: `${projectName}-web-${env} build variables (NEXT_PUBLIC_API_HOST and NEXT_PUBLIC_APP_HOST are already included)`;
|
|
832
|
+
appType.variables[env] = await askKeyValues(rl, label);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
console.log('');
|
|
836
|
+
return {
|
|
837
|
+
credentials,
|
|
838
|
+
projectName,
|
|
839
|
+
slackWebhookUrl,
|
|
840
|
+
location,
|
|
841
|
+
appTypes,
|
|
842
|
+
environments,
|
|
843
|
+
baseDomain,
|
|
844
|
+
domains
|
|
845
|
+
};
|
|
464
846
|
}
|
|
465
847
|
// --- applyInfraInitAnswers ---
|
|
466
848
|
async function applyInfraInitAnswers(answers, opts) {
|
|
@@ -504,7 +886,7 @@ async function applyInfraInitAnswers(answers, opts) {
|
|
|
504
886
|
memory_units: appType.memoryUnits,
|
|
505
887
|
variables: {},
|
|
506
888
|
build_env: {},
|
|
507
|
-
workers: []
|
|
889
|
+
workers: []
|
|
508
890
|
};
|
|
509
891
|
if (isApi) {
|
|
510
892
|
app.postgres = appType.postgres;
|
|
@@ -537,10 +919,12 @@ async function applyInfraInitAnswers(answers, opts) {
|
|
|
537
919
|
deployment: 'ecs',
|
|
538
920
|
ssl: answers.ssl,
|
|
539
921
|
slack_webhook_url: answers.slackWebhookUrl || '',
|
|
540
|
-
apps
|
|
922
|
+
apps
|
|
541
923
|
};
|
|
542
924
|
// 4. Guard against overwriting existing config
|
|
543
|
-
const outputPath = opts.output
|
|
925
|
+
const outputPath = opts.output
|
|
926
|
+
? path.resolve(opts.output)
|
|
927
|
+
: path.resolve('fuel.yml');
|
|
544
928
|
if (await fs.pathExists(outputPath)) {
|
|
545
929
|
throw new Error(`${outputPath} already exists. Remove it first or use --output <path>.`);
|
|
546
930
|
}
|
|
@@ -555,96 +939,230 @@ async function applyInfraInitAnswers(answers, opts) {
|
|
|
555
939
|
console.log(' Review and edit fuel.yml to add workers/variables, then:');
|
|
556
940
|
console.log(` fuel create:app ${answers.projectName} --config fuel.yml\n`);
|
|
557
941
|
}
|
|
558
|
-
// ---
|
|
559
|
-
async function
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
tryGetAwsAccountId(credentials),
|
|
608
|
-
tryGetGithubUser(credentials),
|
|
609
|
-
tryGetGithubOrg(credentials),
|
|
610
|
-
]);
|
|
611
|
-
if ('accountId' in stsResult) {
|
|
612
|
-
console.log(` AWS Account: ${chalk_1.default.green(stsResult.accountId)}`);
|
|
613
|
-
}
|
|
614
|
-
if ('login' in ghUserResult) {
|
|
615
|
-
console.log(` GitHub User: ${chalk_1.default.green(ghUserResult.login)}`);
|
|
942
|
+
// --- applyAzureInfraInitAnswers ---
|
|
943
|
+
async function applyAzureInfraInitAnswers(answers, opts) {
|
|
944
|
+
// 1. Write .fuel-credentials
|
|
945
|
+
const credPath = path.resolve('.fuel-credentials');
|
|
946
|
+
await fs.writeFile(credPath, JSON.stringify(answers.credentials, null, 2));
|
|
947
|
+
console.log(chalk_1.default.green('✓') + ' Credentials written to .fuel-credentials');
|
|
948
|
+
// 2. Ensure .gitignore contains .fuel-credentials
|
|
949
|
+
const gitignorePath = path.resolve('.gitignore');
|
|
950
|
+
const CRED_LINE = '.fuel-credentials';
|
|
951
|
+
if (await fs.pathExists(gitignorePath)) {
|
|
952
|
+
const content = await fs.readFile(gitignorePath, 'utf-8');
|
|
953
|
+
if (!content.split('\n').some((l) => l.trim() === CRED_LINE)) {
|
|
954
|
+
await fs.appendFile(gitignorePath, `\n${CRED_LINE}\n`);
|
|
955
|
+
console.log(chalk_1.default.green('✓') + ' .fuel-credentials added to .gitignore');
|
|
956
|
+
}
|
|
957
|
+
else {
|
|
958
|
+
console.log(' .fuel-credentials already in .gitignore');
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
else {
|
|
962
|
+
await fs.writeFile(gitignorePath, `${CRED_LINE}\n`);
|
|
963
|
+
console.log(chalk_1.default.green('✓') + ' .fuel-credentials added to .gitignore');
|
|
964
|
+
}
|
|
965
|
+
// 3. Build config (cartesian product: appTypes × environments)
|
|
966
|
+
const apps = [];
|
|
967
|
+
for (const appType of answers.appTypes) {
|
|
968
|
+
const isApi = appType.suffix === 'api';
|
|
969
|
+
const technology = isApi ? 'nest' : 'react';
|
|
970
|
+
for (const env of answers.environments) {
|
|
971
|
+
const branch = env === 'production' ? 'main' : env;
|
|
972
|
+
const app = {
|
|
973
|
+
name: `${answers.projectName}-${appType.suffix}-${env}`,
|
|
974
|
+
technology,
|
|
975
|
+
dockerfile: appType.dockerfile,
|
|
976
|
+
domain: answers.domains[appType.suffix][env],
|
|
977
|
+
port: appType.port,
|
|
978
|
+
health_check_path: appType.healthCheckPath,
|
|
979
|
+
branch,
|
|
980
|
+
variables: {},
|
|
981
|
+
build_env: {},
|
|
982
|
+
workers: []
|
|
983
|
+
};
|
|
984
|
+
if (isApi) {
|
|
985
|
+
app.variables = appType.variables[env] ?? {};
|
|
986
|
+
app.database = {
|
|
987
|
+
enabled: appType.databaseEnabled,
|
|
988
|
+
engine: appType.databaseEngine
|
|
989
|
+
};
|
|
990
|
+
app.redis = { enabled: appType.redisEnabled };
|
|
616
991
|
}
|
|
617
|
-
|
|
618
|
-
|
|
992
|
+
else {
|
|
993
|
+
// Auto-populate build_env from domains; user-provided vars override
|
|
994
|
+
const autoBuildEnv = {};
|
|
995
|
+
const webDomain = answers.domains['web']?.[env];
|
|
996
|
+
if (webDomain)
|
|
997
|
+
autoBuildEnv['NEXT_PUBLIC_APP_HOST'] = `https://${webDomain}`;
|
|
998
|
+
const apiDomain = answers.domains['api']?.[env];
|
|
999
|
+
if (apiDomain)
|
|
1000
|
+
autoBuildEnv['NEXT_PUBLIC_API_HOST'] = `https://${apiDomain}`;
|
|
1001
|
+
app.build_env = { ...autoBuildEnv, ...(appType.variables[env] ?? {}) };
|
|
1002
|
+
app.database = {
|
|
1003
|
+
enabled: false,
|
|
1004
|
+
engine: azure_defaults_1.AZURE_DEFAULTS.database.engine
|
|
1005
|
+
};
|
|
1006
|
+
app.redis = { enabled: false };
|
|
619
1007
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
1008
|
+
app.container_app = {
|
|
1009
|
+
cpu: azure_defaults_1.AZURE_DEFAULTS.containerApp.cpu,
|
|
1010
|
+
memory: azure_defaults_1.AZURE_DEFAULTS.containerApp.memory,
|
|
1011
|
+
concurrent_requests: azure_defaults_1.AZURE_DEFAULTS.containerApp.concurrentRequests,
|
|
1012
|
+
min_replicas: azure_defaults_1.AZURE_DEFAULTS.containerApp.minReplicas,
|
|
1013
|
+
max_replicas: azure_defaults_1.AZURE_DEFAULTS.containerApp.maxReplicas
|
|
1014
|
+
};
|
|
1015
|
+
apps.push(app);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
const config = {
|
|
1019
|
+
name: answers.projectName,
|
|
1020
|
+
deployment: 'azure',
|
|
1021
|
+
location: answers.location,
|
|
1022
|
+
slack_webhook_url: answers.slackWebhookUrl || '',
|
|
1023
|
+
apps
|
|
1024
|
+
};
|
|
1025
|
+
// 4. Guard against overwriting existing config
|
|
1026
|
+
const outputPath = opts.output
|
|
1027
|
+
? path.resolve(opts.output)
|
|
1028
|
+
: path.resolve('fuel.yml');
|
|
1029
|
+
if (await fs.pathExists(outputPath)) {
|
|
1030
|
+
throw new Error(`${outputPath} already exists. Remove it first or use --output <path>.`);
|
|
1031
|
+
}
|
|
1032
|
+
// 5. Write fuel.yml
|
|
1033
|
+
const schemaComment = '# yaml-language-server: $schema=node_modules/@swovohq/fuel/schema/fuel.schema.json\n';
|
|
1034
|
+
await fs.writeFile(outputPath, schemaComment + YAML.stringify(config));
|
|
1035
|
+
const totalApps = answers.appTypes.length * answers.environments.length;
|
|
1036
|
+
console.log(chalk_1.default.green('✓') +
|
|
1037
|
+
` fuel.yml written — ${totalApps} apps (${answers.appTypes.length} types × ${answers.environments.length} environments)`);
|
|
1038
|
+
// 6. Next steps
|
|
1039
|
+
console.log('\n Next steps:');
|
|
1040
|
+
console.log(' Review and edit fuel.yml to add workers/variables, then:');
|
|
1041
|
+
console.log(` fuel create:app ${answers.projectName} --config fuel.yml\n`);
|
|
1042
|
+
}
|
|
1043
|
+
// --- infraInit ---
|
|
1044
|
+
/** Interactive credentials-only flow (used by `auth:init` and `infra:init --credentials-only`) for the given cloud. */
|
|
1045
|
+
async function collectCredentialsOnly(rl, cloud) {
|
|
1046
|
+
console.log('\n Credentials');
|
|
1047
|
+
console.log(' ────────────────────────────────────────────────────');
|
|
1048
|
+
const credentials = {};
|
|
1049
|
+
const credFilePath = path.resolve('.fuel-credentials');
|
|
1050
|
+
if (await fs.pathExists(credFilePath)) {
|
|
1051
|
+
try {
|
|
1052
|
+
const existing = (await fs.readJson(credFilePath));
|
|
1053
|
+
console.log('\n Found existing .fuel-credentials');
|
|
1054
|
+
if (cloud === 'aws') {
|
|
1055
|
+
const [stsResult, ghUserResult, ghOrgResult] = await Promise.all([
|
|
1056
|
+
tryGetAwsAccountId(existing),
|
|
1057
|
+
tryGetGithubUser(existing),
|
|
1058
|
+
tryGetGithubOrg(existing)
|
|
1059
|
+
]);
|
|
1060
|
+
if ('accountId' in stsResult)
|
|
1061
|
+
console.log(` AWS Account: ${chalk_1.default.green(stsResult.accountId)}`);
|
|
1062
|
+
if ('login' in ghUserResult)
|
|
1063
|
+
console.log(` GitHub User: ${chalk_1.default.green(ghUserResult.login)}`);
|
|
1064
|
+
if ('login' in ghOrgResult)
|
|
1065
|
+
console.log(` GitHub Org: ${chalk_1.default.green(ghOrgResult.login)}`);
|
|
1066
|
+
assertCredentialResults(stsResult, ghUserResult, ghOrgResult);
|
|
630
1067
|
}
|
|
631
1068
|
else {
|
|
632
|
-
await
|
|
1069
|
+
const [azResult, ghUserResult, ghOrgResult] = await Promise.all([
|
|
1070
|
+
tryGetAzureAccountInfo(existing),
|
|
1071
|
+
tryGetGithubUser(existing),
|
|
1072
|
+
tryGetGithubOrg(existing)
|
|
1073
|
+
]);
|
|
1074
|
+
if ('subscriptionId' in azResult)
|
|
1075
|
+
console.log(` Azure Subscription: ${chalk_1.default.green(azResult.subscriptionId)}`);
|
|
1076
|
+
if ('login' in ghUserResult)
|
|
1077
|
+
console.log(` GitHub User: ${chalk_1.default.green(ghUserResult.login)}`);
|
|
1078
|
+
if ('login' in ghOrgResult)
|
|
1079
|
+
console.log(` GitHub Org: ${chalk_1.default.green(ghOrgResult.login)}`);
|
|
1080
|
+
assertAzureCredentialResults(azResult, ghUserResult, ghOrgResult);
|
|
633
1081
|
}
|
|
634
1082
|
console.log('');
|
|
1083
|
+
const useExisting = await askYesNo(rl, 'Use these credentials?', true);
|
|
1084
|
+
if (useExisting) {
|
|
1085
|
+
console.log(chalk_1.default.green('\n ✓ Existing credentials verified.\n'));
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
635
1088
|
}
|
|
636
|
-
|
|
637
|
-
|
|
1089
|
+
catch (e) {
|
|
1090
|
+
if (e instanceof Error &&
|
|
1091
|
+
e.message.startsWith('Credential verification failed'))
|
|
1092
|
+
throw e;
|
|
1093
|
+
console.log(' Could not read .fuel-credentials — prompting for credentials.');
|
|
638
1094
|
}
|
|
639
|
-
return;
|
|
640
1095
|
}
|
|
1096
|
+
if (cloud === 'aws') {
|
|
1097
|
+
credentials.AWS_ACCESS_KEY_ID = await ask(rl, 'AWS_ACCESS_KEY_ID');
|
|
1098
|
+
credentials.AWS_SECRET_ACCESS_KEY = await ask(rl, 'AWS_SECRET_ACCESS_KEY');
|
|
1099
|
+
credentials.AWS_REGION = await ask(rl, 'AWS_REGION', 'us-east-1');
|
|
1100
|
+
}
|
|
1101
|
+
else {
|
|
1102
|
+
credentials.AZURE_SUBSCRIPTION_ID = await ask(rl, 'AZURE_SUBSCRIPTION_ID');
|
|
1103
|
+
credentials.AZURE_TENANT_ID = await ask(rl, 'AZURE_TENANT_ID');
|
|
1104
|
+
credentials.AZURE_CLIENT_ID = await ask(rl, 'AZURE_CLIENT_ID');
|
|
1105
|
+
credentials.AZURE_CLIENT_SECRET = await ask(rl, 'AZURE_CLIENT_SECRET');
|
|
1106
|
+
}
|
|
1107
|
+
const gh = await askGitHubToken(rl);
|
|
1108
|
+
credentials.GITHUB_TOKEN = gh.token;
|
|
1109
|
+
credentials.GITHUB_USERNAME = await ask(rl, 'GITHUB_USERNAME');
|
|
1110
|
+
credentials.GITHUB_ORGANIZATION = await askGitHubOrganizationFor(rl, gh.token);
|
|
1111
|
+
console.log('\n Verifying credentials...');
|
|
1112
|
+
if (cloud === 'aws') {
|
|
1113
|
+
const [stsResult, ghUserResult, ghOrgResult] = await Promise.all([
|
|
1114
|
+
tryGetAwsAccountId(credentials),
|
|
1115
|
+
tryGetGithubUser(credentials),
|
|
1116
|
+
tryGetGithubOrg(credentials)
|
|
1117
|
+
]);
|
|
1118
|
+
if ('accountId' in stsResult)
|
|
1119
|
+
console.log(` AWS Account: ${chalk_1.default.green(stsResult.accountId)}`);
|
|
1120
|
+
if ('login' in ghUserResult)
|
|
1121
|
+
console.log(` GitHub User: ${chalk_1.default.green(ghUserResult.login)}`);
|
|
1122
|
+
if ('login' in ghOrgResult)
|
|
1123
|
+
console.log(` GitHub Org: ${chalk_1.default.green(ghOrgResult.login)}`);
|
|
1124
|
+
assertCredentialResults(stsResult, ghUserResult, ghOrgResult);
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
const [azResult, ghUserResult, ghOrgResult] = await Promise.all([
|
|
1128
|
+
tryGetAzureAccountInfo(credentials),
|
|
1129
|
+
tryGetGithubUser(credentials),
|
|
1130
|
+
tryGetGithubOrg(credentials)
|
|
1131
|
+
]);
|
|
1132
|
+
if ('subscriptionId' in azResult)
|
|
1133
|
+
console.log(` Azure Subscription: ${chalk_1.default.green(azResult.subscriptionId)}`);
|
|
1134
|
+
if ('login' in ghUserResult)
|
|
1135
|
+
console.log(` GitHub User: ${chalk_1.default.green(ghUserResult.login)}`);
|
|
1136
|
+
if ('login' in ghOrgResult)
|
|
1137
|
+
console.log(` GitHub Org: ${chalk_1.default.green(ghOrgResult.login)}`);
|
|
1138
|
+
assertAzureCredentialResults(azResult, ghUserResult, ghOrgResult);
|
|
1139
|
+
}
|
|
1140
|
+
await fs.writeFile(credFilePath, JSON.stringify(credentials, null, 2));
|
|
1141
|
+
console.log(chalk_1.default.green('\n ✓') + ' Credentials written to .fuel-credentials');
|
|
1142
|
+
const gitignorePath = path.resolve('.gitignore');
|
|
1143
|
+
const CRED_LINE = '.fuel-credentials';
|
|
1144
|
+
if (await fs.pathExists(gitignorePath)) {
|
|
1145
|
+
const content = await fs.readFile(gitignorePath, 'utf-8');
|
|
1146
|
+
if (!content.split('\n').some((l) => l.trim() === CRED_LINE)) {
|
|
1147
|
+
await fs.appendFile(gitignorePath, `\n${CRED_LINE}\n`);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
else {
|
|
1151
|
+
await fs.writeFile(gitignorePath, `${CRED_LINE}\n`);
|
|
1152
|
+
}
|
|
1153
|
+
console.log('');
|
|
1154
|
+
}
|
|
1155
|
+
async function infraInit(opts) {
|
|
641
1156
|
if (opts.template) {
|
|
642
|
-
const outputPath = opts.output
|
|
1157
|
+
const outputPath = opts.output
|
|
1158
|
+
? path.resolve(opts.output)
|
|
1159
|
+
: path.resolve('fuel.yml');
|
|
643
1160
|
if (await fs.pathExists(outputPath)) {
|
|
644
1161
|
throw new Error(`${outputPath} already exists. Remove it first or use --output <path>.`);
|
|
645
1162
|
}
|
|
646
1163
|
const schemaComment = '# yaml-language-server: $schema=node_modules/@swovohq/fuel/schema/fuel.schema.json\n';
|
|
647
|
-
|
|
1164
|
+
const template = opts.azure ? exports.CONFIG_TEMPLATE_AZURE : exports.CONFIG_TEMPLATE;
|
|
1165
|
+
await fs.writeFile(outputPath, schemaComment + YAML.stringify(template));
|
|
648
1166
|
console.log(chalk_1.default.green('✓') + ` Config template written to ${outputPath}`);
|
|
649
1167
|
console.log('\nEdit the file with your project details, then run:\n' +
|
|
650
1168
|
' fuel create:app my-app --config fuel.yml\n');
|
|
@@ -652,8 +1170,21 @@ async function infraInit(opts) {
|
|
|
652
1170
|
}
|
|
653
1171
|
const rl = (0, promises_1.createInterface)({ input: process.stdin, output: process.stdout });
|
|
654
1172
|
try {
|
|
655
|
-
const
|
|
656
|
-
|
|
1173
|
+
const cloud = opts.azure
|
|
1174
|
+
? 'azure'
|
|
1175
|
+
: await askCloudChoice(rl);
|
|
1176
|
+
if (opts.credentialsOnly) {
|
|
1177
|
+
await collectCredentialsOnly(rl, cloud);
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
if (cloud === 'azure') {
|
|
1181
|
+
const answers = await collectAzureAnswers(rl);
|
|
1182
|
+
await applyAzureInfraInitAnswers(answers, opts);
|
|
1183
|
+
}
|
|
1184
|
+
else {
|
|
1185
|
+
const answers = await collectAnswers(rl);
|
|
1186
|
+
await applyInfraInitAnswers(answers, opts);
|
|
1187
|
+
}
|
|
657
1188
|
}
|
|
658
1189
|
finally {
|
|
659
1190
|
rl.close();
|