@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.
Files changed (91) hide show
  1. package/README.md +90 -235
  2. package/dist/{chunk-OYFWMYPG.js → chunk-6DE5Q66O.js} +6 -1
  3. package/dist/{chunk-OYFWMYPG.js.map → chunk-6DE5Q66O.js.map} +1 -1
  4. package/dist/chunk-G66GMEFE.js +31 -0
  5. package/dist/chunk-G66GMEFE.js.map +1 -0
  6. package/dist/{chunk-2FKDEDDE.js → chunk-HOVS7SCD.js} +16 -3
  7. package/dist/chunk-HOVS7SCD.js.map +1 -0
  8. package/dist/{chunk-GUUPSHWC.js → chunk-JEMIKBGX.js} +3 -3
  9. package/dist/chunk-JGAMEJTL.js +4101 -0
  10. package/dist/chunk-JGAMEJTL.js.map +1 -0
  11. package/dist/{chunk-OUGA4CB4.js → chunk-JS6WL5NS.js} +2 -2
  12. package/dist/{chunk-GEESHGE4.js → chunk-L2RUXOL4.js} +2 -2
  13. package/dist/{chunk-54HY52LH.js → chunk-QTJIGPQ3.js} +2 -2
  14. package/dist/{chunk-EKCOW7FM.js → chunk-USP2MI63.js} +41 -23
  15. package/dist/chunk-USP2MI63.js.map +1 -0
  16. package/dist/{chunk-2JW5BYZW.js → chunk-VKE7R2EZ.js} +2 -2
  17. package/dist/{chunk-AC4B3HPJ.js → chunk-XONR27KC.js} +2 -2
  18. package/dist/{chunk-PJIOCW2A.js → chunk-ZWNIZB3Q.js} +2 -2
  19. package/dist/index.js +5 -5
  20. package/dist/index.js.map +1 -1
  21. package/dist/src/commands/deploy.d.ts +3 -3
  22. package/dist/src/commands/deploy.js +148 -78
  23. package/dist/src/commands/deploy.js.map +1 -1
  24. package/dist/src/commands/init.js +5 -5
  25. package/dist/src/commands/module/add.js +4 -4
  26. package/dist/src/commands/module/list.js +2 -2
  27. package/dist/src/commands/module/remove.js +2 -2
  28. package/dist/src/commands/module/update.js +2 -2
  29. package/dist/src/commands/prompt.js +2 -2
  30. package/dist/src/commands/run.js +2 -2
  31. package/dist/src/commands/setup.js +3 -3
  32. package/dist/src/deploy/config-manager.js +3 -2
  33. package/dist/src/deploy/providers/cloudflare.d.ts +13 -8
  34. package/dist/src/deploy/providers/cloudflare.js +161 -52
  35. package/dist/src/deploy/providers/cloudflare.js.map +1 -1
  36. package/dist/src/deploy/providers/dns-cloudflare.d.ts +9 -0
  37. package/dist/src/deploy/providers/dns-cloudflare.js +123 -0
  38. package/dist/src/deploy/providers/dns-cloudflare.js.map +1 -0
  39. package/dist/src/deploy/providers/github.d.ts +6 -2
  40. package/dist/src/deploy/providers/github.js +37 -45
  41. package/dist/src/deploy/providers/github.js.map +1 -1
  42. package/dist/src/deploy/providers/railway.d.ts +17 -8
  43. package/dist/src/deploy/providers/railway.js +106 -45
  44. package/dist/src/deploy/providers/railway.js.map +1 -1
  45. package/dist/src/deploy/registry.d.ts +7 -4
  46. package/dist/src/deploy/registry.js +2 -2
  47. package/dist/src/deploy/schema.d.ts +188 -0
  48. package/dist/src/deploy/schema.js +11 -0
  49. package/dist/src/deploy/schema.js.map +1 -0
  50. package/dist/src/deploy/template-manager.d.ts +12 -0
  51. package/dist/src/deploy/template-manager.js +9 -0
  52. package/dist/src/deploy/template-manager.js.map +1 -0
  53. package/dist/src/deploy/types.d.ts +42 -17
  54. package/dist/src/deploy/types.js +1 -1
  55. package/dist/src/deploy/types.js.map +1 -1
  56. package/dist/src/deploy/utils.js +2 -2
  57. package/dist/src/utils/discovery.js +2 -2
  58. package/dist/src/utils/filter.js +2 -2
  59. package/dist/src/utils/git.js +2 -2
  60. package/dist/src/utils/url-resolver.js +2 -2
  61. package/dist/templates/github-workflow.yaml +23 -0
  62. package/package.json +2 -2
  63. package/src/commands/deploy.ts +169 -88
  64. package/src/deploy/config-manager.ts +14 -1
  65. package/src/deploy/providers/cloudflare.ts +203 -80
  66. package/src/deploy/providers/dns-cloudflare.ts +134 -0
  67. package/src/deploy/providers/github.ts +44 -47
  68. package/src/deploy/providers/railway.ts +135 -55
  69. package/src/deploy/registry.ts +49 -28
  70. package/src/deploy/schema.ts +39 -0
  71. package/src/deploy/template-manager.ts +32 -0
  72. package/src/deploy/templates/github-workflow.yaml +23 -0
  73. package/src/deploy/types.ts +48 -16
  74. package/test/integration/commands/deploy.integration.test.ts +79 -3
  75. package/test/unit/commands/deploy.test.ts +96 -198
  76. package/test/unit/deploy/config-manager.test.ts +9 -5
  77. package/test/unit/deploy/providers/cloudflare.test.ts +95 -96
  78. package/test/unit/deploy/providers/dns-cloudflare.test.ts +148 -0
  79. package/test/unit/deploy/providers/github.test.ts +43 -47
  80. package/test/unit/deploy/providers/railway.test.ts +50 -261
  81. package/test/unit/deploy/registry.test.ts +20 -17
  82. package/tsup.config.ts +3 -0
  83. package/dist/chunk-2FKDEDDE.js.map +0 -1
  84. package/dist/chunk-EKCOW7FM.js.map +0 -1
  85. /package/dist/{chunk-GUUPSHWC.js.map → chunk-JEMIKBGX.js.map} +0 -0
  86. /package/dist/{chunk-OUGA4CB4.js.map → chunk-JS6WL5NS.js.map} +0 -0
  87. /package/dist/{chunk-GEESHGE4.js.map → chunk-L2RUXOL4.js.map} +0 -0
  88. /package/dist/{chunk-54HY52LH.js.map → chunk-QTJIGPQ3.js.map} +0 -0
  89. /package/dist/{chunk-2JW5BYZW.js.map → chunk-VKE7R2EZ.js.map} +0 -0
  90. /package/dist/{chunk-AC4B3HPJ.js.map → chunk-XONR27KC.js.map} +0 -0
  91. /package/dist/{chunk-PJIOCW2A.js.map → chunk-ZWNIZB3Q.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/deploy/providers/dns-cloudflare.ts"],"sourcesContent":["import { logger } from '@nexical/cli-core';\nimport { DnsProvider, DeploymentContext, DnsRecord } from '../types';\n\nexport class CloudflareDnsProvider implements DnsProvider {\n name = 'cloudflare';\n type = 'dns' as const;\n\n async provision(context: DeploymentContext, records: DnsRecord[]): Promise<void> {\n const dnsConfig = context.config.deploy?.dns;\n\n // Cloudflare specific token handling\n const cfConfig = dnsConfig?.cloudflare as { token?: string; zone?: string } | undefined;\n const apiToken = process.env.CLOUDFLARE_API_TOKEN || cfConfig?.token;\n const zoneId = process.env.CLOUDFLARE_ZONE_ID || cfConfig?.zone;\n\n if (!apiToken) {\n throw new Error(\n 'Cloudflare API token not found. Set CLOUDFLARE_API_TOKEN environment variable or deploy.dns.cloudflare.token in nexical.yaml',\n );\n }\n if (!zoneId) {\n throw new Error(\n 'Cloudflare Zone ID not found. Set CLOUDFLARE_ZONE_ID environment variable or deploy.dns.cloudflare.zone in nexical.yaml',\n );\n }\n\n if (records.length === 0) {\n logger.info(`[Cloudflare DNS] No DNS records to provision.`);\n return;\n }\n\n logger.info(`[Cloudflare DNS] Provisioning ${records.length} records...`);\n\n if (context.options.dryRun) {\n for (const record of records) {\n logger.info(\n `[Dry Run] Would create/update DNS record: ${record.name} -> ${record.content} (${record.type})`,\n );\n }\n return;\n }\n\n // Fetch existing records for this zone to avoid creating duplicates\n const response = await fetch(\n `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records`,\n {\n headers: {\n Authorization: `Bearer ${apiToken}`,\n 'Content-Type': 'application/json',\n },\n },\n );\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Failed to fetch Cloudflare DNS records: ${response.status} ${response.statusText} - ${errorText}`,\n );\n }\n\n const jsonRes = (await response.json()) as {\n success: boolean;\n result: { id: string; type: string; name: string; content: string; proxied: boolean }[];\n };\n\n if (!jsonRes.success) {\n throw new Error('Cloudflare API returned success: false when fetching DNS records.');\n }\n\n const existingRecords = jsonRes.result || [];\n\n for (const record of records) {\n // Find matching record by name and type\n const match = existingRecords.find((r) => r.name === record.name && r.type === record.type);\n\n const payload = {\n type: record.type,\n name: record.name,\n content: record.content,\n proxied: record.proxied ?? true, // Default to proxied for Cloudflare\n ttl: 1, // Automatic\n };\n\n if (match) {\n // Update if content or proxied status differs\n if (match.content !== record.content || match.proxied !== payload.proxied) {\n logger.info(\n `[Cloudflare DNS] Updating ${record.name} (${record.type}) -> ${record.content}`,\n );\n const updateRes = await fetch(\n `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records/${match.id}`,\n {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${apiToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(payload),\n },\n );\n if (!updateRes.ok) {\n const errorText = await updateRes.text();\n throw new Error(`Failed to update DNS record ${record.name}: ${errorText}`);\n }\n } else {\n logger.info(`[Cloudflare DNS] Record ${record.name} is already up to date.`);\n }\n } else {\n // Create new record\n logger.info(\n `[Cloudflare DNS] Creating ${record.name} (${record.type}) -> ${record.content}`,\n );\n const createRes = await fetch(\n `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(payload),\n },\n );\n if (!createRes.ok) {\n const errorText = await createRes.text();\n throw new Error(`Failed to create DNS record ${record.name}: ${errorText}`);\n }\n }\n }\n logger.success(`[Cloudflare DNS] Finished provisioning DNS records.`);\n }\n}\n\nexport default CloudflareDnsProvider;\n"],"mappings":"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","names":[]}
@@ -1,10 +1,14 @@
1
- import { RepositoryProvider, DeploymentContext, DeploymentProvider } from '../types.js';
1
+ import { RepositoryProvider, DeploymentContext, HostingProvider, AppConfig } from '../types.js';
2
2
 
3
3
  declare class GitHubProvider implements RepositoryProvider {
4
4
  name: string;
5
+ private templateManager;
5
6
  configureSecrets(context: DeploymentContext, secrets: Record<string, string>): Promise<void>;
6
7
  configureVariables(context: DeploymentContext, variables: Record<string, string>): Promise<void>;
7
- generateWorkflow(context: DeploymentContext, targets: DeploymentProvider[]): Promise<void>;
8
+ generateWorkflow(context: DeploymentContext, targets: {
9
+ provider: HostingProvider;
10
+ app: AppConfig;
11
+ }[]): Promise<void>;
8
12
  }
9
13
 
10
14
  export { GitHubProvider };
@@ -1,10 +1,13 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ TemplateManager
4
+ } from "../../../chunk-G66GMEFE.js";
2
5
  import {
3
6
  execAsync
4
- } from "../../../chunk-2JW5BYZW.js";
7
+ } from "../../../chunk-VKE7R2EZ.js";
5
8
  import {
6
9
  init_esm_shims
7
- } from "../../../chunk-OYFWMYPG.js";
10
+ } from "../../../chunk-6DE5Q66O.js";
8
11
 
9
12
  // src/deploy/providers/github.ts
10
13
  init_esm_shims();
@@ -14,6 +17,7 @@ import YAML from "yaml";
14
17
  import { logger } from "@nexical/cli-core";
15
18
  var GitHubProvider = class {
16
19
  name = "github";
20
+ templateManager = new TemplateManager();
17
21
  async configureSecrets(context, secrets) {
18
22
  for (const [key, value] of Object.entries(secrets)) {
19
23
  if (!value) continue;
@@ -39,54 +43,38 @@ var GitHubProvider = class {
39
43
  async generateWorkflow(context, targets) {
40
44
  const workflowsDir = path.join(context.cwd, ".github/workflows");
41
45
  await fs.mkdir(workflowsDir, { recursive: true });
42
- for (const target of targets) {
43
- const config = target.getCIConfig("github");
46
+ for (const { provider, app } of targets) {
47
+ const config = provider.getCIConfig("github", app);
44
48
  if (!config) continue;
45
- const filename = `deploy-${target.type}.yml`;
49
+ const filename = `deploy-${app.name}.yml`;
46
50
  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
- }
51
+ const workflow = await this.templateManager.loadWorkflow("github-workflow", {
52
+ APP_NAME: app.name,
53
+ PROVIDER_NAME: provider.name
54
+ });
55
+ if (app.paths && app.paths.length > 0) {
56
+ const workflowAny = workflow;
57
+ if (typeof workflowAny.on === "string") {
58
+ workflowAny.on = {
59
+ push: { branches: [workflowAny.on] }
60
+ };
77
61
  }
78
- };
62
+ if (!workflowAny.on.push) {
63
+ workflowAny.on.push = { branches: ["main"] };
64
+ }
65
+ workflowAny.on.push.paths = app.paths;
66
+ }
79
67
  const steps = workflow.jobs.deploy.steps;
80
- if (target.type === "frontend") {
68
+ if (app.buildCommand) {
81
69
  steps.push({
82
- name: "Build Frontend",
83
- run: "npm run build --workspace=@app/frontend"
70
+ name: `Build ${app.name}`,
71
+ run: app.buildCommand
84
72
  });
85
73
  }
86
74
  if (config.installSteps) {
87
75
  for (const step of config.installSteps) {
88
76
  steps.push({
89
- name: `Install ${target.name} CLI`,
77
+ name: `Install ${provider.name} CLI`,
90
78
  run: step
91
79
  });
92
80
  }
@@ -94,12 +82,16 @@ var GitHubProvider = class {
94
82
  if (config.deploySteps) {
95
83
  for (const step of config.deploySteps) {
96
84
  const deployStep = {
97
- name: `Deploy to ${target.name}`,
85
+ name: `Deploy ${app.name} to ${provider.name}`,
98
86
  run: step,
99
- "working-directory": target.type === "backend" ? "apps/backend" : "apps/frontend"
87
+ "working-directory": app.target || "."
100
88
  };
101
- if (config.secrets && config.secrets.length > 0) {
102
- deployStep.env = config.secrets.reduce((acc, secret) => {
89
+ const allSecrets = [...config.secrets || []];
90
+ if (app.secrets) {
91
+ allSecrets.push(...Object.keys(app.secrets));
92
+ }
93
+ if (allSecrets.length > 0) {
94
+ deployStep.env = allSecrets.reduce((acc, secret) => {
103
95
  acc[secret] = `\${{ secrets.${secret} }}`;
104
96
  return acc;
105
97
  }, {});
@@ -110,7 +102,7 @@ var GitHubProvider = class {
110
102
  if (config.githubActionStep) {
111
103
  steps.push(config.githubActionStep);
112
104
  }
113
- await fs.writeFile(filepath, YAML.stringify(workflow), "utf-8");
105
+ await fs.writeFile(filepath, YAML.stringify(workflow, { lineWidth: 0 }), "utf-8");
114
106
  logger.info(`Generated workflow: ${filepath}`);
115
107
  }
116
108
  }
@@ -1 +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":[]}
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, HostingProvider, AppConfig } from '../types';\nimport { execAsync } from '../utils';\nimport { TemplateManager } from '../template-manager';\n\nexport class GitHubProvider implements RepositoryProvider {\n name = 'github';\n private templateManager = new TemplateManager();\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(\n context: DeploymentContext,\n targets: { provider: HostingProvider; app: AppConfig }[],\n ): Promise<void> {\n const workflowsDir = path.join(context.cwd, '.github/workflows');\n await fs.mkdir(workflowsDir, { recursive: true });\n\n for (const { provider, app } of targets) {\n const config = provider.getCIConfig('github', app);\n if (!config) continue;\n\n const filename = `deploy-${app.name}.yml`;\n const filepath = path.join(workflowsDir, filename);\n\n const workflow = await this.templateManager.loadWorkflow('github-workflow', {\n APP_NAME: app.name,\n PROVIDER_NAME: provider.name,\n });\n\n // Update push trigger if paths are specified\n if (app.paths && app.paths.length > 0) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const workflowAny = workflow as any;\n if (typeof workflowAny.on === 'string') {\n workflowAny.on = {\n push: { branches: [workflowAny.on] },\n };\n }\n if (!workflowAny.on.push) {\n workflowAny.on.push = { branches: ['main'] };\n }\n workflowAny.on.push.paths = app.paths;\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 applicable)\n if (app.buildCommand) {\n steps.push({\n name: `Build ${app.name}`,\n run: app.buildCommand,\n });\n }\n\n // Provider Install Steps\n if (config.installSteps) {\n for (const step of config.installSteps) {\n steps.push({\n name: `Install ${provider.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 ${app.name} to ${provider.name}`,\n run: step,\n 'working-directory': app.target || '.',\n };\n\n const allSecrets = [...(config.secrets || [])];\n if (app.secrets) {\n allSecrets.push(...Object.keys(app.secrets));\n }\n\n if (allSecrets.length > 0) {\n deployStep.env = allSecrets.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, { lineWidth: 0 }), 'utf-8');\n logger.info(`Generated workflow: ${filepath}`);\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,13 +1,22 @@
1
- import { DeploymentProvider, DeploymentContext, CIConfig } from '../types.js';
1
+ import { HostingProvider, DeploymentContext, AppConfig, CIConfig } from '../types.js';
2
2
 
3
- declare class RailwayProvider implements DeploymentProvider {
3
+ interface RailwayConfig {
4
+ token?: string;
5
+ services?: {
6
+ type: string;
7
+ name: string;
8
+ [key: string]: unknown;
9
+ }[];
10
+ }
11
+ declare class RailwayProvider implements HostingProvider {
4
12
  name: string;
5
- type: "backend";
6
- provision(context: DeploymentContext): Promise<void>;
13
+ provision(context: DeploymentContext, app: AppConfig): Promise<void>;
7
14
  private resolveToken;
8
- getSecrets(context: DeploymentContext): Promise<Record<string, string>>;
9
- getVariables(context: DeploymentContext): Promise<Record<string, string>>;
10
- getCIConfig(): CIConfig;
15
+ deploy(context: DeploymentContext, app: AppConfig): Promise<void>;
16
+ getSecrets(context: DeploymentContext, app: AppConfig): Promise<Record<string, string>>;
17
+ getVariables(context: DeploymentContext, app: AppConfig): Promise<Record<string, string>>;
18
+ getCIConfig(repoType: 'github' | 'gitlab', app: AppConfig): CIConfig;
19
+ getDefaultDnsTarget(app: AppConfig): string | undefined;
11
20
  }
12
21
 
13
- export { RailwayProvider };
22
+ export { type RailwayConfig, RailwayProvider };
@@ -1,10 +1,10 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  execAsync
4
- } from "../../../chunk-2JW5BYZW.js";
4
+ } from "../../../chunk-VKE7R2EZ.js";
5
5
  import {
6
6
  init_esm_shims
7
- } from "../../../chunk-OYFWMYPG.js";
7
+ } from "../../../chunk-6DE5Q66O.js";
8
8
 
9
9
  // src/deploy/providers/railway.ts
10
10
  init_esm_shims();
@@ -12,29 +12,27 @@ import path from "path";
12
12
  import { logger } from "@nexical/cli-core";
13
13
  var RailwayProvider = class {
14
14
  name = "railway";
15
- type = "backend";
16
- async provision(context) {
17
- const backendDir = path.join(context.cwd, "apps/backend");
18
- const env = context.options.env || "production";
19
- const baseProjectName = context.config.deploy?.backend?.projectName;
15
+ async provision(context, app) {
16
+ const targetDir = app.target ? path.resolve(context.cwd, app.target) : context.cwd;
17
+ const baseProjectName = app.projectName;
20
18
  if (!baseProjectName) {
21
19
  throw new Error(
22
- "Railway project name not found in nexical.yaml. Please configure 'deploy.backend.projectName'."
20
+ `Railway project name not found for ${app.name}. Please configure 'projectName'.`
23
21
  );
24
22
  }
25
- const railwayName = env === "production" ? baseProjectName : `${baseProjectName}-${env}`;
26
- logger.info("Configuring Railway...");
23
+ const projectName = baseProjectName;
24
+ logger.info(`Configuring Railway project "${projectName}" for ${app.name}...`);
27
25
  if (context.options.dryRun) {
28
- logger.info(`[Dry Run] Would check Railway status and init project "${railwayName}".`);
26
+ logger.info(`[Dry Run] Would check Railway status and init project "${projectName}".`);
29
27
  return;
30
28
  }
31
29
  try {
32
- const env2 = { ...process.env };
33
- delete env2.RAILWAY_API_TOKEN;
34
- delete env2.RAILWAY_TOKEN;
30
+ const processEnv = { ...process.env };
31
+ delete processEnv.RAILWAY_API_TOKEN;
32
+ delete processEnv.RAILWAY_TOKEN;
35
33
  logger.info("Using local Railway CLI credentials (environment variables stripped).");
36
34
  try {
37
- await execAsync("railway status", { cwd: backendDir, env: env2 });
35
+ await execAsync("railway status", { cwd: targetDir, env: processEnv });
38
36
  } catch (error) {
39
37
  const errMsg = error instanceof Error ? error.message : String(error);
40
38
  const stderr = error.stderr || "";
@@ -43,27 +41,50 @@ var RailwayProvider = class {
43
41
  if (fullError.includes("Project not found") || fullError.includes("No project") || fullError.includes("Project is deleted")) {
44
42
  if (fullError.includes("Project is deleted")) {
45
43
  logger.info("[Railway] Project is deleted. Unlinking...");
46
- await execAsync("railway unlink", { cwd: backendDir }).catch(() => {
44
+ await execAsync("railway unlink", { cwd: targetDir }).catch(() => {
47
45
  });
48
46
  }
49
- const initCmd = `railway init --name ${railwayName}`;
47
+ const initCmd = `railway init --name ${projectName}`;
50
48
  logger.info(`No active Railway project linked. Initializing with: ${initCmd}`);
51
- await execAsync(initCmd, { cwd: backendDir, env: env2 });
49
+ await execAsync(initCmd, { cwd: targetDir, env: processEnv });
52
50
  } else if (fullError.includes("Invalid RAILWAY_API_TOKEN") || fullError.includes("Unauthorized")) {
53
51
  throw new Error("Railway authentication failed during status check.");
54
52
  } else {
55
53
  logger.warn(`Railway status check failed: ${errMsg}. Proceeding.`);
56
54
  }
57
55
  }
58
- logger.info(`Adding PostgreSQL service if missing for "${railwayName}"...`);
59
- const { stdout: status } = await execAsync("railway status", { cwd: backendDir, env: env2 }).catch(
60
- () => ({ stdout: "" })
61
- );
62
- if (!status.includes("postgres")) {
63
- try {
64
- await execAsync("railway add --database postgres", { cwd: backendDir, env: env2 });
65
- } catch {
66
- logger.warn("Failed to auto-add PostgreSQL.");
56
+ const rwConfig = app.railway || {};
57
+ const services = rwConfig.services || [];
58
+ if (services.length > 0) {
59
+ logger.info(`Provisioning ${services.length} services for project "${projectName}"...`);
60
+ const statusData = await execAsync("railway status", {
61
+ cwd: targetDir,
62
+ env: processEnv
63
+ }).catch(() => ({ stdout: "" }));
64
+ const status = statusData.stdout || "";
65
+ for (const service of services) {
66
+ if (service.type === "database") {
67
+ const dbName = service.name;
68
+ if (!status.toLowerCase().includes(dbName.toLowerCase())) {
69
+ logger.info(`Adding ${dbName} service to project "${projectName}"...`);
70
+ try {
71
+ await execAsync(`railway add --database ${dbName}`, {
72
+ cwd: targetDir,
73
+ env: processEnv
74
+ });
75
+ } catch (err) {
76
+ logger.warn(
77
+ `Failed to auto-add ${dbName} database: ${err instanceof Error ? err.message : String(err)}`
78
+ );
79
+ }
80
+ } else {
81
+ logger.info(`Service ${dbName} already present in project "${projectName}".`);
82
+ }
83
+ } else {
84
+ logger.warn(
85
+ `Service type "${service.type}" is not yet supported for automatic provisioning.`
86
+ );
87
+ }
67
88
  }
68
89
  }
69
90
  } catch (e) {
@@ -79,46 +100,86 @@ var RailwayProvider = class {
79
100
  );
80
101
  }
81
102
  }
82
- resolveToken(context) {
83
- const options = context.config.deploy?.backend?.options || {};
84
- const tokenEnvVar = typeof options.tokenEnvVar === "string" ? options.tokenEnvVar : void 0;
103
+ resolveToken(context, app) {
104
+ const rwConfig = app.railway || {};
105
+ const tokenEnvVar = rwConfig.token;
85
106
  return process.env.RAILWAY_API_TOKEN?.trim() || (tokenEnvVar ? process.env[tokenEnvVar]?.trim() : void 0) || process.env.RAILWAY_TOKEN?.trim();
86
107
  }
87
- async getSecrets(context) {
88
- const token = this.resolveToken(context);
108
+ async deploy(context, app) {
109
+ const targetDir = app.target ? path.resolve(context.cwd, app.target) : context.cwd;
110
+ logger.info(`Deploying ${app.name} to Railway...`);
111
+ if (context.options.dryRun) {
112
+ logger.info(`[Dry Run] Would run "railway up" in ${targetDir}.`);
113
+ return;
114
+ }
115
+ const token = this.resolveToken(context, app);
116
+ const processEnv = { ...process.env };
117
+ if (token) {
118
+ processEnv.RAILWAY_TOKEN = token;
119
+ }
120
+ await execAsync("railway up --detach", {
121
+ cwd: targetDir,
122
+ env: processEnv
123
+ });
124
+ logger.success(`Successfully deployed ${app.name} to Railway.`);
125
+ }
126
+ async getSecrets(context, app) {
127
+ const token = this.resolveToken(context, app);
89
128
  const secrets = {};
90
129
  if (!token) {
91
130
  throw new Error(
92
- `Railway Token not found. Please provide it via:
131
+ `Railway Token not found for ${app.name}. Please provide it via:
93
132
  1. Setting RAILWAY_API_TOKEN in .env (Recommended)
94
- 2. Configuring 'deploy.backend.options.tokenEnvVar' in nexical.yaml
133
+ 2. Configuring 'railway.token' and setting that env var in .env
95
134
  3. Setting RAILWAY_TOKEN in .env`
96
135
  );
97
136
  }
98
137
  secrets["RAILWAY_API_TOKEN"] = token;
138
+ if (app.secrets) {
139
+ for (const [key, envVar] of Object.entries(app.secrets)) {
140
+ const value = process.env[envVar];
141
+ if (!value) {
142
+ throw new Error(`Custom secret '${key}' mapping failed: Env var '${envVar}' not found.`);
143
+ }
144
+ secrets[key] = value;
145
+ }
146
+ }
99
147
  return secrets;
100
148
  }
101
- async getVariables(context) {
149
+ async getVariables(context, app) {
102
150
  const env = context.options.env || "production";
103
- const baseProjectName = context.config.deploy?.backend?.projectName;
151
+ const baseProjectName = app.projectName;
104
152
  if (!baseProjectName) {
105
- throw new Error(
106
- "Railway project name not found in nexical.yaml. Please configure 'deploy.backend.projectName'."
107
- );
153
+ throw new Error(`Railway project name not found for ${app.name}.`);
108
154
  }
109
- const projectName = env === "production" ? baseProjectName : `${baseProjectName}-${env}`;
110
- return {
111
- RAILWAY_PROJECT_NAME: projectName
155
+ const result = {
156
+ RAILWAY_PROJECT_NAME: baseProjectName,
157
+ RAILWAY_ENVIRONMENT: env
112
158
  };
159
+ if (app.env) {
160
+ for (const [key, value] of Object.entries(app.env)) {
161
+ const resolvedValue = process.env[value] || value;
162
+ result[key] = resolvedValue;
163
+ }
164
+ }
165
+ return result;
113
166
  }
114
- getCIConfig() {
167
+ getCIConfig(repoType, app) {
115
168
  return {
116
169
  secrets: ["RAILWAY_API_TOKEN"],
117
- variables: [],
170
+ variables: ["RAILWAY_ENVIRONMENT"],
118
171
  installSteps: ["npm install -g @railway/cli"],
119
- deploySteps: ["railway up --detach --project=${{ vars.RAILWAY_PROJECT_NAME }}"]
172
+ deploySteps: [
173
+ `railway up --detach --project=\${{ vars.RAILWAY_PROJECT_NAME }} --environment=\${{ vars.RAILWAY_ENVIRONMENT }}`
174
+ ]
120
175
  };
121
176
  }
177
+ getDefaultDnsTarget(app) {
178
+ if (app.projectName) {
179
+ return `${app.projectName}.up.railway.app`;
180
+ }
181
+ return void 0;
182
+ }
122
183
  };
123
184
  export {
124
185
  RailwayProvider
@@ -1 +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 env = (context.options.env as string) || 'production';\n const baseProjectName = context.config.deploy?.backend?.projectName;\n\n if (!baseProjectName) {\n throw new Error(\n \"Railway project name not found in nexical.yaml. Please configure 'deploy.backend.projectName'.\",\n );\n }\n\n const railwayName = env === 'production' ? baseProjectName : `${baseProjectName}-${env}`;\n\n logger.info('Configuring Railway...');\n\n if (context.options.dryRun) {\n logger.info(`[Dry Run] Would check Railway status and init project \"${railwayName}\".`);\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 const env = (context.options.env as string) || 'production';\n const baseProjectName = context.config.deploy?.backend?.projectName;\n\n if (!baseProjectName) {\n throw new Error(\n \"Railway project name not found in nexical.yaml. Please configure 'deploy.backend.projectName'.\",\n );\n }\n\n const projectName = env === 'production' ? baseProjectName : `${baseProjectName}-${env}`;\n return {\n RAILWAY_PROJECT_NAME: projectName,\n };\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 --project=${{ vars.RAILWAY_PROJECT_NAME }}'],\n };\n }\n}\n"],"mappings":"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","names":["env"]}
1
+ {"version":3,"sources":["../../../../src/deploy/providers/railway.ts"],"sourcesContent":["import path from 'node:path';\nimport { logger } from '@nexical/cli-core';\nimport { HostingProvider, DeploymentContext, CIConfig, AppConfig } from '../types';\nimport { execAsync } from '../utils';\n\nexport interface RailwayConfig {\n token?: string;\n services?: {\n type: string;\n name: string;\n [key: string]: unknown;\n }[];\n}\n\nexport class RailwayProvider implements HostingProvider {\n name = 'railway';\n\n async provision(context: DeploymentContext, app: AppConfig): Promise<void> {\n const targetDir = app.target ? path.resolve(context.cwd, app.target) : context.cwd;\n const baseProjectName = app.projectName;\n\n if (!baseProjectName) {\n throw new Error(\n `Railway project name not found for ${app.name}. Please configure 'projectName'.`,\n );\n }\n\n const projectName = baseProjectName;\n\n logger.info(`Configuring Railway project \"${projectName}\" for ${app.name}...`);\n\n if (context.options.dryRun) {\n logger.info(`[Dry Run] Would check Railway status and init project \"${projectName}\".`);\n return;\n }\n\n try {\n const processEnv = { ...process.env };\n delete processEnv.RAILWAY_API_TOKEN;\n delete processEnv.RAILWAY_TOKEN;\n\n logger.info('Using local Railway CLI credentials (environment variables stripped).');\n\n try {\n await execAsync('railway status', { cwd: targetDir, env: processEnv });\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 (\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 await execAsync('railway unlink', { cwd: targetDir }).catch(() => {});\n }\n const initCmd = `railway init --name ${projectName}`;\n logger.info(`No active Railway project linked. Initializing with: ${initCmd}`);\n await execAsync(initCmd, { cwd: targetDir, env: processEnv });\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 logger.warn(`Railway status check failed: ${errMsg}. Proceeding.`);\n }\n }\n\n const rwConfig = (app.railway as RailwayConfig) || {};\n const services = rwConfig.services || [];\n if (services.length > 0) {\n logger.info(`Provisioning ${services.length} services for project \"${projectName}\"...`);\n\n // Re-check status once to see what's already there\n const statusData = await execAsync('railway status', {\n cwd: targetDir,\n env: processEnv,\n }).catch(() => ({ stdout: '' }));\n const status = (statusData as { stdout: string }).stdout || '';\n\n for (const service of services) {\n if (service.type === 'database') {\n const dbName = service.name;\n if (!status.toLowerCase().includes(dbName.toLowerCase())) {\n logger.info(`Adding ${dbName} service to project \"${projectName}\"...`);\n try {\n await execAsync(`railway add --database ${dbName}`, {\n cwd: targetDir,\n env: processEnv,\n });\n } catch (err: unknown) {\n logger.warn(\n `Failed to auto-add ${dbName} database: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n } else {\n logger.info(`Service ${dbName} already present in project \"${projectName}\".`);\n }\n } else {\n logger.warn(\n `Service type \"${service.type}\" is not yet supported for automatic provisioning.`,\n );\n }\n }\n }\n } catch (e: unknown) {\n const errMsg = e instanceof Error ? e.message : String(e);\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, app: AppConfig): string | undefined {\n const rwConfig = (app.railway as RailwayConfig) || {};\n const tokenEnvVar = rwConfig.token;\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 deploy(context: DeploymentContext, app: AppConfig): Promise<void> {\n const targetDir = app.target ? path.resolve(context.cwd, app.target) : context.cwd;\n\n logger.info(`Deploying ${app.name} to Railway...`);\n\n if (context.options.dryRun) {\n logger.info(`[Dry Run] Would run \"railway up\" in ${targetDir}.`);\n return;\n }\n\n const token = this.resolveToken(context, app);\n const processEnv = { ...process.env };\n if (token) {\n processEnv.RAILWAY_TOKEN = token;\n }\n\n await execAsync('railway up --detach', {\n cwd: targetDir,\n env: processEnv,\n });\n\n logger.success(`Successfully deployed ${app.name} to Railway.`);\n }\n\n async getSecrets(context: DeploymentContext, app: AppConfig): Promise<Record<string, string>> {\n const token = this.resolveToken(context, app);\n const secrets: Record<string, string> = {};\n\n if (!token) {\n throw new Error(\n `Railway Token not found for ${app.name}. Please provide it via:\\n` +\n `1. Setting RAILWAY_API_TOKEN in .env (Recommended)\\n` +\n `2. Configuring 'railway.token' and setting that env var in .env\\n` +\n `3. Setting RAILWAY_TOKEN in .env`,\n );\n }\n\n secrets['RAILWAY_API_TOKEN'] = token;\n\n // Custom mapped secrets\n if (app.secrets) {\n for (const [key, envVar] of Object.entries(app.secrets)) {\n const value = process.env[envVar];\n if (!value) {\n throw new Error(`Custom secret '${key}' mapping failed: Env var '${envVar}' not found.`);\n }\n secrets[key] = value;\n }\n }\n\n return secrets;\n }\n\n async getVariables(context: DeploymentContext, app: AppConfig): Promise<Record<string, string>> {\n const env = (context.options.env as string) || 'production';\n const baseProjectName = app.projectName;\n\n if (!baseProjectName) {\n throw new Error(`Railway project name not found for ${app.name}.`);\n }\n\n const result: Record<string, string> = {\n RAILWAY_PROJECT_NAME: baseProjectName,\n RAILWAY_ENVIRONMENT: env,\n };\n\n // Custom mapped variables\n if (app.env) {\n for (const [key, value] of Object.entries(app.env)) {\n const resolvedValue = process.env[value] || value;\n result[key] = resolvedValue;\n }\n }\n\n return result;\n }\n\n getCIConfig(repoType: 'github' | 'gitlab', app: AppConfig): CIConfig {\n return {\n secrets: ['RAILWAY_API_TOKEN'],\n variables: ['RAILWAY_ENVIRONMENT'],\n installSteps: ['npm install -g @railway/cli'],\n deploySteps: [\n `railway up --detach --project=\\${{ vars.RAILWAY_PROJECT_NAME }} --environment=\\${{ vars.RAILWAY_ENVIRONMENT }}`,\n ],\n };\n }\n\n getDefaultDnsTarget(app: AppConfig): string | undefined {\n // Railway typically creates a [project-name]-[environment].up.railway.app domain.\n // For simpler custom domain linking, users often just CNAME directly to up.railway.app\n // or the specific assigned railway generated domain if it's predictable.\n // For automatic resolution without runtime polling, returning the predictable project CNAME.\n if (app.projectName) {\n return `${app.projectName}.up.railway.app`;\n }\n return undefined;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,12 +1,15 @@
1
- import { DeploymentProvider, RepositoryProvider } from './types.js';
1
+ import { HostingProvider, RepositoryProvider, DnsProvider } from './types.js';
2
2
 
3
3
  declare class ProviderRegistry {
4
- private deploymentProviders;
4
+ private hostingProviders;
5
5
  private repositoryProviders;
6
- registerDeploymentProvider(provider: DeploymentProvider): void;
6
+ private dnsProviders;
7
+ registerHostingProvider(provider: HostingProvider): void;
7
8
  registerRepositoryProvider(provider: RepositoryProvider): void;
8
- getDeploymentProvider(name: string): DeploymentProvider | undefined;
9
+ getHostingProvider(name: string): HostingProvider | undefined;
9
10
  getRepositoryProvider(name: string): RepositoryProvider | undefined;
11
+ registerDnsProvider(provider: DnsProvider): void;
12
+ getDnsProvider(name: string): DnsProvider | undefined;
10
13
  private registerProviderFromModule;
11
14
  loadCoreProviders(): Promise<void>;
12
15
  loadLocalProviders(cwd: string): Promise<void>;
@@ -1,8 +1,8 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  ProviderRegistry
4
- } from "../../chunk-EKCOW7FM.js";
5
- import "../../chunk-OYFWMYPG.js";
4
+ } from "../../chunk-USP2MI63.js";
5
+ import "../../chunk-6DE5Q66O.js";
6
6
  export {
7
7
  ProviderRegistry
8
8
  };