@nexical/cli 0.11.22 → 0.12.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/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 +134 -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 +157 -93
- 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/tsconfig.json +1 -1
- 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,126 @@ 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
|
-
|
|
120
|
+
this.info(`Deploying ${apps.length} applications in parallel...`);
|
|
121
|
+
|
|
122
|
+
const isManual = !!options.manual;
|
|
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
|
+
}
|
|
132
|
+
|
|
133
|
+
// Build
|
|
134
|
+
if (isManual && app.buildCommand) {
|
|
135
|
+
this.info(` Building ${app.name} locally...`);
|
|
136
|
+
if (context.options.dryRun) {
|
|
137
|
+
this.info(` [Dry Run] Would run build: ${app.buildCommand}`);
|
|
138
|
+
} else {
|
|
139
|
+
try {
|
|
140
|
+
const { execAsync } = await import('../deploy/utils');
|
|
141
|
+
await execAsync(app.buildCommand);
|
|
142
|
+
} catch (e: unknown) {
|
|
143
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
144
|
+
this.error(`Build failed for ${app.name}: ${message}`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Provision
|
|
151
|
+
this.info(` Provisioning ${app.name} with ${provider.name}...`);
|
|
152
|
+
await provider.provision(context, app);
|
|
153
|
+
|
|
154
|
+
// Direct Deploy
|
|
155
|
+
if (isManual && provider.deploy) {
|
|
156
|
+
this.info(` Performing direct deployment for ${app.name}...`);
|
|
157
|
+
await provider.deploy(context, app);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Collect secrets
|
|
161
|
+
this.info(` Resolving secrets for ${app.name} from ${provider.name}...`);
|
|
162
|
+
try {
|
|
163
|
+
const appSecrets = await provider.getSecrets(context, app);
|
|
164
|
+
Object.assign(secrets, appSecrets);
|
|
165
|
+
} catch (e: unknown) {
|
|
166
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
167
|
+
this.error(`Failed to resolve secrets for ${app.name} (${provider.name}): ${message}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Collect variables
|
|
171
|
+
this.info(` Resolving variables for ${app.name} from ${provider.name}...`);
|
|
172
|
+
try {
|
|
173
|
+
const appVars = await provider.getVariables(context, app);
|
|
174
|
+
Object.assign(variables, appVars);
|
|
175
|
+
} catch (e: unknown) {
|
|
176
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
177
|
+
this.error(`Failed to resolve variables for ${app.name} (${provider.name}): ${message}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
activeApps.push({ provider, app });
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
119
183
|
|
|
120
184
|
// Configure Repo
|
|
121
185
|
this.info(`Configuring Repository with ${repoProvider.name}...`);
|
|
122
|
-
|
|
123
|
-
const secrets: Record<string, string> = {};
|
|
124
|
-
|
|
125
|
-
// Collect secrets from Backend Provider
|
|
126
|
-
this.info(`Resolving secrets from ${backendProvider.name}...`);
|
|
127
|
-
try {
|
|
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
|
-
}
|
|
134
|
-
|
|
135
|
-
// Collect secrets from Frontend Provider
|
|
136
|
-
this.info(`Resolving secrets from ${frontendProvider.name}...`);
|
|
137
|
-
try {
|
|
138
|
-
const frontendSecrets = await frontendProvider.getSecrets(context);
|
|
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
|
-
}
|
|
144
|
-
|
|
145
186
|
await repoProvider.configureSecrets(context, secrets);
|
|
146
|
-
|
|
147
|
-
const variables: Record<string, string> = {};
|
|
148
|
-
|
|
149
|
-
// Collect variables from Backend Provider
|
|
150
|
-
try {
|
|
151
|
-
const backendVars = await backendProvider.getVariables(context);
|
|
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
|
-
}
|
|
157
|
-
|
|
158
|
-
// Collect variables from Frontend Provider
|
|
159
|
-
try {
|
|
160
|
-
const frontendVars = await frontendProvider.getVariables(context);
|
|
161
|
-
Object.assign(variables, frontendVars);
|
|
162
|
-
} catch (e: unknown) {
|
|
163
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
164
|
-
this.error(`Failed to resolve variables for ${frontendProvider.name}: ${message}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
187
|
await repoProvider.configureVariables(context, variables);
|
|
168
188
|
|
|
169
189
|
// Generate Workflows
|
|
170
190
|
this.info('Generating CI/CD Workflows...');
|
|
171
|
-
await repoProvider.generateWorkflow(context,
|
|
191
|
+
await repoProvider.generateWorkflow(context, activeApps);
|
|
192
|
+
|
|
193
|
+
// DNS Provisioning
|
|
194
|
+
const dnsConfig = config.deploy?.dns;
|
|
195
|
+
if (dnsConfig?.provider) {
|
|
196
|
+
const dnsProvider = registry.getDnsProvider(dnsConfig.provider);
|
|
197
|
+
if (!dnsProvider) {
|
|
198
|
+
this.error(`DNS provider '${dnsConfig.provider}' not found.`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const dnsRecords: DnsRecord[] = [];
|
|
203
|
+
for (const { app, provider } of activeApps) {
|
|
204
|
+
const target =
|
|
205
|
+
app.dnsTarget ||
|
|
206
|
+
(provider.getDefaultDnsTarget ? provider.getDefaultDnsTarget(app) : undefined);
|
|
207
|
+
|
|
208
|
+
if (app.domain && target) {
|
|
209
|
+
const domains = Array.isArray(app.domain) ? app.domain : [app.domain];
|
|
210
|
+
for (const domain of domains) {
|
|
211
|
+
const isIp = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(target);
|
|
212
|
+
dnsRecords.push({
|
|
213
|
+
type: isIp ? 'A' : 'CNAME',
|
|
214
|
+
name: domain,
|
|
215
|
+
content: target,
|
|
216
|
+
proxied: true,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
} else if (app.domain && !target) {
|
|
220
|
+
this.warn(
|
|
221
|
+
`App '${app.name}' specifies domain(s) but no 'dnsTarget' could be inferred. Skipping DNS auto-provisioning.`,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (dnsRecords.length > 0) {
|
|
227
|
+
this.info(`Configuring DNS with ${dnsProvider.name}...`);
|
|
228
|
+
try {
|
|
229
|
+
await dnsProvider.provision(context, dnsRecords);
|
|
230
|
+
} catch (e: unknown) {
|
|
231
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
232
|
+
this.warn(`DNS provisioning failed: ${message}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
172
236
|
|
|
173
237
|
this.success('Deployment configuration complete!');
|
|
174
238
|
}
|
|
@@ -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 &&
|