@nexical/cli 0.11.4 → 0.11.6
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 +2 -18
- package/dist/src/commands/deploy.js +100 -150
- 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 +13 -0
- package/dist/src/deploy/providers/railway.js +114 -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 +125 -193
- 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 +143 -0
- package/src/deploy/registry.ts +136 -0
- package/src/deploy/types.ts +63 -0
- package/src/deploy/utils.ts +13 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/deploy.ts"],"sourcesContent":["import { BaseCommand } from '@nexical/cli-core';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport path from 'node:path';\n\nconst execAsync = promisify(exec);\n\ninterface DeployOptions {\n dryRun: boolean;\n railwayToken?: string;\n railwayName?: string;\n backendName: string;\n frontendName: string;\n cloudflareToken?: string;\n cloudflareAccount?: string;\n}\n\nexport default class DeployCommand extends BaseCommand {\n static description = `Deploy the application to Railway and Cloudflare.\n\nENVIRONMENT SETUP & PREREQUISITES:\n1. Install Required CLIs:\n - Railway CLI: npm i -g @railway/cli\n - Wrangler (Cloudflare): npm i -g wrangler\n - GitHub CLI: https://cli.github.com/\n\n2. Authentication:\n - Railway: Run 'railway login'\n - GitHub: Run 'gh auth login'\n - Cloudflare: Obtain an API Token (with Pages edit permissions) and your Account ID from the dashboard.\n\n3. Configuration:\n Run this command with --cloudflare-token and --cloudflare-account to automate the full setup.\n Optional: Use --railway-token if you prefer not to use the interactive login.\n Optional: Use --railway-name to specify a custom Railway project name.\n Optional: Use --backend-name to specify the Railway service name (default: nexical-backend).\n Optional: Use --frontend-name to specify the Cloudflare Pages project name (default: nexical-frontend).\n\nPROCESS:\n- Provisions a PostgreSQL database on Railway (if missing).\n- Creates a Cloudflare Pages project for the frontend.\n- Syncs all necessary deployment secrets and variables to GitHub for CI/CD automation.`;\n\n static args = {\n options: [\n {\n name: '--dry-run',\n description: 'Simulate the deployment process without making changes.',\n default: false,\n },\n {\n name: '--railway-token <token>',\n description: 'Railway Project Token (optional if already logged in).',\n },\n {\n name: '--railway-name <name>',\n description: 'Railway Project Name (used during initialization).',\n },\n {\n name: '--backend-name <name>',\n description: 'Backend service name on Railway.',\n default: 'nexical-backend',\n },\n {\n name: '--frontend-name <name>',\n description: 'Frontend project name on Cloudflare.',\n default: 'nexical-frontend',\n },\n {\n name: '--cloudflare-token <token>',\n description: 'Cloudflare API Token.',\n },\n {\n name: '--cloudflare-account <id>',\n description: 'Cloudflare Account ID.',\n },\n ],\n };\n\n async run(options: DeployOptions) {\n this.info('Starting Nexical Deployment Automation...');\n\n if (options.dryRun) {\n this.notice('DRY RUN MODE ENABLED');\n }\n\n try {\n // 1. Railway Setup\n await this.setupRailway(options);\n\n // 2. Cloudflare Setup\n await this.setupCloudflare(options);\n\n // 3. GitHub Configuration (Secrets & Variables)\n await this.setupGitHubConfig(options);\n\n this.success('Deployment setup complete! Your application is being deployed.');\n } catch (error: unknown) {\n if (error instanceof Error) {\n this.error(`Deployment failed: ${error.message}`);\n } else {\n this.error(`Deployment failed: ${String(error)}`);\n }\n process.exit(1);\n }\n }\n\n private async setupRailway(options: DeployOptions) {\n this.info('Configuring Railway...');\n\n if (options.dryRun) {\n const initCmd = options.railwayName\n ? `railway init --name ${options.railwayName}`\n : 'railway init';\n this.info(`[Dry Run] Would run: ${initCmd}`);\n this.info('[Dry Run] Would run: railway add --database postgres');\n return;\n }\n\n try {\n // Check if railway project exists or init\n // Note: railway init might be interactive, so we might need to handle that or assume user has linked.\n // For now, let's assume we use 'railway link' if they passed a token or have it set.\n\n this.info('Ensuring Railway project is linked...');\n // Note: We intentionally DO NOT set process.env.RAILWAY_TOKEN here.\n // Management commands (init, status, add) require an interactive session (railway login).\n // The provided --railway-token is reserved for GitHub Secrets setup (CI/CD).\n\n const backendDir = path.join(process.cwd(), 'apps/backend');\n\n try {\n await execAsync('railway status', { cwd: backendDir });\n } catch {\n const initCmd = options.railwayName\n ? `railway init --name ${options.railwayName}`\n : 'railway init';\n this.info(`No Railway project detected in apps/backend. Initializing with: ${initCmd}`);\n await execAsync(initCmd, { cwd: backendDir });\n }\n\n this.info(`Adding PostgreSQL service if missing for \"${options.backendName}\"...`);\n const { stdout: status } = await execAsync('railway status', { cwd: backendDir });\n if (!status.includes('postgres')) {\n await execAsync('railway add --database postgres', { cwd: backendDir });\n }\n } catch (e: unknown) {\n this.warn(\n 'Railway setup encountered an issue. Ensure you are logged in with `railway login`.',\n );\n throw e;\n }\n }\n\n private async setupCloudflare(options: DeployOptions) {\n this.info('Configuring Cloudflare Pages...');\n\n if (options.dryRun) {\n this.info(`[Dry Run] Would run: wrangler pages project create ${options.frontendName}`);\n return;\n }\n\n if (!options.cloudflareToken || !options.cloudflareAccount) {\n this.warn('Cloudflare credentials missing. Skipping automated Cloudflare setup.');\n this.info('You can manually set up Cloudflare Pages and add the secrets to GitHub.');\n return;\n }\n\n try {\n // Use wrangler to create project if it doesn't exist\n const projectName = options.frontendName;\n this.info(`Ensuring Cloudflare Pages project \"${projectName}\" exists...`);\n\n try {\n await execAsync(`wrangler pages project create ${projectName} --production-branch main`, {\n env: {\n ...process.env,\n CLOUDFLARE_API_TOKEN: options.cloudflareToken,\n CLOUDFLARE_ACCOUNT_ID: options.cloudflareAccount,\n },\n });\n } catch {\n this.info('Cloudflare project might already exist.');\n }\n } catch (e: unknown) {\n this.warn('Cloudflare setup failed.');\n throw e;\n }\n }\n\n private async setupGitHubConfig(options: DeployOptions) {\n this.info('Configuring GitHub Secrets and Variables...');\n\n if (options.dryRun) {\n this.info('[Dry Run] Would run: gh secret set RAILWAY_TOKEN');\n this.info('[Dry Run] Would run: gh secret set CLOUDFLARE_API_TOKEN');\n this.info('[Dry Run] Would run: gh secret set CLOUDFLARE_ACCOUNT_ID');\n this.info(\n `[Dry Run] Would run: gh variable set RAILWAY_SERVICE_NAME --body \"${options.backendName}\"`,\n );\n this.info(\n `[Dry Run] Would run: gh variable set CLOUDFLARE_PROJECT_NAME --body \"${options.frontendName}\"`,\n );\n return;\n }\n\n try {\n if (options.railwayToken) {\n this.info('Setting RAILWAY_TOKEN in GitHub...');\n await execAsync(`gh secret set RAILWAY_TOKEN --body \"${options.railwayToken}\"`);\n }\n\n if (options.cloudflareToken) {\n this.info('Setting CLOUDFLARE_API_TOKEN in GitHub...');\n await execAsync(`gh secret set CLOUDFLARE_API_TOKEN --body \"${options.cloudflareToken}\"`);\n }\n\n if (options.cloudflareAccount) {\n this.info('Setting CLOUDFLARE_ACCOUNT_ID in GitHub...');\n await execAsync(\n `gh secret set CLOUDFLARE_ACCOUNT_ID --body \"${options.cloudflareAccount}\"`,\n );\n }\n\n // Set variables\n this.info(`Setting RAILWAY_SERVICE_NAME to \"${options.backendName}\" in GitHub...`);\n await execAsync(`gh variable set RAILWAY_SERVICE_NAME --body \"${options.backendName}\"`);\n\n this.info(`Setting CLOUDFLARE_PROJECT_NAME to \"${options.frontendName}\" in GitHub...`);\n await execAsync(`gh variable set CLOUDFLARE_PROJECT_NAME --body \"${options.frontendName}\"`);\n } catch (e: unknown) {\n this.warn(\n 'GitHub configuration failed. Ensure you have the GitHub CLI (gh) installed and are logged in.',\n );\n throw e;\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/deploy.ts"],"sourcesContent":["import path from 'node:path';\nimport dotenv from 'dotenv';\nimport { BaseCommand } from '@nexical/cli-core';\nimport { ConfigManager } from '../deploy/config-manager';\nimport { ProviderRegistry } from '../deploy/registry';\nimport { DeploymentContext } from '../deploy/types';\n\nexport default class DeployCommand extends BaseCommand {\n static description = `Deploy the application based on nexical.yaml configuration.\n\nThis command orchestrates the deployment of your frontend and backend applications \nby interacting with the providers specified in your configuration file.\n\nCONFIGURATION:\n- Requires a 'nexical.yaml' file in the project root.\n- If the file or specific sections are missing, the CLI will prompt you to run an interactive setup \n and save the configuration for future uses.\n- Supports loading environment variables from a .env file in the project root.\n\nPROVIDERS:\n- Backend: Railway, etc.\n- Frontend: Cloudflare Pages, etc.\n- Repository: GitHub, GitLab, etc.\n\nPROCESS:\n1. Loads environment variables from '.env'.\n2. Loads configuration from 'nexical.yaml'.\n3. Provisions resources via the selected providers.\n4. Configures the repository (secrets/variables) for CI/CD.\n5. Generates CI/CD workflow files.`;\n\n static args = {\n options: [\n {\n name: '--backend <provider>',\n description: 'Override backend provider',\n },\n {\n name: '--frontend <provider>',\n description: 'Override frontend provider',\n },\n {\n name: '--repo <provider>',\n description: 'Override repositroy provider',\n },\n {\n name: '--dry-run',\n description: 'Simulate the deployment process',\n default: false,\n },\n ],\n };\n\n async run(options: Record<string, unknown>) {\n this.info('Starting Nexical Deployment...');\n\n // Load environment variables from .env\n dotenv.config({ path: path.join(process.cwd(), '.env') });\n\n const configManager = new ConfigManager(process.cwd());\n const config = await configManager.load();\n const registry = new ProviderRegistry();\n\n // Register core and local providers\n await registry.loadCoreProviders();\n await registry.loadLocalProviders(process.cwd());\n\n // Resolve providers (CLI flags > Config > Error)\n const backendProviderName =\n (options.backend as string | undefined) || config.deploy?.backend?.provider;\n if (!backendProviderName) {\n this.error(\n \"Backend provider not specified. Use --backend flag or configure 'deploy.backend.provider' in nexical.yaml.\",\n );\n }\n\n const frontendProviderName =\n (options.frontend as string | undefined) || config.deploy?.frontend?.provider;\n if (!frontendProviderName) {\n this.error(\n \"Frontend provider not specified. Use --frontend flag or configure 'deploy.frontend.provider' in nexical.yaml.\",\n );\n }\n\n const repoProviderName =\n (options.repo as string | undefined) || config.deploy?.repository?.provider;\n if (!repoProviderName) {\n this.error(\n \"Repository provider not specified. Use --repo flag or configure 'deploy.repository.provider' in nexical.yaml.\",\n );\n }\n\n const backendProvider = registry.getDeploymentProvider(backendProviderName!);\n const frontendProvider = registry.getDeploymentProvider(frontendProviderName!);\n const repoProvider = registry.getRepositoryProvider(repoProviderName!);\n\n if (!backendProvider) throw new Error(`Backend provider '${backendProviderName}' not found.`);\n if (!frontendProvider)\n throw new Error(`Frontend provider '${frontendProviderName}' not found.`);\n if (!repoProvider) throw new Error(`Repository provider '${repoProviderName}' not found.`);\n\n const context: DeploymentContext = {\n cwd: process.cwd(),\n config,\n options,\n };\n\n // Provision\n this.info(`Provisioning Backend with ${backendProvider.name}...`);\n await backendProvider.provision(context);\n\n this.info(`Provisioning Frontend with ${frontendProvider.name}...`);\n await frontendProvider.provision(context);\n\n // Configure Repo\n this.info(`Configuring Repository with ${repoProvider.name}...`);\n\n const secrets: Record<string, string> = {};\n\n // Collect secrets from Backend Provider\n this.info(`Resolving secrets from ${backendProvider.name}...`);\n try {\n const backendSecrets = await backendProvider.getSecrets(context);\n Object.assign(secrets, backendSecrets);\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : String(e);\n this.error(`Failed to resolve secrets for ${backendProvider.name}: ${message}`);\n }\n\n // Collect secrets from Frontend Provider\n this.info(`Resolving secrets from ${frontendProvider.name}...`);\n try {\n const frontendSecrets = await frontendProvider.getSecrets(context);\n Object.assign(secrets, frontendSecrets);\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : String(e);\n this.error(`Failed to resolve secrets for ${frontendProvider.name}: ${message}`);\n }\n\n await repoProvider.configureSecrets(context, secrets);\n\n const variables: Record<string, string> = {};\n\n // Collect variables from Backend Provider\n try {\n const backendVars = await backendProvider.getVariables(context);\n Object.assign(variables, backendVars);\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : String(e);\n this.error(`Failed to resolve variables for ${backendProvider.name}: ${message}`);\n }\n\n // Collect variables from Frontend Provider\n try {\n const frontendVars = await frontendProvider.getVariables(context);\n Object.assign(variables, frontendVars);\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : String(e);\n this.error(`Failed to resolve variables for ${frontendProvider.name}: ${message}`);\n }\n\n await repoProvider.configureVariables(context, variables);\n\n // Generate Workflows\n this.info('Generating CI/CD Workflows...');\n await repoProvider.generateWorkflow(context, [backendProvider, frontendProvider]);\n\n this.success('Deployment configuration complete!');\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { createRequire } from "module"; const require = createRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
require_lib
|
|
4
|
-
} from "../../chunk-LZ3YQWAR.js";
|
|
5
2
|
import {
|
|
6
3
|
addAll,
|
|
7
4
|
clone,
|
|
@@ -12,6 +9,9 @@ import {
|
|
|
12
9
|
import {
|
|
13
10
|
resolveGitUrl
|
|
14
11
|
} from "../../chunk-PJIOCW2A.js";
|
|
12
|
+
import {
|
|
13
|
+
require_lib
|
|
14
|
+
} from "../../chunk-LZ3YQWAR.js";
|
|
15
15
|
import {
|
|
16
16
|
__toESM,
|
|
17
17
|
init_esm_shims
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { createRequire } from "module"; const require = createRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
require_lib
|
|
4
|
-
} from "../../../chunk-LZ3YQWAR.js";
|
|
5
2
|
import {
|
|
6
3
|
clone,
|
|
7
4
|
getRemoteUrl
|
|
@@ -9,6 +6,9 @@ import {
|
|
|
9
6
|
import {
|
|
10
7
|
resolveGitUrl
|
|
11
8
|
} from "../../../chunk-PJIOCW2A.js";
|
|
9
|
+
import {
|
|
10
|
+
require_lib
|
|
11
|
+
} from "../../../chunk-LZ3YQWAR.js";
|
|
12
12
|
import {
|
|
13
13
|
__toESM,
|
|
14
14
|
init_esm_shims
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NexicalConfig } from './types.js';
|
|
2
|
+
|
|
3
|
+
declare class ConfigManager {
|
|
4
|
+
private configPath;
|
|
5
|
+
constructor(cwd: string);
|
|
6
|
+
load(): Promise<NexicalConfig>;
|
|
7
|
+
save(config: NexicalConfig): Promise<void>;
|
|
8
|
+
exists(): Promise<boolean>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export { ConfigManager };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { DeploymentProvider, DeploymentContext, CIConfig } from '../types.js';
|
|
2
|
+
|
|
3
|
+
declare class CloudflareProvider implements DeploymentProvider {
|
|
4
|
+
name: string;
|
|
5
|
+
type: "frontend";
|
|
6
|
+
provision(context: DeploymentContext): Promise<void>;
|
|
7
|
+
getSecrets(context: DeploymentContext): Promise<Record<string, string>>;
|
|
8
|
+
getVariables(context: DeploymentContext): Promise<Record<string, string>>;
|
|
9
|
+
getCIConfig(): CIConfig;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { CloudflareProvider };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { createRequire } from "module"; const require = createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
execAsync
|
|
4
|
+
} from "../../../chunk-2JW5BYZW.js";
|
|
5
|
+
import {
|
|
6
|
+
init_esm_shims
|
|
7
|
+
} from "../../../chunk-OYFWMYPG.js";
|
|
8
|
+
|
|
9
|
+
// src/deploy/providers/cloudflare.ts
|
|
10
|
+
init_esm_shims();
|
|
11
|
+
import { logger } from "@nexical/cli-core";
|
|
12
|
+
var CloudflareProvider = class {
|
|
13
|
+
name = "cloudflare";
|
|
14
|
+
type = "frontend";
|
|
15
|
+
async provision(context) {
|
|
16
|
+
const projectName = context.config.deploy?.frontend?.projectName;
|
|
17
|
+
if (!projectName) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
"Cloudflare project name not found in nexical.yaml. Please configure 'deploy.frontend.projectName'."
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
const options = context.config.deploy?.frontend?.options || {};
|
|
23
|
+
const apiTokenEnvVar = typeof options.apiTokenEnvVar === "string" ? options.apiTokenEnvVar : void 0;
|
|
24
|
+
const apiToken = (typeof context.options.cloudflareToken === "string" ? context.options.cloudflareToken : void 0) || (apiTokenEnvVar ? process.env[apiTokenEnvVar] : void 0) || process.env.CLOUDFLARE_API_TOKEN;
|
|
25
|
+
const accountIdEnvVar = typeof options.accountIdEnvVar === "string" ? options.accountIdEnvVar : void 0;
|
|
26
|
+
const accountId = (typeof context.options.cloudflareAccount === "string" ? context.options.cloudflareAccount : void 0) || (accountIdEnvVar ? process.env[accountIdEnvVar] : void 0) || process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
27
|
+
logger.info("Configuring Cloudflare Pages...");
|
|
28
|
+
if (context.options.dryRun) {
|
|
29
|
+
logger.info("[Dry Run] Would check Cloudflare Pages project and create if missing.");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!apiToken || !accountId) {
|
|
33
|
+
logger.warn("Cloudflare credentials missing. Skipping automated Cloudflare setup.");
|
|
34
|
+
logger.info("You can manually set up Cloudflare Pages and add the secrets to GitHub.");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
logger.info(`Ensuring Cloudflare Pages project "${projectName}" exists...`);
|
|
39
|
+
try {
|
|
40
|
+
await execAsync(`wrangler pages project create ${projectName} --production-branch main`, {
|
|
41
|
+
env: {
|
|
42
|
+
...process.env,
|
|
43
|
+
CLOUDFLARE_API_TOKEN: apiToken,
|
|
44
|
+
CLOUDFLARE_ACCOUNT_ID: accountId
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
} catch {
|
|
48
|
+
logger.info("Cloudflare project might already exist.");
|
|
49
|
+
}
|
|
50
|
+
} catch (e) {
|
|
51
|
+
logger.warn("Cloudflare setup failed.");
|
|
52
|
+
throw e;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async getSecrets(context) {
|
|
56
|
+
const options = context.config.deploy?.frontend?.options || {};
|
|
57
|
+
const secrets = {};
|
|
58
|
+
const apiTokenEnvVar = typeof options.apiTokenEnvVar === "string" ? options.apiTokenEnvVar : void 0;
|
|
59
|
+
const apiToken = (apiTokenEnvVar ? process.env[apiTokenEnvVar] : void 0) || process.env.CLOUDFLARE_API_TOKEN;
|
|
60
|
+
if (!apiToken) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Cloudflare API Token not found. Please provide it via:
|
|
63
|
+
1. Configuring 'deploy.frontend.options.apiTokenEnvVar' in nexical.yaml and setting that env var in .env
|
|
64
|
+
2. Setting CLOUDFLARE_API_TOKEN in .env`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
secrets["CLOUDFLARE_API_TOKEN"] = apiToken;
|
|
68
|
+
const accountIdEnvVar = typeof options.accountIdEnvVar === "string" ? options.accountIdEnvVar : void 0;
|
|
69
|
+
const accountId = (accountIdEnvVar ? process.env[accountIdEnvVar] : void 0) || process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
70
|
+
if (!accountId) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Cloudflare Account ID not found. Please provide it via:
|
|
73
|
+
1. Configuring 'deploy.frontend.options.accountIdEnvVar' in nexical.yaml and setting that env var in .env
|
|
74
|
+
2. Setting CLOUDFLARE_ACCOUNT_ID in .env`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
secrets["CLOUDFLARE_ACCOUNT_ID"] = accountId;
|
|
78
|
+
return secrets;
|
|
79
|
+
}
|
|
80
|
+
async getVariables(context) {
|
|
81
|
+
const projectName = context.config.deploy?.frontend?.projectName;
|
|
82
|
+
if (!projectName) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
"Cloudflare project name not found in nexical.yaml. Please configure 'deploy.frontend.projectName'."
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
CLOUDFLARE_PROJECT_NAME: projectName
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
getCIConfig() {
|
|
92
|
+
return {
|
|
93
|
+
secrets: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"],
|
|
94
|
+
variables: ["CLOUDFLARE_PROJECT_NAME"],
|
|
95
|
+
deploySteps: [],
|
|
96
|
+
// Handled by action
|
|
97
|
+
githubActionStep: {
|
|
98
|
+
name: "Deploy to Cloudflare Pages",
|
|
99
|
+
uses: "cloudflare/wrangler-action@v3",
|
|
100
|
+
with: {
|
|
101
|
+
apiToken: "${{ secrets.CLOUDFLARE_API_TOKEN }}",
|
|
102
|
+
accountId: "${{ secrets.CLOUDFLARE_ACCOUNT_ID }}",
|
|
103
|
+
command: "pages deploy dist --project-name=${{ vars.CLOUDFLARE_PROJECT_NAME }}",
|
|
104
|
+
workingDirectory: "apps/frontend"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
export {
|
|
111
|
+
CloudflareProvider
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/deploy/providers/cloudflare.ts"],"sourcesContent":["import { logger } from '@nexical/cli-core';\nimport { DeploymentProvider, DeploymentContext, CIConfig } from '../types';\nimport { execAsync } from '../utils';\n\nexport class CloudflareProvider implements DeploymentProvider {\n name = 'cloudflare';\n type = 'frontend' as const;\n\n async provision(context: DeploymentContext): Promise<void> {\n const projectName = context.config.deploy?.frontend?.projectName;\n\n if (!projectName) {\n throw new Error(\n \"Cloudflare project name not found in nexical.yaml. Please configure 'deploy.frontend.projectName'.\",\n );\n }\n\n const options = context.config.deploy?.frontend?.options || {};\n\n // Resolve credentials:\n // 1. CLI flag (options)\n // 2. Env var defined in config (options.apiTokenEnvVar)\n // 3. Default env var (CLOUDFLARE_API_TOKEN)\n const apiTokenEnvVar =\n typeof options.apiTokenEnvVar === 'string' ? options.apiTokenEnvVar : undefined;\n const apiToken =\n (typeof context.options.cloudflareToken === 'string'\n ? context.options.cloudflareToken\n : undefined) ||\n (apiTokenEnvVar ? process.env[apiTokenEnvVar] : undefined) ||\n process.env.CLOUDFLARE_API_TOKEN;\n\n const accountIdEnvVar =\n typeof options.accountIdEnvVar === 'string' ? options.accountIdEnvVar : undefined;\n const accountId =\n (typeof context.options.cloudflareAccount === 'string'\n ? context.options.cloudflareAccount\n : undefined) ||\n (accountIdEnvVar ? process.env[accountIdEnvVar] : undefined) ||\n process.env.CLOUDFLARE_ACCOUNT_ID;\n\n logger.info('Configuring Cloudflare Pages...');\n\n if (context.options.dryRun) {\n logger.info('[Dry Run] Would check Cloudflare Pages project and create if missing.');\n return;\n }\n\n if (!apiToken || !accountId) {\n logger.warn('Cloudflare credentials missing. Skipping automated Cloudflare setup.');\n logger.info('You can manually set up Cloudflare Pages and add the secrets to GitHub.');\n return;\n }\n\n try {\n logger.info(`Ensuring Cloudflare Pages project \"${projectName}\" exists...`);\n try {\n await execAsync(`wrangler pages project create ${projectName} --production-branch main`, {\n env: {\n ...process.env,\n CLOUDFLARE_API_TOKEN: apiToken,\n CLOUDFLARE_ACCOUNT_ID: accountId,\n },\n });\n } catch {\n logger.info('Cloudflare project might already exist.');\n }\n } catch (e: unknown) {\n logger.warn('Cloudflare setup failed.');\n throw e;\n }\n }\n\n async getSecrets(context: DeploymentContext): Promise<Record<string, string>> {\n const options = context.config.deploy?.frontend?.options || {};\n const secrets: Record<string, string> = {};\n\n // Resolve API Token\n const apiTokenEnvVar =\n typeof options.apiTokenEnvVar === 'string' ? options.apiTokenEnvVar : undefined;\n const apiToken =\n (apiTokenEnvVar ? process.env[apiTokenEnvVar] : undefined) ||\n process.env.CLOUDFLARE_API_TOKEN;\n\n if (!apiToken) {\n throw new Error(\n `Cloudflare API Token not found. Please provide it via:\\n` +\n `1. Configuring 'deploy.frontend.options.apiTokenEnvVar' in nexical.yaml and setting that env var in .env\\n` +\n `2. Setting CLOUDFLARE_API_TOKEN in .env`,\n );\n }\n secrets['CLOUDFLARE_API_TOKEN'] = apiToken;\n\n // Resolve Account ID\n const accountIdEnvVar =\n typeof options.accountIdEnvVar === 'string' ? options.accountIdEnvVar : undefined;\n const accountId =\n (accountIdEnvVar ? process.env[accountIdEnvVar] : undefined) ||\n process.env.CLOUDFLARE_ACCOUNT_ID;\n\n if (!accountId) {\n throw new Error(\n `Cloudflare Account ID not found. Please provide it via:\\n` +\n `1. Configuring 'deploy.frontend.options.accountIdEnvVar' in nexical.yaml and setting that env var in .env\\n` +\n `2. Setting CLOUDFLARE_ACCOUNT_ID in .env`,\n );\n }\n secrets['CLOUDFLARE_ACCOUNT_ID'] = accountId;\n\n return secrets;\n }\n\n async getVariables(context: DeploymentContext): Promise<Record<string, string>> {\n const projectName = context.config.deploy?.frontend?.projectName;\n\n if (!projectName) {\n throw new Error(\n \"Cloudflare project name not found in nexical.yaml. Please configure 'deploy.frontend.projectName'.\",\n );\n }\n return {\n CLOUDFLARE_PROJECT_NAME: projectName,\n };\n }\n\n getCIConfig(): CIConfig {\n return {\n secrets: ['CLOUDFLARE_API_TOKEN', 'CLOUDFLARE_ACCOUNT_ID'],\n variables: ['CLOUDFLARE_PROJECT_NAME'],\n deploySteps: [], // Handled by action\n githubActionStep: {\n name: 'Deploy to Cloudflare Pages',\n uses: 'cloudflare/wrangler-action@v3',\n with: {\n apiToken: '${{ secrets.CLOUDFLARE_API_TOKEN }}',\n accountId: '${{ secrets.CLOUDFLARE_ACCOUNT_ID }}',\n command: 'pages deploy dist --project-name=${{ vars.CLOUDFLARE_PROJECT_NAME }}',\n workingDirectory: 'apps/frontend',\n },\n },\n };\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RepositoryProvider, DeploymentContext, DeploymentProvider } from '../types.js';
|
|
2
|
+
|
|
3
|
+
declare class GitHubProvider implements RepositoryProvider {
|
|
4
|
+
name: string;
|
|
5
|
+
configureSecrets(context: DeploymentContext, secrets: Record<string, string>): Promise<void>;
|
|
6
|
+
configureVariables(context: DeploymentContext, variables: Record<string, string>): Promise<void>;
|
|
7
|
+
generateWorkflow(context: DeploymentContext, targets: DeploymentProvider[]): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { GitHubProvider };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createRequire } from "module"; const require = createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
execAsync
|
|
4
|
+
} from "../../../chunk-2JW5BYZW.js";
|
|
5
|
+
import {
|
|
6
|
+
init_esm_shims
|
|
7
|
+
} from "../../../chunk-OYFWMYPG.js";
|
|
8
|
+
|
|
9
|
+
// src/deploy/providers/github.ts
|
|
10
|
+
init_esm_shims();
|
|
11
|
+
import fs from "fs/promises";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import YAML from "yaml";
|
|
14
|
+
import { logger } from "@nexical/cli-core";
|
|
15
|
+
var GitHubProvider = class {
|
|
16
|
+
name = "github";
|
|
17
|
+
async configureSecrets(context, secrets) {
|
|
18
|
+
for (const [key, value] of Object.entries(secrets)) {
|
|
19
|
+
if (!value) continue;
|
|
20
|
+
logger.info(`Setting secret ${key} in GitHub...`);
|
|
21
|
+
if (context.options.dryRun) {
|
|
22
|
+
logger.info(`[Dry Run] Would set secret ${key}`);
|
|
23
|
+
} else {
|
|
24
|
+
await execAsync(`gh secret set ${key} --body "${value}"`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async configureVariables(context, variables) {
|
|
29
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
30
|
+
if (!value) continue;
|
|
31
|
+
logger.info(`Setting variable ${key} in GitHub...`);
|
|
32
|
+
if (context.options.dryRun) {
|
|
33
|
+
logger.info(`[Dry Run] Would set variable ${key}`);
|
|
34
|
+
} else {
|
|
35
|
+
await execAsync(`gh variable set ${key} --body "${value}"`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async generateWorkflow(context, targets) {
|
|
40
|
+
const workflowsDir = path.join(context.cwd, ".github/workflows");
|
|
41
|
+
await fs.mkdir(workflowsDir, { recursive: true });
|
|
42
|
+
for (const target of targets) {
|
|
43
|
+
const config = target.getCIConfig("github");
|
|
44
|
+
if (!config) continue;
|
|
45
|
+
const filename = `deploy-${target.type}.yml`;
|
|
46
|
+
const filepath = path.join(workflowsDir, filename);
|
|
47
|
+
const workflow = {
|
|
48
|
+
name: `Deploy ${target.type === "backend" ? "Backend" : "Frontend"} to ${target.name}`,
|
|
49
|
+
on: {
|
|
50
|
+
push: { branches: ["main"] },
|
|
51
|
+
workflow_dispatch: {}
|
|
52
|
+
},
|
|
53
|
+
jobs: {
|
|
54
|
+
deploy: {
|
|
55
|
+
"runs-on": "ubuntu-latest",
|
|
56
|
+
permissions: {
|
|
57
|
+
contents: "read",
|
|
58
|
+
deployments: "write"
|
|
59
|
+
},
|
|
60
|
+
steps: [
|
|
61
|
+
{
|
|
62
|
+
name: "Checkout",
|
|
63
|
+
uses: "actions/checkout@v4",
|
|
64
|
+
with: { submodules: "recursive" }
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "Setup Node",
|
|
68
|
+
uses: "actions/setup-node@v4",
|
|
69
|
+
with: { "node-version": 20, cache: "npm" }
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "Install Dependencies",
|
|
73
|
+
run: "npm ci"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const steps = workflow.jobs.deploy.steps;
|
|
80
|
+
if (target.type === "frontend") {
|
|
81
|
+
steps.push({
|
|
82
|
+
name: "Build Frontend",
|
|
83
|
+
run: "npm run build --workspace=@app/frontend"
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (config.installSteps) {
|
|
87
|
+
for (const step of config.installSteps) {
|
|
88
|
+
steps.push({
|
|
89
|
+
name: `Install ${target.name} CLI`,
|
|
90
|
+
run: step
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (config.deploySteps) {
|
|
95
|
+
for (const step of config.deploySteps) {
|
|
96
|
+
const deployStep = {
|
|
97
|
+
name: `Deploy to ${target.name}`,
|
|
98
|
+
run: step,
|
|
99
|
+
"working-directory": target.type === "backend" ? "apps/backend" : "apps/frontend"
|
|
100
|
+
};
|
|
101
|
+
if (config.secrets && config.secrets.length > 0) {
|
|
102
|
+
deployStep.env = config.secrets.reduce((acc, secret) => {
|
|
103
|
+
acc[secret] = `\${{ secrets.${secret} }}`;
|
|
104
|
+
return acc;
|
|
105
|
+
}, {});
|
|
106
|
+
}
|
|
107
|
+
steps.push(deployStep);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (config.githubActionStep) {
|
|
111
|
+
steps.push(config.githubActionStep);
|
|
112
|
+
}
|
|
113
|
+
await fs.writeFile(filepath, YAML.stringify(workflow), "utf-8");
|
|
114
|
+
logger.info(`Generated workflow: ${filepath}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
export {
|
|
119
|
+
GitHubProvider
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/deploy/providers/github.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport YAML from 'yaml';\nimport { logger } from '@nexical/cli-core';\nimport { RepositoryProvider, DeploymentContext, DeploymentProvider } from '../types';\nimport { execAsync } from '../utils';\n\nexport class GitHubProvider implements RepositoryProvider {\n name = 'github';\n\n async configureSecrets(\n context: DeploymentContext,\n secrets: Record<string, string>,\n ): Promise<void> {\n for (const [key, value] of Object.entries(secrets)) {\n if (!value) continue;\n logger.info(`Setting secret ${key} in GitHub...`);\n if (context.options.dryRun) {\n logger.info(`[Dry Run] Would set secret ${key}`);\n } else {\n await execAsync(`gh secret set ${key} --body \"${value}\"`);\n }\n }\n }\n\n async configureVariables(\n context: DeploymentContext,\n variables: Record<string, string>,\n ): Promise<void> {\n for (const [key, value] of Object.entries(variables)) {\n if (!value) continue;\n logger.info(`Setting variable ${key} in GitHub...`);\n if (context.options.dryRun) {\n logger.info(`[Dry Run] Would set variable ${key}`);\n } else {\n await execAsync(`gh variable set ${key} --body \"${value}\"`);\n }\n }\n }\n\n async generateWorkflow(context: DeploymentContext, targets: DeploymentProvider[]): Promise<void> {\n const workflowsDir = path.join(context.cwd, '.github/workflows');\n await fs.mkdir(workflowsDir, { recursive: true });\n\n for (const target of targets) {\n const config = target.getCIConfig('github');\n if (!config) continue;\n\n const filename = `deploy-${target.type}.yml`;\n const filepath = path.join(workflowsDir, filename);\n\n const workflow: Record<string, unknown> = {\n name: `Deploy ${target.type === 'backend' ? 'Backend' : 'Frontend'} to ${target.name}`,\n on: {\n push: { branches: ['main'] },\n workflow_dispatch: {},\n },\n jobs: {\n deploy: {\n 'runs-on': 'ubuntu-latest',\n permissions: {\n contents: 'read',\n deployments: 'write',\n },\n steps: [\n {\n name: 'Checkout',\n uses: 'actions/checkout@v4',\n with: { submodules: 'recursive' },\n },\n {\n name: 'Setup Node',\n uses: 'actions/setup-node@v4',\n with: { 'node-version': 20, cache: 'npm' },\n },\n {\n name: 'Install Dependencies',\n run: 'npm ci',\n },\n ],\n },\n },\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const steps = (workflow as any).jobs.deploy.steps;\n\n // Build (if frontend)\n if (target.type === 'frontend') {\n steps.push({\n name: 'Build Frontend',\n run: 'npm run build --workspace=@app/frontend',\n });\n }\n\n // Provider Install Steps\n if (config.installSteps) {\n for (const step of config.installSteps) {\n steps.push({\n name: `Install ${target.name} CLI`,\n run: step,\n });\n }\n }\n\n // Provider Deploy Steps\n if (config.deploySteps) {\n for (const step of config.deploySteps) {\n const deployStep: Record<string, unknown> = {\n name: `Deploy to ${target.name}`,\n run: step,\n 'working-directory': target.type === 'backend' ? 'apps/backend' : 'apps/frontend',\n };\n\n if (config.secrets && config.secrets.length > 0) {\n deployStep.env = config.secrets.reduce((acc: Record<string, string>, secret) => {\n acc[secret] = `\\${{ secrets.${secret} }}`;\n return acc;\n }, {});\n }\n\n steps.push(deployStep);\n }\n }\n\n // Provider Action Step\n if (config.githubActionStep) {\n steps.push(config.githubActionStep);\n }\n\n await fs.writeFile(filepath, YAML.stringify(workflow), 'utf-8');\n logger.info(`Generated workflow: ${filepath}`);\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DeploymentProvider, DeploymentContext, CIConfig } from '../types.js';
|
|
2
|
+
|
|
3
|
+
declare class RailwayProvider implements DeploymentProvider {
|
|
4
|
+
name: string;
|
|
5
|
+
type: "backend";
|
|
6
|
+
provision(context: DeploymentContext): Promise<void>;
|
|
7
|
+
private resolveToken;
|
|
8
|
+
getSecrets(context: DeploymentContext): Promise<Record<string, string>>;
|
|
9
|
+
getVariables(context: DeploymentContext): Promise<Record<string, string>>;
|
|
10
|
+
getCIConfig(): CIConfig;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { RailwayProvider };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createRequire } from "module"; const require = createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
execAsync
|
|
4
|
+
} from "../../../chunk-2JW5BYZW.js";
|
|
5
|
+
import {
|
|
6
|
+
init_esm_shims
|
|
7
|
+
} from "../../../chunk-OYFWMYPG.js";
|
|
8
|
+
|
|
9
|
+
// src/deploy/providers/railway.ts
|
|
10
|
+
init_esm_shims();
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { logger } from "@nexical/cli-core";
|
|
13
|
+
var RailwayProvider = class {
|
|
14
|
+
name = "railway";
|
|
15
|
+
type = "backend";
|
|
16
|
+
async provision(context) {
|
|
17
|
+
const backendDir = path.join(context.cwd, "apps/backend");
|
|
18
|
+
const railwayName = context.config.deploy?.backend?.projectName;
|
|
19
|
+
if (!railwayName) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
"Railway project name not found in nexical.yaml. Please configure 'deploy.backend.projectName'."
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
logger.info("Configuring Railway...");
|
|
25
|
+
if (context.options.dryRun) {
|
|
26
|
+
logger.info("[Dry Run] Would check Railway status and init project.");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const env = { ...process.env };
|
|
31
|
+
delete env.RAILWAY_API_TOKEN;
|
|
32
|
+
delete env.RAILWAY_TOKEN;
|
|
33
|
+
logger.info("Using local Railway CLI credentials (environment variables stripped).");
|
|
34
|
+
try {
|
|
35
|
+
await execAsync("railway status", { cwd: backendDir, env });
|
|
36
|
+
} catch (error) {
|
|
37
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
38
|
+
const stderr = error.stderr || "";
|
|
39
|
+
const stdout = error.stdout || "";
|
|
40
|
+
const fullError = `${errMsg} ${stderr} ${stdout}`;
|
|
41
|
+
if (fullError.includes("Project not found") || fullError.includes("No project") || fullError.includes("Project is deleted")) {
|
|
42
|
+
if (fullError.includes("Project is deleted")) {
|
|
43
|
+
logger.info("[Railway] Project is deleted. Unlinking...");
|
|
44
|
+
await execAsync("railway unlink", { cwd: backendDir }).catch(() => {
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const initCmd = `railway init --name ${railwayName}`;
|
|
48
|
+
logger.info(`No active Railway project linked. Initializing with: ${initCmd}`);
|
|
49
|
+
await execAsync(initCmd, { cwd: backendDir, env });
|
|
50
|
+
} else if (fullError.includes("Invalid RAILWAY_API_TOKEN") || fullError.includes("Unauthorized")) {
|
|
51
|
+
throw new Error("Railway authentication failed during status check.");
|
|
52
|
+
} else {
|
|
53
|
+
logger.warn(`Railway status check failed: ${errMsg}. Proceeding.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
logger.info(`Adding PostgreSQL service if missing for "${railwayName}"...`);
|
|
57
|
+
const { stdout: status } = await execAsync("railway status", { cwd: backendDir, env }).catch(
|
|
58
|
+
() => ({ stdout: "" })
|
|
59
|
+
);
|
|
60
|
+
if (!status.includes("postgres")) {
|
|
61
|
+
try {
|
|
62
|
+
await execAsync("railway add --database postgres", { cwd: backendDir, env });
|
|
63
|
+
} catch {
|
|
64
|
+
logger.warn("Failed to auto-add PostgreSQL.");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
69
|
+
const stderr = e.stderr || "";
|
|
70
|
+
const stdout = e.stdout || "";
|
|
71
|
+
if (errMsg.includes("Railway authentication failed")) throw e;
|
|
72
|
+
logger.error(`Railway setup failed with error: ${errMsg}`);
|
|
73
|
+
if (stderr) logger.error(`[Railway stderr]: ${stderr}`);
|
|
74
|
+
if (stdout) logger.info(`[Railway stdout]: ${stdout}`);
|
|
75
|
+
logger.warn(
|
|
76
|
+
"Railway setup encountered an issue. Ensure you are logged in or have a valid token."
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
resolveToken(context) {
|
|
81
|
+
const options = context.config.deploy?.backend?.options || {};
|
|
82
|
+
const tokenEnvVar = typeof options.tokenEnvVar === "string" ? options.tokenEnvVar : void 0;
|
|
83
|
+
return process.env.RAILWAY_API_TOKEN?.trim() || (tokenEnvVar ? process.env[tokenEnvVar]?.trim() : void 0) || process.env.RAILWAY_TOKEN?.trim();
|
|
84
|
+
}
|
|
85
|
+
async getSecrets(context) {
|
|
86
|
+
const token = this.resolveToken(context);
|
|
87
|
+
const secrets = {};
|
|
88
|
+
if (!token) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Railway Token not found. Please provide it via:
|
|
91
|
+
1. Setting RAILWAY_API_TOKEN in .env (Recommended)
|
|
92
|
+
2. Configuring 'deploy.backend.options.tokenEnvVar' in nexical.yaml
|
|
93
|
+
3. Setting RAILWAY_TOKEN in .env`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
secrets["RAILWAY_API_TOKEN"] = token;
|
|
97
|
+
return secrets;
|
|
98
|
+
}
|
|
99
|
+
async getVariables(context) {
|
|
100
|
+
return {};
|
|
101
|
+
}
|
|
102
|
+
getCIConfig() {
|
|
103
|
+
return {
|
|
104
|
+
secrets: ["RAILWAY_API_TOKEN"],
|
|
105
|
+
variables: [],
|
|
106
|
+
installSteps: ["npm install -g @railway/cli"],
|
|
107
|
+
deploySteps: ["railway up --detach"]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
export {
|
|
112
|
+
RailwayProvider
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=railway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/deploy/providers/railway.ts"],"sourcesContent":["import path from 'node:path';\nimport { logger } from '@nexical/cli-core';\nimport { DeploymentProvider, DeploymentContext, CIConfig } from '../types';\nimport { execAsync } from '../utils';\n\nexport class RailwayProvider implements DeploymentProvider {\n name = 'railway';\n type = 'backend' as const;\n\n async provision(context: DeploymentContext): Promise<void> {\n const backendDir = path.join(context.cwd, 'apps/backend');\n const railwayName = context.config.deploy?.backend?.projectName;\n\n if (!railwayName) {\n throw new Error(\n \"Railway project name not found in nexical.yaml. Please configure 'deploy.backend.projectName'.\",\n );\n }\n\n logger.info('Configuring Railway...');\n\n if (context.options.dryRun) {\n logger.info('[Dry Run] Would check Railway status and init project.');\n return;\n }\n\n try {\n // We consciously DO NOT pass any RAILWAY_API_TOKEN to the subprocess.\n // The user may have an invalid token in their .env file (which process.env inherits).\n // We want to force the Railway CLI to use the locally logged-in user's credentials.\n const env = { ...process.env };\n delete env.RAILWAY_API_TOKEN;\n delete env.RAILWAY_TOKEN;\n\n logger.info('Using local Railway CLI credentials (environment variables stripped).');\n\n // Check status to see if we are linked to a project\n try {\n await execAsync('railway status', { cwd: backendDir, env });\n } catch (error: unknown) {\n const errMsg = error instanceof Error ? error.message : String(error);\n const stderr = (error as { stderr?: string }).stderr || '';\n const stdout = (error as { stdout?: string }).stdout || '';\n const fullError = `${errMsg} ${stderr} ${stdout}`;\n\n // If status fails, likely project doesn't exist locally or we aren't linked.\n if (\n fullError.includes('Project not found') ||\n fullError.includes('No project') ||\n fullError.includes('Project is deleted')\n ) {\n if (fullError.includes('Project is deleted')) {\n logger.info('[Railway] Project is deleted. Unlinking...');\n // If it's deleted, we might need to unlink first to clean up local config\n await execAsync('railway unlink', { cwd: backendDir }).catch(() => {});\n }\n const initCmd = `railway init --name ${railwayName}`;\n logger.info(`No active Railway project linked. Initializing with: ${initCmd}`);\n await execAsync(initCmd, { cwd: backendDir, env });\n } else if (\n fullError.includes('Invalid RAILWAY_API_TOKEN') ||\n fullError.includes('Unauthorized')\n ) {\n throw new Error('Railway authentication failed during status check.');\n } else {\n // Some other error (e.g. timeout), warn and try to proceed\n logger.warn(`Railway status check failed: ${errMsg}. Proceeding.`);\n }\n }\n\n logger.info(`Adding PostgreSQL service if missing for \"${railwayName}\"...`);\n const { stdout: status } = await execAsync('railway status', { cwd: backendDir, env }).catch(\n () => ({ stdout: '' }),\n );\n if (!status.includes('postgres')) {\n try {\n await execAsync('railway add --database postgres', { cwd: backendDir, env });\n } catch {\n logger.warn('Failed to auto-add PostgreSQL.');\n }\n }\n } catch (e: unknown) {\n // Rethrow explicit auth errors, otherwise warn\n const errMsg = e instanceof Error ? e.message : String(e);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const stderr = (e as any).stderr || '';\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const stdout = (e as any).stdout || '';\n\n if (errMsg.includes('Railway authentication failed')) throw e;\n\n logger.error(`Railway setup failed with error: ${errMsg}`);\n if (stderr) logger.error(`[Railway stderr]: ${stderr}`);\n if (stdout) logger.info(`[Railway stdout]: ${stdout}`);\n\n logger.warn(\n 'Railway setup encountered an issue. Ensure you are logged in or have a valid token.',\n );\n }\n }\n\n private resolveToken(context: DeploymentContext): string | undefined {\n const options = context.config.deploy?.backend?.options || {};\n const tokenEnvVar = typeof options.tokenEnvVar === 'string' ? options.tokenEnvVar : undefined;\n return (\n process.env.RAILWAY_API_TOKEN?.trim() ||\n (tokenEnvVar ? process.env[tokenEnvVar]?.trim() : undefined) ||\n process.env.RAILWAY_TOKEN?.trim()\n );\n }\n\n async getSecrets(context: DeploymentContext): Promise<Record<string, string>> {\n const token = this.resolveToken(context);\n const secrets: Record<string, string> = {};\n\n if (!token) {\n // Strict check: Error if token is missing\n throw new Error(\n `Railway Token not found. Please provide it via:\\n` +\n `1. Setting RAILWAY_API_TOKEN in .env (Recommended)\\n` +\n `2. Configuring 'deploy.backend.options.tokenEnvVar' in nexical.yaml\\n` +\n `3. Setting RAILWAY_TOKEN in .env`,\n );\n }\n\n secrets['RAILWAY_API_TOKEN'] = token;\n return secrets;\n }\n\n async getVariables(context: DeploymentContext): Promise<Record<string, string>> {\n return {};\n }\n\n getCIConfig(): CIConfig {\n return {\n secrets: ['RAILWAY_API_TOKEN'],\n variables: [],\n installSteps: ['npm install -g @railway/cli'],\n deploySteps: ['railway up --detach'],\n };\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DeploymentProvider, RepositoryProvider } from './types.js';
|
|
2
|
+
|
|
3
|
+
declare class ProviderRegistry {
|
|
4
|
+
private deploymentProviders;
|
|
5
|
+
private repositoryProviders;
|
|
6
|
+
registerDeploymentProvider(provider: DeploymentProvider): void;
|
|
7
|
+
registerRepositoryProvider(provider: RepositoryProvider): void;
|
|
8
|
+
getDeploymentProvider(name: string): DeploymentProvider | undefined;
|
|
9
|
+
getRepositoryProvider(name: string): RepositoryProvider | undefined;
|
|
10
|
+
private registerProviderFromModule;
|
|
11
|
+
loadCoreProviders(): Promise<void>;
|
|
12
|
+
loadLocalProviders(cwd: string): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { ProviderRegistry };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
interface CIConfig {
|
|
2
|
+
secrets: string[];
|
|
3
|
+
variables: string[];
|
|
4
|
+
installSteps?: string[];
|
|
5
|
+
buildSteps?: string[];
|
|
6
|
+
deploySteps?: string[];
|
|
7
|
+
githubActionStep?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
interface DeploymentContext {
|
|
10
|
+
cwd: string;
|
|
11
|
+
config: NexicalConfig;
|
|
12
|
+
options: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
interface DeploymentProvider {
|
|
15
|
+
name: string;
|
|
16
|
+
type: 'frontend' | 'backend';
|
|
17
|
+
provision(context: DeploymentContext): Promise<void>;
|
|
18
|
+
getCIConfig(repoType: 'github' | 'gitlab'): CIConfig;
|
|
19
|
+
getSecrets(context: DeploymentContext): Promise<Record<string, string>>;
|
|
20
|
+
getVariables(context: DeploymentContext): Promise<Record<string, string>>;
|
|
21
|
+
}
|
|
22
|
+
interface RepositoryProvider {
|
|
23
|
+
name: string;
|
|
24
|
+
configureSecrets(context: DeploymentContext, secrets: Record<string, string>): Promise<void>;
|
|
25
|
+
configureVariables(context: DeploymentContext, variables: Record<string, string>): Promise<void>;
|
|
26
|
+
generateWorkflow(context: DeploymentContext, targets: DeploymentProvider[]): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
interface NexicalConfig {
|
|
29
|
+
deploy?: {
|
|
30
|
+
backend?: {
|
|
31
|
+
provider: string;
|
|
32
|
+
projectName?: string;
|
|
33
|
+
options?: Record<string, unknown>;
|
|
34
|
+
};
|
|
35
|
+
frontend?: {
|
|
36
|
+
provider: string;
|
|
37
|
+
projectName?: string;
|
|
38
|
+
options?: Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
repository?: {
|
|
41
|
+
provider: string;
|
|
42
|
+
options?: Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type { CIConfig, DeploymentContext, DeploymentProvider, NexicalConfig, RepositoryProvider };
|