@nexical/cli 0.11.23 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -235
- package/dist/{chunk-OYFWMYPG.js → chunk-6DE5Q66O.js} +6 -1
- package/dist/{chunk-OYFWMYPG.js.map → chunk-6DE5Q66O.js.map} +1 -1
- package/dist/chunk-G66GMEFE.js +31 -0
- package/dist/chunk-G66GMEFE.js.map +1 -0
- package/dist/{chunk-2FKDEDDE.js → chunk-HOVS7SCD.js} +16 -3
- package/dist/chunk-HOVS7SCD.js.map +1 -0
- package/dist/{chunk-GUUPSHWC.js → chunk-JEMIKBGX.js} +3 -3
- package/dist/chunk-JGAMEJTL.js +4101 -0
- package/dist/chunk-JGAMEJTL.js.map +1 -0
- package/dist/{chunk-OUGA4CB4.js → chunk-JS6WL5NS.js} +2 -2
- package/dist/{chunk-GEESHGE4.js → chunk-L2RUXOL4.js} +2 -2
- package/dist/{chunk-54HY52LH.js → chunk-QTJIGPQ3.js} +2 -2
- package/dist/{chunk-EKCOW7FM.js → chunk-USP2MI63.js} +41 -23
- package/dist/chunk-USP2MI63.js.map +1 -0
- package/dist/{chunk-2JW5BYZW.js → chunk-VKE7R2EZ.js} +2 -2
- package/dist/{chunk-AC4B3HPJ.js → chunk-XONR27KC.js} +2 -2
- package/dist/{chunk-PJIOCW2A.js → chunk-ZWNIZB3Q.js} +2 -2
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/src/commands/deploy.d.ts +3 -3
- package/dist/src/commands/deploy.js +148 -78
- package/dist/src/commands/deploy.js.map +1 -1
- package/dist/src/commands/init.js +5 -5
- package/dist/src/commands/module/add.js +4 -4
- package/dist/src/commands/module/list.js +2 -2
- package/dist/src/commands/module/remove.js +2 -2
- package/dist/src/commands/module/update.js +2 -2
- package/dist/src/commands/prompt.js +2 -2
- package/dist/src/commands/run.js +2 -2
- package/dist/src/commands/setup.js +3 -3
- package/dist/src/deploy/config-manager.js +3 -2
- package/dist/src/deploy/providers/cloudflare.d.ts +13 -8
- package/dist/src/deploy/providers/cloudflare.js +161 -52
- package/dist/src/deploy/providers/cloudflare.js.map +1 -1
- package/dist/src/deploy/providers/dns-cloudflare.d.ts +9 -0
- package/dist/src/deploy/providers/dns-cloudflare.js +123 -0
- package/dist/src/deploy/providers/dns-cloudflare.js.map +1 -0
- package/dist/src/deploy/providers/github.d.ts +6 -2
- package/dist/src/deploy/providers/github.js +37 -45
- package/dist/src/deploy/providers/github.js.map +1 -1
- package/dist/src/deploy/providers/railway.d.ts +17 -8
- package/dist/src/deploy/providers/railway.js +106 -45
- package/dist/src/deploy/providers/railway.js.map +1 -1
- package/dist/src/deploy/registry.d.ts +7 -4
- package/dist/src/deploy/registry.js +2 -2
- package/dist/src/deploy/schema.d.ts +188 -0
- package/dist/src/deploy/schema.js +11 -0
- package/dist/src/deploy/schema.js.map +1 -0
- package/dist/src/deploy/template-manager.d.ts +12 -0
- package/dist/src/deploy/template-manager.js +9 -0
- package/dist/src/deploy/template-manager.js.map +1 -0
- package/dist/src/deploy/types.d.ts +42 -17
- package/dist/src/deploy/types.js +1 -1
- package/dist/src/deploy/types.js.map +1 -1
- package/dist/src/deploy/utils.js +2 -2
- package/dist/src/utils/discovery.js +2 -2
- package/dist/src/utils/filter.js +2 -2
- package/dist/src/utils/git.js +2 -2
- package/dist/src/utils/url-resolver.js +2 -2
- package/dist/templates/github-workflow.yaml +23 -0
- package/package.json +2 -2
- package/src/commands/deploy.ts +169 -88
- package/src/deploy/config-manager.ts +14 -1
- package/src/deploy/providers/cloudflare.ts +203 -80
- package/src/deploy/providers/dns-cloudflare.ts +134 -0
- package/src/deploy/providers/github.ts +44 -47
- package/src/deploy/providers/railway.ts +135 -55
- package/src/deploy/registry.ts +49 -28
- package/src/deploy/schema.ts +39 -0
- package/src/deploy/template-manager.ts +32 -0
- package/src/deploy/templates/github-workflow.yaml +23 -0
- package/src/deploy/types.ts +48 -16
- package/test/integration/commands/deploy.integration.test.ts +79 -3
- package/test/unit/commands/deploy.test.ts +96 -198
- package/test/unit/deploy/config-manager.test.ts +9 -5
- package/test/unit/deploy/providers/cloudflare.test.ts +95 -96
- package/test/unit/deploy/providers/dns-cloudflare.test.ts +148 -0
- package/test/unit/deploy/providers/github.test.ts +43 -47
- package/test/unit/deploy/providers/railway.test.ts +50 -261
- package/test/unit/deploy/registry.test.ts +20 -17
- package/tsup.config.ts +3 -0
- package/dist/chunk-2FKDEDDE.js.map +0 -1
- package/dist/chunk-EKCOW7FM.js.map +0 -1
- /package/dist/{chunk-GUUPSHWC.js.map → chunk-JEMIKBGX.js.map} +0 -0
- /package/dist/{chunk-OUGA4CB4.js.map → chunk-JS6WL5NS.js.map} +0 -0
- /package/dist/{chunk-GEESHGE4.js.map → chunk-L2RUXOL4.js.map} +0 -0
- /package/dist/{chunk-54HY52LH.js.map → chunk-QTJIGPQ3.js.map} +0 -0
- /package/dist/{chunk-2JW5BYZW.js.map → chunk-VKE7R2EZ.js.map} +0 -0
- /package/dist/{chunk-AC4B3HPJ.js.map → chunk-XONR27KC.js.map} +0 -0
- /package/dist/{chunk-PJIOCW2A.js.map → chunk-ZWNIZB3Q.js.map} +0 -0
package/src/commands/deploy.ts
CHANGED
|
@@ -3,46 +3,28 @@ import dotenv from 'dotenv';
|
|
|
3
3
|
import { BaseCommand } from '@nexical/cli-core';
|
|
4
4
|
import { ConfigManager } from '../deploy/config-manager';
|
|
5
5
|
import { ProviderRegistry } from '../deploy/registry';
|
|
6
|
-
import { DeploymentContext } from '../deploy/types';
|
|
6
|
+
import { DeploymentContext, HostingProvider, AppConfig, DnsRecord } from '../deploy/types';
|
|
7
7
|
|
|
8
8
|
export default class DeployCommand extends BaseCommand {
|
|
9
9
|
static usage = 'deploy';
|
|
10
10
|
static description = 'Deploy the application based on nexical.yaml configuration.';
|
|
11
|
-
static help = `This command orchestrates the deployment of your
|
|
11
|
+
static help = `This command orchestrates the deployment of your applications
|
|
12
12
|
by interacting with the providers specified in your configuration file.
|
|
13
13
|
|
|
14
14
|
CONFIGURATION:
|
|
15
15
|
- Requires a 'nexical.yaml' file in the project root.
|
|
16
|
-
-
|
|
17
|
-
and save the configuration for future uses.
|
|
16
|
+
- Supports definition of multiple applications under 'deploy.apps'.
|
|
18
17
|
- Supports loading environment variables from a .env file in the project root.
|
|
19
18
|
|
|
20
|
-
PROVIDERS:
|
|
21
|
-
- Backend: Railway, etc.
|
|
22
|
-
- Frontend: Cloudflare Pages, etc.
|
|
23
|
-
- Repository: GitHub, GitLab, etc.
|
|
24
|
-
|
|
25
19
|
PROCESS:
|
|
26
20
|
1. Loads environment variables from '.env'.
|
|
27
21
|
2. Loads configuration from 'nexical.yaml'.
|
|
28
|
-
3. Provisions resources
|
|
22
|
+
3. Provisions resources for each application.
|
|
29
23
|
4. Configures the repository (secrets/variables) for CI/CD.
|
|
30
|
-
5. Generates CI/CD workflow files.`;
|
|
24
|
+
5. Generates CI/CD workflow files for each application.`;
|
|
31
25
|
|
|
32
26
|
static args = {
|
|
33
27
|
options: [
|
|
34
|
-
{
|
|
35
|
-
name: '--backend <provider>',
|
|
36
|
-
description: 'Override backend provider',
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
name: '--frontend <provider>',
|
|
40
|
-
description: 'Override frontend provider',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
name: '--repo <provider>',
|
|
44
|
-
description: 'Override repositroy provider',
|
|
45
|
-
},
|
|
46
28
|
{
|
|
47
29
|
name: '--env <environment>',
|
|
48
30
|
description: 'Deployment environment (e.g. production, staging)',
|
|
@@ -53,6 +35,19 @@ PROCESS:
|
|
|
53
35
|
description: 'Simulate the deployment process',
|
|
54
36
|
default: false,
|
|
55
37
|
},
|
|
38
|
+
{
|
|
39
|
+
name: '--apps <apps>',
|
|
40
|
+
description: 'Comma separated list of applications to deploy',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: '--manual',
|
|
44
|
+
description: 'Perform a direct build and deployment from the local machine',
|
|
45
|
+
default: false,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: '--repo <provider>',
|
|
49
|
+
description: 'Repository provider to use (e.g. github, gitlab)',
|
|
50
|
+
},
|
|
56
51
|
],
|
|
57
52
|
};
|
|
58
53
|
|
|
@@ -60,7 +55,7 @@ PROCESS:
|
|
|
60
55
|
this.info('Starting Nexical Deployment...');
|
|
61
56
|
|
|
62
57
|
// Load environment variables from .env
|
|
63
|
-
dotenv.config({ path: path.join(process.cwd(), '.env') });
|
|
58
|
+
dotenv.config({ path: path.join(process.cwd(), '.env'), quiet: true });
|
|
64
59
|
|
|
65
60
|
const configManager = new ConfigManager(process.cwd());
|
|
66
61
|
const config = await configManager.load();
|
|
@@ -70,21 +65,35 @@ PROCESS:
|
|
|
70
65
|
await registry.loadCoreProviders();
|
|
71
66
|
await registry.loadLocalProviders(process.cwd());
|
|
72
67
|
|
|
73
|
-
// Resolve
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
68
|
+
// Resolve Applications
|
|
69
|
+
const appsMap = config.deploy?.apps || {};
|
|
70
|
+
let apps: AppConfig[] = Object.entries(appsMap).map(([name, appConfig]) => {
|
|
71
|
+
const app: AppConfig = {
|
|
72
|
+
...(appConfig as unknown as AppConfig),
|
|
73
|
+
name,
|
|
74
|
+
};
|
|
75
|
+
return app;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Filter applications if --apps is specified
|
|
79
|
+
const selectedApps = options.apps as string | undefined;
|
|
80
|
+
if (selectedApps) {
|
|
81
|
+
const appNames = selectedApps.split(',').map((s) => s.trim());
|
|
82
|
+
const filteredApps = apps.filter((app) => appNames.includes(app.name));
|
|
83
|
+
|
|
84
|
+
// Validation: Ensure all specified apps exist
|
|
85
|
+
const missingApps = appNames.filter((name) => !apps.find((app) => app.name === name));
|
|
86
|
+
if (missingApps.length > 0) {
|
|
87
|
+
this.error(
|
|
88
|
+
`The following applications were not found in nexical.yaml: ${missingApps.join(', ')}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
apps = filteredApps;
|
|
80
93
|
}
|
|
81
94
|
|
|
82
|
-
|
|
83
|
-
(
|
|
84
|
-
if (!frontendProviderName) {
|
|
85
|
-
this.error(
|
|
86
|
-
"Frontend provider not specified. Use --frontend flag or configure 'deploy.frontend.provider' in nexical.yaml.",
|
|
87
|
-
);
|
|
95
|
+
if (apps.length === 0) {
|
|
96
|
+
this.error('No applications found in nexical.yaml. Please configure [deploy.apps].');
|
|
88
97
|
}
|
|
89
98
|
|
|
90
99
|
const repoProviderName =
|
|
@@ -95,13 +104,7 @@ PROCESS:
|
|
|
95
104
|
);
|
|
96
105
|
}
|
|
97
106
|
|
|
98
|
-
const backendProvider = registry.getDeploymentProvider(backendProviderName!);
|
|
99
|
-
const frontendProvider = registry.getDeploymentProvider(frontendProviderName!);
|
|
100
107
|
const repoProvider = registry.getRepositoryProvider(repoProviderName!);
|
|
101
|
-
|
|
102
|
-
if (!backendProvider) throw new Error(`Backend provider '${backendProviderName}' not found.`);
|
|
103
|
-
if (!frontendProvider)
|
|
104
|
-
throw new Error(`Frontend provider '${frontendProviderName}' not found.`);
|
|
105
108
|
if (!repoProvider) throw new Error(`Repository provider '${repoProviderName}' not found.`);
|
|
106
109
|
|
|
107
110
|
const context: DeploymentContext = {
|
|
@@ -110,65 +113,143 @@ PROCESS:
|
|
|
110
113
|
options,
|
|
111
114
|
};
|
|
112
115
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
const activeApps: { provider: HostingProvider; app: AppConfig }[] = [];
|
|
117
|
+
const secrets: Record<string, string> = {};
|
|
118
|
+
const variables: Record<string, string> = {};
|
|
116
119
|
|
|
117
|
-
this.info(`
|
|
118
|
-
await frontendProvider.provision(context);
|
|
120
|
+
this.info(`Deploying ${apps.length} applications in parallel...`);
|
|
119
121
|
|
|
120
|
-
|
|
121
|
-
this.info(`Configuring Repository with ${repoProvider.name}...`);
|
|
122
|
+
const isManual = !!options.manual;
|
|
122
123
|
|
|
123
|
-
|
|
124
|
+
await Promise.all(
|
|
125
|
+
apps.map(async (app) => {
|
|
126
|
+
this.info(`Processing application: ${app.name}...`);
|
|
127
|
+
const provider = registry.getHostingProvider(app.provider);
|
|
128
|
+
if (!provider) {
|
|
129
|
+
this.error(`Provider '${app.provider}' not found for application '${app.name}'.`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
124
132
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const backendSecrets = await backendProvider.getSecrets(context);
|
|
129
|
-
Object.assign(secrets, backendSecrets);
|
|
130
|
-
} catch (e: unknown) {
|
|
131
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
132
|
-
this.error(`Failed to resolve secrets for ${backendProvider.name}: ${message}`);
|
|
133
|
-
}
|
|
133
|
+
// Build
|
|
134
|
+
if (isManual && app.buildCommand) {
|
|
135
|
+
this.info(` Building ${app.name} locally...`);
|
|
134
136
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
Object.assign(secrets, frontendSecrets);
|
|
140
|
-
} catch (e: unknown) {
|
|
141
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
142
|
-
this.error(`Failed to resolve secrets for ${frontendProvider.name}: ${message}`);
|
|
143
|
-
}
|
|
137
|
+
const buildEnv: Record<string, string> = {
|
|
138
|
+
...(process.env as Record<string, string>),
|
|
139
|
+
...(app.env || {}),
|
|
140
|
+
};
|
|
144
141
|
|
|
145
|
-
|
|
142
|
+
if (app.domain) {
|
|
143
|
+
const domain = Array.isArray(app.domain) ? app.domain[0] : app.domain;
|
|
144
|
+
buildEnv.SITE = `https://${domain}`;
|
|
145
|
+
buildEnv.BASE = '/';
|
|
146
|
+
}
|
|
146
147
|
|
|
147
|
-
|
|
148
|
+
if (context.options.dryRun) {
|
|
149
|
+
this.info(` [Dry Run] Would run build: ${app.buildCommand}`);
|
|
150
|
+
if (buildEnv.SITE) {
|
|
151
|
+
this.info(
|
|
152
|
+
` [Dry Run] Environment override: SITE=${buildEnv.SITE} BASE=${buildEnv.BASE}`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
try {
|
|
157
|
+
const { execAsync } = await import('../deploy/utils');
|
|
158
|
+
await execAsync(app.buildCommand, { env: buildEnv });
|
|
159
|
+
} catch (e: unknown) {
|
|
160
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
161
|
+
this.error(`Build failed for ${app.name}: ${message}`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
148
166
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
Object.assign(variables, backendVars);
|
|
153
|
-
} catch (e: unknown) {
|
|
154
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
155
|
-
this.error(`Failed to resolve variables for ${backendProvider.name}: ${message}`);
|
|
156
|
-
}
|
|
167
|
+
// Provision
|
|
168
|
+
this.info(` Provisioning ${app.name} with ${provider.name}...`);
|
|
169
|
+
await provider.provision(context, app);
|
|
157
170
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
164
|
-
this.error(`Failed to resolve variables for ${frontendProvider.name}: ${message}`);
|
|
165
|
-
}
|
|
171
|
+
// Direct Deploy
|
|
172
|
+
if (isManual && provider.deploy) {
|
|
173
|
+
this.info(` Performing direct deployment for ${app.name}...`);
|
|
174
|
+
await provider.deploy(context, app);
|
|
175
|
+
}
|
|
166
176
|
|
|
177
|
+
// Collect secrets
|
|
178
|
+
this.info(` Resolving secrets for ${app.name} from ${provider.name}...`);
|
|
179
|
+
try {
|
|
180
|
+
const appSecrets = await provider.getSecrets(context, app);
|
|
181
|
+
Object.assign(secrets, appSecrets);
|
|
182
|
+
} catch (e: unknown) {
|
|
183
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
184
|
+
this.error(`Failed to resolve secrets for ${app.name} (${provider.name}): ${message}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Collect variables
|
|
188
|
+
this.info(` Resolving variables for ${app.name} from ${provider.name}...`);
|
|
189
|
+
try {
|
|
190
|
+
const appVars = await provider.getVariables(context, app);
|
|
191
|
+
Object.assign(variables, appVars);
|
|
192
|
+
} catch (e: unknown) {
|
|
193
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
194
|
+
this.error(`Failed to resolve variables for ${app.name} (${provider.name}): ${message}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
activeApps.push({ provider, app });
|
|
198
|
+
}),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Configure Repo
|
|
202
|
+
this.info(`Configuring Repository with ${repoProvider.name}...`);
|
|
203
|
+
await repoProvider.configureSecrets(context, secrets);
|
|
167
204
|
await repoProvider.configureVariables(context, variables);
|
|
168
205
|
|
|
169
206
|
// Generate Workflows
|
|
170
207
|
this.info('Generating CI/CD Workflows...');
|
|
171
|
-
await repoProvider.generateWorkflow(context,
|
|
208
|
+
await repoProvider.generateWorkflow(context, activeApps);
|
|
209
|
+
|
|
210
|
+
// DNS Provisioning
|
|
211
|
+
const dnsConfig = config.deploy?.dns;
|
|
212
|
+
if (dnsConfig?.provider) {
|
|
213
|
+
const dnsProvider = registry.getDnsProvider(dnsConfig.provider);
|
|
214
|
+
if (!dnsProvider) {
|
|
215
|
+
this.error(`DNS provider '${dnsConfig.provider}' not found.`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const dnsRecords: DnsRecord[] = [];
|
|
220
|
+
for (const { app, provider } of activeApps) {
|
|
221
|
+
const target =
|
|
222
|
+
app.dnsTarget ||
|
|
223
|
+
(provider.getDefaultDnsTarget ? provider.getDefaultDnsTarget(app) : undefined);
|
|
224
|
+
|
|
225
|
+
if (app.domain && target) {
|
|
226
|
+
const domains = Array.isArray(app.domain) ? app.domain : [app.domain];
|
|
227
|
+
for (const domain of domains) {
|
|
228
|
+
const isIp = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(target);
|
|
229
|
+
dnsRecords.push({
|
|
230
|
+
type: isIp ? 'A' : 'CNAME',
|
|
231
|
+
name: domain,
|
|
232
|
+
content: target,
|
|
233
|
+
proxied: true,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
} else if (app.domain && !target) {
|
|
237
|
+
this.warn(
|
|
238
|
+
`App '${app.name}' specifies domain(s) but no 'dnsTarget' could be inferred. Skipping DNS auto-provisioning.`,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (dnsRecords.length > 0) {
|
|
244
|
+
this.info(`Configuring DNS with ${dnsProvider.name}...`);
|
|
245
|
+
try {
|
|
246
|
+
await dnsProvider.provision(context, dnsRecords);
|
|
247
|
+
} catch (e: unknown) {
|
|
248
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
249
|
+
this.warn(`DNS provisioning failed: ${message}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
172
253
|
|
|
173
254
|
this.success('Deployment configuration complete!');
|
|
174
255
|
}
|
|
@@ -2,6 +2,8 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import YAML from 'yaml';
|
|
4
4
|
import { NexicalConfig } from './types';
|
|
5
|
+
import { DeploymentSchema } from './schema';
|
|
6
|
+
import { logger } from '@nexical/cli-core';
|
|
5
7
|
|
|
6
8
|
export class ConfigManager {
|
|
7
9
|
private configPath: string;
|
|
@@ -13,7 +15,18 @@ export class ConfigManager {
|
|
|
13
15
|
async load(): Promise<NexicalConfig> {
|
|
14
16
|
try {
|
|
15
17
|
const content = await fs.readFile(this.configPath, 'utf-8');
|
|
16
|
-
|
|
18
|
+
const parsed = YAML.parse(content);
|
|
19
|
+
|
|
20
|
+
const result = DeploymentSchema.safeParse(parsed);
|
|
21
|
+
if (!result.success) {
|
|
22
|
+
logger.error('Invalid nexical.yaml configuration:');
|
|
23
|
+
result.error.issues.forEach((err) => {
|
|
24
|
+
logger.error(` - ${err.path.join('.')}: ${err.message}`);
|
|
25
|
+
});
|
|
26
|
+
throw new Error('Configuration validation failed.');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return result.data as NexicalConfig;
|
|
17
30
|
} catch (error: unknown) {
|
|
18
31
|
if (
|
|
19
32
|
error &&
|