@nexical/cli 0.11.3 → 0.11.5
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/dist/chunk-2FKDEDDE.js +39 -0
- package/dist/chunk-2FKDEDDE.js.map +1 -0
- package/dist/chunk-2JW5BYZW.js +24 -0
- package/dist/chunk-2JW5BYZW.js.map +1 -0
- package/dist/chunk-EKCOW7FM.js +118 -0
- package/dist/chunk-EKCOW7FM.js.map +1 -0
- package/dist/index.js +13 -11
- package/dist/index.js.map +1 -1
- package/dist/src/commands/deploy.d.ts +3 -12
- package/dist/src/commands/deploy.js +106 -108
- package/dist/src/commands/deploy.js.map +1 -1
- package/dist/src/commands/init.js +3 -3
- package/dist/src/commands/module/add.js +3 -3
- package/dist/src/deploy/config-manager.d.ts +11 -0
- package/dist/src/deploy/config-manager.js +9 -0
- package/dist/src/deploy/config-manager.js.map +1 -0
- package/dist/src/deploy/providers/cloudflare.d.ts +12 -0
- package/dist/src/deploy/providers/cloudflare.js +113 -0
- package/dist/src/deploy/providers/cloudflare.js.map +1 -0
- package/dist/src/deploy/providers/github.d.ts +10 -0
- package/dist/src/deploy/providers/github.js +121 -0
- package/dist/src/deploy/providers/github.js.map +1 -0
- package/dist/src/deploy/providers/railway.d.ts +12 -0
- package/dist/src/deploy/providers/railway.js +89 -0
- package/dist/src/deploy/providers/railway.js.map +1 -0
- package/dist/src/deploy/registry.d.ts +15 -0
- package/dist/src/deploy/registry.js +9 -0
- package/dist/src/deploy/registry.js.map +1 -0
- package/dist/src/deploy/types.d.ts +47 -0
- package/dist/src/deploy/types.js +8 -0
- package/dist/src/deploy/types.js.map +1 -0
- package/dist/src/deploy/utils.d.ts +6 -0
- package/dist/src/deploy/utils.js +11 -0
- package/dist/src/deploy/utils.js.map +1 -0
- package/package.json +13 -11
- package/src/commands/deploy.ts +128 -144
- package/src/deploy/config-manager.ts +41 -0
- package/src/deploy/providers/cloudflare.ts +143 -0
- package/src/deploy/providers/github.ts +135 -0
- package/src/deploy/providers/railway.ts +103 -0
- package/src/deploy/registry.ts +136 -0
- package/src/deploy/types.ts +63 -0
- package/src/deploy/utils.ts +13 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import YAML from 'yaml';
|
|
4
|
+
import { logger } from '@nexical/cli-core';
|
|
5
|
+
import { RepositoryProvider, DeploymentContext, DeploymentProvider } from '../types';
|
|
6
|
+
import { execAsync } from '../utils';
|
|
7
|
+
|
|
8
|
+
export class GitHubProvider implements RepositoryProvider {
|
|
9
|
+
name = 'github';
|
|
10
|
+
|
|
11
|
+
async configureSecrets(
|
|
12
|
+
context: DeploymentContext,
|
|
13
|
+
secrets: Record<string, string>,
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
for (const [key, value] of Object.entries(secrets)) {
|
|
16
|
+
if (!value) continue;
|
|
17
|
+
logger.info(`Setting secret ${key} in GitHub...`);
|
|
18
|
+
if (context.options.dryRun) {
|
|
19
|
+
logger.info(`[Dry Run] Would set secret ${key}`);
|
|
20
|
+
} else {
|
|
21
|
+
await execAsync(`gh secret set ${key} --body "${value}"`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async configureVariables(
|
|
27
|
+
context: DeploymentContext,
|
|
28
|
+
variables: Record<string, string>,
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
31
|
+
if (!value) continue;
|
|
32
|
+
logger.info(`Setting variable ${key} in GitHub...`);
|
|
33
|
+
if (context.options.dryRun) {
|
|
34
|
+
logger.info(`[Dry Run] Would set variable ${key}`);
|
|
35
|
+
} else {
|
|
36
|
+
await execAsync(`gh variable set ${key} --body "${value}"`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async generateWorkflow(context: DeploymentContext, targets: DeploymentProvider[]): Promise<void> {
|
|
42
|
+
const workflowsDir = path.join(context.cwd, '.github/workflows');
|
|
43
|
+
await fs.mkdir(workflowsDir, { recursive: true });
|
|
44
|
+
|
|
45
|
+
for (const target of targets) {
|
|
46
|
+
const config = target.getCIConfig('github');
|
|
47
|
+
if (!config) continue;
|
|
48
|
+
|
|
49
|
+
const filename = `deploy-${target.type}.yml`;
|
|
50
|
+
const filepath = path.join(workflowsDir, filename);
|
|
51
|
+
|
|
52
|
+
const workflow: Record<string, unknown> = {
|
|
53
|
+
name: `Deploy ${target.type === 'backend' ? 'Backend' : 'Frontend'} to ${target.name}`,
|
|
54
|
+
on: {
|
|
55
|
+
push: { branches: ['main'] },
|
|
56
|
+
workflow_dispatch: {},
|
|
57
|
+
},
|
|
58
|
+
jobs: {
|
|
59
|
+
deploy: {
|
|
60
|
+
'runs-on': 'ubuntu-latest',
|
|
61
|
+
permissions: {
|
|
62
|
+
contents: 'read',
|
|
63
|
+
deployments: 'write',
|
|
64
|
+
},
|
|
65
|
+
steps: [
|
|
66
|
+
{
|
|
67
|
+
name: 'Checkout',
|
|
68
|
+
uses: 'actions/checkout@v4',
|
|
69
|
+
with: { submodules: 'recursive' },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'Setup Node',
|
|
73
|
+
uses: 'actions/setup-node@v4',
|
|
74
|
+
with: { 'node-version': 20, cache: 'npm' },
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'Install Dependencies',
|
|
78
|
+
run: 'npm ci',
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
const steps = (workflow as any).jobs.deploy.steps;
|
|
87
|
+
|
|
88
|
+
// Build (if frontend)
|
|
89
|
+
if (target.type === 'frontend') {
|
|
90
|
+
steps.push({
|
|
91
|
+
name: 'Build Frontend',
|
|
92
|
+
run: 'npm run build --workspace=@app/frontend',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Provider Install Steps
|
|
97
|
+
if (config.installSteps) {
|
|
98
|
+
for (const step of config.installSteps) {
|
|
99
|
+
steps.push({
|
|
100
|
+
name: `Install ${target.name} CLI`,
|
|
101
|
+
run: step,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Provider Deploy Steps
|
|
107
|
+
if (config.deploySteps) {
|
|
108
|
+
for (const step of config.deploySteps) {
|
|
109
|
+
const deployStep: Record<string, unknown> = {
|
|
110
|
+
name: `Deploy to ${target.name}`,
|
|
111
|
+
run: step,
|
|
112
|
+
'working-directory': target.type === 'backend' ? 'apps/backend' : 'apps/frontend',
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if (config.secrets && config.secrets.length > 0) {
|
|
116
|
+
deployStep.env = config.secrets.reduce((acc: Record<string, string>, secret) => {
|
|
117
|
+
acc[secret] = `\${{ secrets.${secret} }}`;
|
|
118
|
+
return acc;
|
|
119
|
+
}, {});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
steps.push(deployStep);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Provider Action Step
|
|
127
|
+
if (config.githubActionStep) {
|
|
128
|
+
steps.push(config.githubActionStep);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await fs.writeFile(filepath, YAML.stringify(workflow), 'utf-8');
|
|
132
|
+
logger.info(`Generated workflow: ${filepath}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { logger } from '@nexical/cli-core';
|
|
3
|
+
import { DeploymentProvider, DeploymentContext, CIConfig } from '../types';
|
|
4
|
+
import { execAsync } from '../utils';
|
|
5
|
+
|
|
6
|
+
export class RailwayProvider implements DeploymentProvider {
|
|
7
|
+
name = 'railway';
|
|
8
|
+
type = 'backend' as const;
|
|
9
|
+
|
|
10
|
+
async provision(context: DeploymentContext): Promise<void> {
|
|
11
|
+
const backendDir = path.join(context.cwd, 'apps/backend');
|
|
12
|
+
// Resolve project name/token
|
|
13
|
+
const railwayName = context.config.deploy?.backend?.projectName;
|
|
14
|
+
|
|
15
|
+
if (!railwayName) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
"Railway project name not found in nexical.yaml. Please configure 'deploy.backend.projectName'.",
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Service name defaults to project name for the primary backend
|
|
22
|
+
const serviceName = railwayName || 'nexical-backend';
|
|
23
|
+
// Note: Token is usually handled by `railway login` for CLI, but for CI we need it.
|
|
24
|
+
// The provider might not need to know the token for `provision` if we rely on CLI auth.
|
|
25
|
+
// However, we might need to export it for GitHub secrets.
|
|
26
|
+
|
|
27
|
+
logger.info('Configuring Railway...');
|
|
28
|
+
|
|
29
|
+
if (context.options.dryRun) {
|
|
30
|
+
logger.info('[Dry Run] Would check Railway status and init project.');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
try {
|
|
36
|
+
await execAsync('railway status', { cwd: backendDir });
|
|
37
|
+
} catch {
|
|
38
|
+
const initCmd = railwayName ? `railway init --name ${railwayName}` : 'railway init';
|
|
39
|
+
logger.info(`No Railway project detected in apps/backend. Initializing with: ${initCmd}`);
|
|
40
|
+
await execAsync(initCmd, { cwd: backendDir });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
logger.info(`Adding PostgreSQL service if missing for "${serviceName}"...`);
|
|
44
|
+
const { stdout: status } = await execAsync('railway status', { cwd: backendDir });
|
|
45
|
+
if (!status.includes('postgres')) {
|
|
46
|
+
await execAsync('railway add --database postgres', { cwd: backendDir });
|
|
47
|
+
}
|
|
48
|
+
} catch (e: unknown) {
|
|
49
|
+
logger.warn(
|
|
50
|
+
'Railway setup encountered an issue. Ensure you are logged in with `railway login`.',
|
|
51
|
+
);
|
|
52
|
+
throw e;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async getSecrets(context: DeploymentContext): Promise<Record<string, string>> {
|
|
57
|
+
const options = context.config.deploy?.backend?.options || {};
|
|
58
|
+
const secrets: Record<string, string> = {};
|
|
59
|
+
|
|
60
|
+
// Resolve Railway Token
|
|
61
|
+
// Priority: Configured Env Var > Default Env Var
|
|
62
|
+
const tokenEnvVar = typeof options.tokenEnvVar === 'string' ? options.tokenEnvVar : undefined;
|
|
63
|
+
const token = (tokenEnvVar ? process.env[tokenEnvVar] : undefined) || process.env.RAILWAY_TOKEN;
|
|
64
|
+
|
|
65
|
+
if (!token) {
|
|
66
|
+
// Strict check: Error if token is missing
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Railway Token not found. Please provide it via:\n` +
|
|
69
|
+
`1. Configuring 'deploy.backend.options.tokenEnvVar' in nexical.yaml and setting that env var in .env\n` +
|
|
70
|
+
`2. Setting RAILWAY_TOKEN in .env`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
secrets['RAILWAY_TOKEN'] = token;
|
|
75
|
+
return secrets;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async getVariables(context: DeploymentContext): Promise<Record<string, string>> {
|
|
79
|
+
const railwayName = context.config.deploy?.backend?.projectName;
|
|
80
|
+
|
|
81
|
+
if (!railwayName) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
"Railway project name not found in nexical.yaml. Please configure 'deploy.backend.projectName'.",
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Service name defaults to project name
|
|
88
|
+
const serviceName = railwayName || 'nexical-backend';
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
RAILWAY_SERVICE_NAME: serviceName,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getCIConfig(): CIConfig {
|
|
96
|
+
return {
|
|
97
|
+
secrets: ['RAILWAY_TOKEN'],
|
|
98
|
+
variables: ['RAILWAY_SERVICE_NAME'],
|
|
99
|
+
installSteps: ['npm install -g @railway/cli'],
|
|
100
|
+
deploySteps: ['railway up --service ${{ vars.RAILWAY_SERVICE_NAME }} --detach'],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { logger } from '@nexical/cli-core';
|
|
4
|
+
import { DeploymentProvider, RepositoryProvider } from './types';
|
|
5
|
+
|
|
6
|
+
export class ProviderRegistry {
|
|
7
|
+
private deploymentProviders: Map<string, DeploymentProvider> = new Map();
|
|
8
|
+
private repositoryProviders: Map<string, RepositoryProvider> = new Map();
|
|
9
|
+
|
|
10
|
+
registerDeploymentProvider(provider: DeploymentProvider) {
|
|
11
|
+
this.deploymentProviders.set(provider.name, provider);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
registerRepositoryProvider(provider: RepositoryProvider) {
|
|
15
|
+
this.repositoryProviders.set(provider.name, provider);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getDeploymentProvider(name: string): DeploymentProvider | undefined {
|
|
19
|
+
return this.deploymentProviders.get(name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getRepositoryProvider(name: string): RepositoryProvider | undefined {
|
|
23
|
+
return this.repositoryProviders.get(name);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private registerProviderFromModule(module: unknown, source: string) {
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
const moduleAny = module as any;
|
|
29
|
+
let provider = moduleAny.default;
|
|
30
|
+
|
|
31
|
+
// Handle named exports if default is missing (fallback)
|
|
32
|
+
if (!provider && Object.keys(moduleAny).length > 0) {
|
|
33
|
+
// Try to find a class export that looks like a provider
|
|
34
|
+
for (const key of Object.keys(moduleAny)) {
|
|
35
|
+
if (typeof moduleAny[key] === 'function') {
|
|
36
|
+
provider = moduleAny[key];
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If it's a class, instantiate it
|
|
43
|
+
if (typeof provider === 'function') {
|
|
44
|
+
try {
|
|
45
|
+
provider = new provider();
|
|
46
|
+
} catch {
|
|
47
|
+
// Not a constructor or failed
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (provider) {
|
|
52
|
+
if (typeof provider.provision === 'function' && typeof provider.getCIConfig === 'function') {
|
|
53
|
+
logger.info(`[Registry] Loaded ${source} deployment provider: ${provider.name}`);
|
|
54
|
+
this.registerDeploymentProvider(provider as DeploymentProvider);
|
|
55
|
+
} else if (
|
|
56
|
+
typeof provider.configureSecrets === 'function' &&
|
|
57
|
+
typeof provider.generateWorkflow === 'function'
|
|
58
|
+
) {
|
|
59
|
+
logger.info(`[Registry] Loaded ${source} repository provider: ${provider.name}`);
|
|
60
|
+
this.registerRepositoryProvider(provider as RepositoryProvider);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async loadCoreProviders() {
|
|
66
|
+
const dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
67
|
+
|
|
68
|
+
// Try multiple paths to find the providers directory
|
|
69
|
+
// 1. 'providers' - Standard source structure / flattened dist
|
|
70
|
+
// 2. 'src/deploy/providers' - tsup output (chunk in root, files in src/...)
|
|
71
|
+
const candidates = [
|
|
72
|
+
path.join(dirname, 'providers'),
|
|
73
|
+
path.join(dirname, 'src/deploy/providers'),
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
let providersDir = '';
|
|
77
|
+
for (const candidate of candidates) {
|
|
78
|
+
try {
|
|
79
|
+
await fs.access(candidate);
|
|
80
|
+
providersDir = candidate;
|
|
81
|
+
break;
|
|
82
|
+
} catch {
|
|
83
|
+
// Ignore missing dir
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!providersDir) {
|
|
88
|
+
logger.warn(
|
|
89
|
+
`[Registry] Could not locate core providers directory. Checked: ${candidates.join(', ')}`,
|
|
90
|
+
);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const files = await fs.readdir(providersDir);
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
if (file.endsWith('.js') || (file.endsWith('.ts') && !file.endsWith('.d.ts'))) {
|
|
98
|
+
try {
|
|
99
|
+
const providerPath = path.join(providersDir, file);
|
|
100
|
+
const module = await import(providerPath);
|
|
101
|
+
this.registerProviderFromModule(module, 'core');
|
|
102
|
+
} catch (e: unknown) {
|
|
103
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
104
|
+
logger.warn(`Failed to load core provider from ${file}: ${message}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch (e: unknown) {
|
|
109
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
110
|
+
logger.warn(`Failed to scan core providers at ${providersDir}: ${message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async loadLocalProviders(cwd: string) {
|
|
115
|
+
const deployDir = path.join(cwd, 'deploy');
|
|
116
|
+
try {
|
|
117
|
+
const files = await fs.readdir(deployDir);
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
|
120
|
+
try {
|
|
121
|
+
const providerPath = path.join(deployDir, file);
|
|
122
|
+
// Use jiti to load TS/JS files dynamically
|
|
123
|
+
const jiti = (await import('jiti')).createJiti(import.meta.url);
|
|
124
|
+
const module = (await jiti.import(providerPath)) as unknown;
|
|
125
|
+
this.registerProviderFromModule(module, 'local');
|
|
126
|
+
} catch (e: unknown) {
|
|
127
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
128
|
+
logger.warn(`Failed to load local provider from ${file}: ${message}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
// Ignore if deploy dir doesn't exist
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface CIConfig {
|
|
2
|
+
secrets: string[];
|
|
3
|
+
variables: string[];
|
|
4
|
+
installSteps?: string[];
|
|
5
|
+
buildSteps?: string[];
|
|
6
|
+
deploySteps?: string[];
|
|
7
|
+
// Platform specific overrides (e.g. uses: action/...)
|
|
8
|
+
githubActionStep?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface DeploymentContext {
|
|
12
|
+
cwd: string;
|
|
13
|
+
config: NexicalConfig;
|
|
14
|
+
options: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DeploymentProvider {
|
|
18
|
+
name: string;
|
|
19
|
+
type: 'frontend' | 'backend';
|
|
20
|
+
|
|
21
|
+
// Interactive or automatic setup of the provider resources
|
|
22
|
+
provision(context: DeploymentContext): Promise<void>;
|
|
23
|
+
|
|
24
|
+
// Returns the CI configuration for this provider
|
|
25
|
+
getCIConfig(repoType: 'github' | 'gitlab'): CIConfig;
|
|
26
|
+
|
|
27
|
+
// Returns a map of secrets to be set in the repository (e.g. tokens, account IDs)
|
|
28
|
+
// The provider is responsible for resolving these from config/env and throwing if missing.
|
|
29
|
+
getSecrets(context: DeploymentContext): Promise<Record<string, string>>;
|
|
30
|
+
|
|
31
|
+
// Returns a map of variables to be set in the repository (e.g. project names, service names)
|
|
32
|
+
getVariables(context: DeploymentContext): Promise<Record<string, string>>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RepositoryProvider {
|
|
36
|
+
name: string;
|
|
37
|
+
|
|
38
|
+
// Sets secrets/variables in the repo
|
|
39
|
+
configureSecrets(context: DeploymentContext, secrets: Record<string, string>): Promise<void>;
|
|
40
|
+
configureVariables(context: DeploymentContext, variables: Record<string, string>): Promise<void>;
|
|
41
|
+
|
|
42
|
+
// Generates and writes the CI workflow files
|
|
43
|
+
generateWorkflow(context: DeploymentContext, targets: DeploymentProvider[]): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface NexicalConfig {
|
|
47
|
+
deploy?: {
|
|
48
|
+
backend?: {
|
|
49
|
+
provider: string;
|
|
50
|
+
projectName?: string;
|
|
51
|
+
options?: Record<string, unknown>;
|
|
52
|
+
};
|
|
53
|
+
frontend?: {
|
|
54
|
+
provider: string;
|
|
55
|
+
projectName?: string;
|
|
56
|
+
options?: Record<string, unknown>;
|
|
57
|
+
};
|
|
58
|
+
repository?: {
|
|
59
|
+
provider: string;
|
|
60
|
+
options?: Record<string, unknown>;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
|
|
4
|
+
export const execAsync = promisify(exec);
|
|
5
|
+
|
|
6
|
+
export async function checkCommand(command: string): Promise<boolean> {
|
|
7
|
+
try {
|
|
8
|
+
await execAsync(command);
|
|
9
|
+
return true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|