@nexical/cli 0.11.3 → 0.11.4

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/index.js CHANGED
@@ -15,7 +15,7 @@ import { fileURLToPath } from "url";
15
15
  // package.json
16
16
  var package_default = {
17
17
  name: "@nexical/cli",
18
- version: "0.11.3",
18
+ version: "0.11.4",
19
19
  type: "module",
20
20
  bin: {
21
21
  nexical: "./dist/index.js"
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../index.ts","../package.json"],"sourcesContent":["#!/usr/bin/env node\nimport { CLI, findProjectRoot } from '@nexical/cli-core';\nimport { fileURLToPath } from 'node:url';\nimport { discoverCommandDirectories } from './src/utils/discovery.js';\nimport pkg from './package.json';\nimport path from 'node:path';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst commandName = 'nexical';\nconst projectRoot = (await findProjectRoot(commandName, process.cwd())) || process.cwd();\nconst coreCommandsDir = path.resolve(__dirname, './src/commands');\nconst additionalCommands = discoverCommandDirectories(projectRoot);\n\n// Filter out duplicate core commands and source versions\nconst filteredAdditional = additionalCommands.filter((dir) => {\n const resolvedDir = path.resolve(dir);\n const resolvedCore = path.resolve(coreCommandsDir);\n\n if (resolvedDir === resolvedCore) return false;\n\n // Check if this is another instance of the core CLI commands (by checking path suffix)\n const coreSuffix = path.join('@nexical', 'cli', 'dist', 'src', 'commands');\n const coreSuffixSrc = path.join('packages', 'cli', 'dist', 'src', 'commands');\n const coreSuffixRawSrc = path.join('packages', 'cli', 'src', 'commands');\n\n if (\n resolvedDir.endsWith(coreSuffix) ||\n resolvedDir.endsWith(coreSuffixSrc) ||\n resolvedDir.endsWith(coreSuffixRawSrc)\n ) {\n return false;\n }\n\n // Handle mismatch between dist/src and src/\n if (resolvedCore.includes(path.join(path.sep, 'dist', 'src', 'commands'))) {\n const srcVersion = resolvedCore.replace(\n path.join(path.sep, 'dist', 'src', 'commands'),\n path.join(path.sep, 'src', 'commands'),\n );\n if (resolvedDir === srcVersion) return false;\n }\n\n return true;\n});\n\nconst app = new CLI({\n version: pkg.version,\n commandName: commandName,\n searchDirectories: [...new Set([coreCommandsDir, ...filteredAdditional])],\n});\napp.start();\n","{\n \"name\": \"@nexical/cli\",\n \"version\": \"0.11.3\",\n \"type\": \"module\",\n \"bin\": {\n \"nexical\": \"./dist/index.js\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"start\": \"node dist/index.js\",\n \"test\": \"npm run test:unit && npm run test:integration && npm run test:e2e\",\n \"test:unit\": \"vitest run --config vitest.config.ts --coverage\",\n \"test:integration\": \"vitest run --config vitest.integration.config.ts\",\n \"test:e2e\": \"npm run build && vitest run --config vitest.e2e.config.ts\",\n \"test:watch\": \"vitest\",\n \"format\": \"prettier --write .\",\n \"lint\": \"eslint .\",\n \"lint:fix\": \"eslint . --fix\",\n \"prepare\": \"husky\"\n },\n \"lint-staged\": {\n \"**/*\": [\n \"prettier --write --ignore-unknown\"\n ],\n \"**/*.{js,jsx,ts,tsx,astro}\": [\n \"eslint --fix\"\n ]\n },\n \"dependencies\": {\n \"@nexical/cli-core\": \"^0.1.12\",\n \"yaml\": \"^2.3.4\",\n \"fast-glob\": \"^3.3.3\"\n },\n \"devDependencies\": {\n \"@types/fs-extra\": \"^11.0.4\",\n \"@types/node\": \"^20.10.0\",\n \"@vitest/coverage-v8\": \"^4.0.15\",\n \"execa\": \"^9.6.1\",\n \"fs-extra\": \"^11.3.2\",\n \"tsup\": \"^8.0.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.3.3\",\n \"vitest\": \"^4.0.15\",\n \"@eslint/js\": \"^9.39.2\",\n \"eslint\": \"^9.39.2\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"eslint-plugin-astro\": \"^1.5.0\",\n \"eslint-plugin-jsx-a11y\": \"^6.10.2\",\n \"eslint-plugin-react\": \"^7.37.5\",\n \"eslint-plugin-react-hooks\": \"^7.0.1\",\n \"globals\": \"^17.2.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^16.2.7\",\n \"prettier\": \"^3.8.1\",\n \"typescript-eslint\": \"^8.54.0\"\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AACA,SAAS,KAAK,uBAAuB;AACrC,SAAS,qBAAqB;;;ACF9B;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,KAAO;AAAA,IACL,SAAW;AAAA,EACb;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,KAAO;AAAA,IACP,OAAS;AAAA,IACT,MAAQ;AAAA,IACR,aAAa;AAAA,IACb,oBAAoB;AAAA,IACpB,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,QAAU;AAAA,IACV,MAAQ;AAAA,IACR,YAAY;AAAA,IACZ,SAAW;AAAA,EACb;AAAA,EACA,eAAe;AAAA,IACb,QAAQ;AAAA,MACN;AAAA,IACF;AAAA,IACA,8BAA8B;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAAA,EACA,cAAgB;AAAA,IACd,qBAAqB;AAAA,IACrB,MAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,iBAAmB;AAAA,IACjB,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,uBAAuB;AAAA,IACvB,OAAS;AAAA,IACT,YAAY;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,IACP,YAAc;AAAA,IACd,QAAU;AAAA,IACV,cAAc;AAAA,IACd,QAAU;AAAA,IACV,0BAA0B;AAAA,IAC1B,uBAAuB;AAAA,IACvB,0BAA0B;AAAA,IAC1B,uBAAuB;AAAA,IACvB,6BAA6B;AAAA,IAC7B,SAAW;AAAA,IACX,OAAS;AAAA,IACT,eAAe;AAAA,IACf,UAAY;AAAA,IACZ,qBAAqB;AAAA,EACvB;AACF;;;ADpDA,OAAO,UAAU;AAEjB,IAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAE7D,IAAM,cAAc;AACpB,IAAM,cAAe,MAAM,gBAAgB,aAAa,QAAQ,IAAI,CAAC,KAAM,QAAQ,IAAI;AACvF,IAAM,kBAAkB,KAAK,QAAQ,WAAW,gBAAgB;AAChE,IAAM,qBAAqB,2BAA2B,WAAW;AAGjE,IAAM,qBAAqB,mBAAmB,OAAO,CAAC,QAAQ;AAC5D,QAAM,cAAc,KAAK,QAAQ,GAAG;AACpC,QAAM,eAAe,KAAK,QAAQ,eAAe;AAEjD,MAAI,gBAAgB,aAAc,QAAO;AAGzC,QAAM,aAAa,KAAK,KAAK,YAAY,OAAO,QAAQ,OAAO,UAAU;AACzE,QAAM,gBAAgB,KAAK,KAAK,YAAY,OAAO,QAAQ,OAAO,UAAU;AAC5E,QAAM,mBAAmB,KAAK,KAAK,YAAY,OAAO,OAAO,UAAU;AAEvE,MACE,YAAY,SAAS,UAAU,KAC/B,YAAY,SAAS,aAAa,KAClC,YAAY,SAAS,gBAAgB,GACrC;AACA,WAAO;AAAA,EACT;AAGA,MAAI,aAAa,SAAS,KAAK,KAAK,KAAK,KAAK,QAAQ,OAAO,UAAU,CAAC,GAAG;AACzE,UAAM,aAAa,aAAa;AAAA,MAC9B,KAAK,KAAK,KAAK,KAAK,QAAQ,OAAO,UAAU;AAAA,MAC7C,KAAK,KAAK,KAAK,KAAK,OAAO,UAAU;AAAA,IACvC;AACA,QAAI,gBAAgB,WAAY,QAAO;AAAA,EACzC;AAEA,SAAO;AACT,CAAC;AAED,IAAM,MAAM,IAAI,IAAI;AAAA,EAClB,SAAS,gBAAI;AAAA,EACb;AAAA,EACA,mBAAmB,CAAC,GAAG,oBAAI,IAAI,CAAC,iBAAiB,GAAG,kBAAkB,CAAC,CAAC;AAC1E,CAAC;AACD,IAAI,MAAM;","names":[]}
1
+ {"version":3,"sources":["../index.ts","../package.json"],"sourcesContent":["#!/usr/bin/env node\nimport { CLI, findProjectRoot } from '@nexical/cli-core';\nimport { fileURLToPath } from 'node:url';\nimport { discoverCommandDirectories } from './src/utils/discovery.js';\nimport pkg from './package.json';\nimport path from 'node:path';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst commandName = 'nexical';\nconst projectRoot = (await findProjectRoot(commandName, process.cwd())) || process.cwd();\nconst coreCommandsDir = path.resolve(__dirname, './src/commands');\nconst additionalCommands = discoverCommandDirectories(projectRoot);\n\n// Filter out duplicate core commands and source versions\nconst filteredAdditional = additionalCommands.filter((dir) => {\n const resolvedDir = path.resolve(dir);\n const resolvedCore = path.resolve(coreCommandsDir);\n\n if (resolvedDir === resolvedCore) return false;\n\n // Check if this is another instance of the core CLI commands (by checking path suffix)\n const coreSuffix = path.join('@nexical', 'cli', 'dist', 'src', 'commands');\n const coreSuffixSrc = path.join('packages', 'cli', 'dist', 'src', 'commands');\n const coreSuffixRawSrc = path.join('packages', 'cli', 'src', 'commands');\n\n if (\n resolvedDir.endsWith(coreSuffix) ||\n resolvedDir.endsWith(coreSuffixSrc) ||\n resolvedDir.endsWith(coreSuffixRawSrc)\n ) {\n return false;\n }\n\n // Handle mismatch between dist/src and src/\n if (resolvedCore.includes(path.join(path.sep, 'dist', 'src', 'commands'))) {\n const srcVersion = resolvedCore.replace(\n path.join(path.sep, 'dist', 'src', 'commands'),\n path.join(path.sep, 'src', 'commands'),\n );\n if (resolvedDir === srcVersion) return false;\n }\n\n return true;\n});\n\nconst app = new CLI({\n version: pkg.version,\n commandName: commandName,\n searchDirectories: [...new Set([coreCommandsDir, ...filteredAdditional])],\n});\napp.start();\n","{\n \"name\": \"@nexical/cli\",\n \"version\": \"0.11.4\",\n \"type\": \"module\",\n \"bin\": {\n \"nexical\": \"./dist/index.js\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"start\": \"node dist/index.js\",\n \"test\": \"npm run test:unit && npm run test:integration && npm run test:e2e\",\n \"test:unit\": \"vitest run --config vitest.config.ts --coverage\",\n \"test:integration\": \"vitest run --config vitest.integration.config.ts\",\n \"test:e2e\": \"npm run build && vitest run --config vitest.e2e.config.ts\",\n \"test:watch\": \"vitest\",\n \"format\": \"prettier --write .\",\n \"lint\": \"eslint .\",\n \"lint:fix\": \"eslint . --fix\",\n \"prepare\": \"husky\"\n },\n \"lint-staged\": {\n \"**/*\": [\n \"prettier --write --ignore-unknown\"\n ],\n \"**/*.{js,jsx,ts,tsx,astro}\": [\n \"eslint --fix\"\n ]\n },\n \"dependencies\": {\n \"@nexical/cli-core\": \"^0.1.12\",\n \"yaml\": \"^2.3.4\",\n \"fast-glob\": \"^3.3.3\"\n },\n \"devDependencies\": {\n \"@types/fs-extra\": \"^11.0.4\",\n \"@types/node\": \"^20.10.0\",\n \"@vitest/coverage-v8\": \"^4.0.15\",\n \"execa\": \"^9.6.1\",\n \"fs-extra\": \"^11.3.2\",\n \"tsup\": \"^8.0.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.3.3\",\n \"vitest\": \"^4.0.15\",\n \"@eslint/js\": \"^9.39.2\",\n \"eslint\": \"^9.39.2\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"eslint-plugin-astro\": \"^1.5.0\",\n \"eslint-plugin-jsx-a11y\": \"^6.10.2\",\n \"eslint-plugin-react\": \"^7.37.5\",\n \"eslint-plugin-react-hooks\": \"^7.0.1\",\n \"globals\": \"^17.2.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^16.2.7\",\n \"prettier\": \"^3.8.1\",\n \"typescript-eslint\": \"^8.54.0\"\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AACA,SAAS,KAAK,uBAAuB;AACrC,SAAS,qBAAqB;;;ACF9B;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,KAAO;AAAA,IACL,SAAW;AAAA,EACb;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,KAAO;AAAA,IACP,OAAS;AAAA,IACT,MAAQ;AAAA,IACR,aAAa;AAAA,IACb,oBAAoB;AAAA,IACpB,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,QAAU;AAAA,IACV,MAAQ;AAAA,IACR,YAAY;AAAA,IACZ,SAAW;AAAA,EACb;AAAA,EACA,eAAe;AAAA,IACb,QAAQ;AAAA,MACN;AAAA,IACF;AAAA,IACA,8BAA8B;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAAA,EACA,cAAgB;AAAA,IACd,qBAAqB;AAAA,IACrB,MAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,iBAAmB;AAAA,IACjB,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,uBAAuB;AAAA,IACvB,OAAS;AAAA,IACT,YAAY;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,IACP,YAAc;AAAA,IACd,QAAU;AAAA,IACV,cAAc;AAAA,IACd,QAAU;AAAA,IACV,0BAA0B;AAAA,IAC1B,uBAAuB;AAAA,IACvB,0BAA0B;AAAA,IAC1B,uBAAuB;AAAA,IACvB,6BAA6B;AAAA,IAC7B,SAAW;AAAA,IACX,OAAS;AAAA,IACT,eAAe;AAAA,IACf,UAAY;AAAA,IACZ,qBAAqB;AAAA,EACvB;AACF;;;ADpDA,OAAO,UAAU;AAEjB,IAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAE7D,IAAM,cAAc;AACpB,IAAM,cAAe,MAAM,gBAAgB,aAAa,QAAQ,IAAI,CAAC,KAAM,QAAQ,IAAI;AACvF,IAAM,kBAAkB,KAAK,QAAQ,WAAW,gBAAgB;AAChE,IAAM,qBAAqB,2BAA2B,WAAW;AAGjE,IAAM,qBAAqB,mBAAmB,OAAO,CAAC,QAAQ;AAC5D,QAAM,cAAc,KAAK,QAAQ,GAAG;AACpC,QAAM,eAAe,KAAK,QAAQ,eAAe;AAEjD,MAAI,gBAAgB,aAAc,QAAO;AAGzC,QAAM,aAAa,KAAK,KAAK,YAAY,OAAO,QAAQ,OAAO,UAAU;AACzE,QAAM,gBAAgB,KAAK,KAAK,YAAY,OAAO,QAAQ,OAAO,UAAU;AAC5E,QAAM,mBAAmB,KAAK,KAAK,YAAY,OAAO,OAAO,UAAU;AAEvE,MACE,YAAY,SAAS,UAAU,KAC/B,YAAY,SAAS,aAAa,KAClC,YAAY,SAAS,gBAAgB,GACrC;AACA,WAAO;AAAA,EACT;AAGA,MAAI,aAAa,SAAS,KAAK,KAAK,KAAK,KAAK,QAAQ,OAAO,UAAU,CAAC,GAAG;AACzE,UAAM,aAAa,aAAa;AAAA,MAC9B,KAAK,KAAK,KAAK,KAAK,QAAQ,OAAO,UAAU;AAAA,MAC7C,KAAK,KAAK,KAAK,KAAK,OAAO,UAAU;AAAA,IACvC;AACA,QAAI,gBAAgB,WAAY,QAAO;AAAA,EACzC;AAEA,SAAO;AACT,CAAC;AAED,IAAM,MAAM,IAAI,IAAI;AAAA,EAClB,SAAS,gBAAI;AAAA,EACb;AAAA,EACA,mBAAmB,CAAC,GAAG,oBAAI,IAAI,CAAC,iBAAiB,GAAG,kBAAkB,CAAC,CAAC;AAC1E,CAAC;AACD,IAAI,MAAM;","names":[]}
@@ -3,6 +3,9 @@ import { BaseCommand } from '@nexical/cli-core';
3
3
  interface DeployOptions {
4
4
  dryRun: boolean;
5
5
  railwayToken?: string;
6
+ railwayName?: string;
7
+ backendName: string;
8
+ frontendName: string;
6
9
  cloudflareToken?: string;
7
10
  cloudflareAccount?: string;
8
11
  }
@@ -17,12 +20,16 @@ declare class DeployCommand extends BaseCommand {
17
20
  name: string;
18
21
  description: string;
19
22
  default?: undefined;
23
+ } | {
24
+ name: string;
25
+ description: string;
26
+ default: string;
20
27
  })[];
21
28
  };
22
29
  run(options: DeployOptions): Promise<void>;
23
30
  private setupRailway;
24
31
  private setupCloudflare;
25
- private setupGitHubSecrets;
32
+ private setupGitHubConfig;
26
33
  }
27
34
 
28
35
  export { DeployCommand as default };
@@ -5,12 +5,36 @@ import {
5
5
 
6
6
  // src/commands/deploy.ts
7
7
  init_esm_shims();
8
- import { BaseCommand, runCommand } from "@nexical/cli-core";
8
+ import { BaseCommand } from "@nexical/cli-core";
9
9
  import { exec } from "child_process";
10
10
  import { promisify } from "util";
11
+ import path from "path";
11
12
  var execAsync = promisify(exec);
12
13
  var DeployCommand = class extends BaseCommand {
13
- static description = "Deploy the application to Railway and Cloudflare.";
14
+ static description = `Deploy the application to Railway and Cloudflare.
15
+
16
+ ENVIRONMENT SETUP & PREREQUISITES:
17
+ 1. Install Required CLIs:
18
+ - Railway CLI: npm i -g @railway/cli
19
+ - Wrangler (Cloudflare): npm i -g wrangler
20
+ - GitHub CLI: https://cli.github.com/
21
+
22
+ 2. Authentication:
23
+ - Railway: Run 'railway login'
24
+ - GitHub: Run 'gh auth login'
25
+ - Cloudflare: Obtain an API Token (with Pages edit permissions) and your Account ID from the dashboard.
26
+
27
+ 3. Configuration:
28
+ Run this command with --cloudflare-token and --cloudflare-account to automate the full setup.
29
+ Optional: Use --railway-token if you prefer not to use the interactive login.
30
+ Optional: Use --railway-name to specify a custom Railway project name.
31
+ Optional: Use --backend-name to specify the Railway service name (default: nexical-backend).
32
+ Optional: Use --frontend-name to specify the Cloudflare Pages project name (default: nexical-frontend).
33
+
34
+ PROCESS:
35
+ - Provisions a PostgreSQL database on Railway (if missing).
36
+ - Creates a Cloudflare Pages project for the frontend.
37
+ - Syncs all necessary deployment secrets and variables to GitHub for CI/CD automation.`;
14
38
  static args = {
15
39
  options: [
16
40
  {
@@ -22,6 +46,20 @@ var DeployCommand = class extends BaseCommand {
22
46
  name: "--railway-token <token>",
23
47
  description: "Railway Project Token (optional if already logged in)."
24
48
  },
49
+ {
50
+ name: "--railway-name <name>",
51
+ description: "Railway Project Name (used during initialization)."
52
+ },
53
+ {
54
+ name: "--backend-name <name>",
55
+ description: "Backend service name on Railway.",
56
+ default: "nexical-backend"
57
+ },
58
+ {
59
+ name: "--frontend-name <name>",
60
+ description: "Frontend project name on Cloudflare.",
61
+ default: "nexical-frontend"
62
+ },
25
63
  {
26
64
  name: "--cloudflare-token <token>",
27
65
  description: "Cloudflare API Token."
@@ -40,7 +78,7 @@ var DeployCommand = class extends BaseCommand {
40
78
  try {
41
79
  await this.setupRailway(options);
42
80
  await this.setupCloudflare(options);
43
- await this.setupGitHubSecrets(options);
81
+ await this.setupGitHubConfig(options);
44
82
  this.success("Deployment setup complete! Your application is being deployed.");
45
83
  } catch (error) {
46
84
  if (error instanceof Error) {
@@ -54,25 +92,25 @@ var DeployCommand = class extends BaseCommand {
54
92
  async setupRailway(options) {
55
93
  this.info("Configuring Railway...");
56
94
  if (options.dryRun) {
57
- this.info("[Dry Run] Would run: railway init");
95
+ const initCmd = options.railwayName ? `railway init --name ${options.railwayName}` : "railway init";
96
+ this.info(`[Dry Run] Would run: ${initCmd}`);
58
97
  this.info("[Dry Run] Would run: railway add --database postgres");
59
98
  return;
60
99
  }
61
100
  try {
62
101
  this.info("Ensuring Railway project is linked...");
63
- if (options.railwayToken) {
64
- process.env.RAILWAY_TOKEN = options.railwayToken;
65
- }
102
+ const backendDir = path.join(process.cwd(), "apps/backend");
66
103
  try {
67
- await runCommand("railway status");
104
+ await execAsync("railway status", { cwd: backendDir });
68
105
  } catch {
69
- this.info("No Railway project detected. Initializing...");
70
- await runCommand("railway init");
106
+ const initCmd = options.railwayName ? `railway init --name ${options.railwayName}` : "railway init";
107
+ this.info(`No Railway project detected in apps/backend. Initializing with: ${initCmd}`);
108
+ await execAsync(initCmd, { cwd: backendDir });
71
109
  }
72
- this.info("Adding PostgreSQL service if missing...");
73
- const { stdout: status } = await execAsync("railway status");
110
+ this.info(`Adding PostgreSQL service if missing for "${options.backendName}"...`);
111
+ const { stdout: status } = await execAsync("railway status", { cwd: backendDir });
74
112
  if (!status.includes("postgres")) {
75
- await runCommand("railway add --database postgres");
113
+ await execAsync("railway add --database postgres", { cwd: backendDir });
76
114
  }
77
115
  } catch (e) {
78
116
  this.warn(
@@ -84,7 +122,7 @@ var DeployCommand = class extends BaseCommand {
84
122
  async setupCloudflare(options) {
85
123
  this.info("Configuring Cloudflare Pages...");
86
124
  if (options.dryRun) {
87
- this.info("[Dry Run] Would run: wrangler pages project create nexical-frontend");
125
+ this.info(`[Dry Run] Would run: wrangler pages project create ${options.frontendName}`);
88
126
  return;
89
127
  }
90
128
  if (!options.cloudflareToken || !options.cloudflareAccount) {
@@ -93,7 +131,7 @@ var DeployCommand = class extends BaseCommand {
93
131
  return;
94
132
  }
95
133
  try {
96
- const projectName = "nexical-frontend";
134
+ const projectName = options.frontendName;
97
135
  this.info(`Ensuring Cloudflare Pages project "${projectName}" exists...`);
98
136
  try {
99
137
  await execAsync(`wrangler pages project create ${projectName} --production-branch main`, {
@@ -111,32 +149,42 @@ var DeployCommand = class extends BaseCommand {
111
149
  throw e;
112
150
  }
113
151
  }
114
- async setupGitHubSecrets(options) {
115
- this.info("Configuring GitHub Secrets...");
152
+ async setupGitHubConfig(options) {
153
+ this.info("Configuring GitHub Secrets and Variables...");
116
154
  if (options.dryRun) {
117
155
  this.info("[Dry Run] Would run: gh secret set RAILWAY_TOKEN");
118
156
  this.info("[Dry Run] Would run: gh secret set CLOUDFLARE_API_TOKEN");
119
157
  this.info("[Dry Run] Would run: gh secret set CLOUDFLARE_ACCOUNT_ID");
158
+ this.info(
159
+ `[Dry Run] Would run: gh variable set RAILWAY_SERVICE_NAME --body "${options.backendName}"`
160
+ );
161
+ this.info(
162
+ `[Dry Run] Would run: gh variable set CLOUDFLARE_PROJECT_NAME --body "${options.frontendName}"`
163
+ );
120
164
  return;
121
165
  }
122
166
  try {
123
167
  if (options.railwayToken) {
124
168
  this.info("Setting RAILWAY_TOKEN in GitHub...");
125
- await runCommand(`gh secret set RAILWAY_TOKEN --body "${options.railwayToken}"`);
169
+ await execAsync(`gh secret set RAILWAY_TOKEN --body "${options.railwayToken}"`);
126
170
  }
127
171
  if (options.cloudflareToken) {
128
172
  this.info("Setting CLOUDFLARE_API_TOKEN in GitHub...");
129
- await runCommand(`gh secret set CLOUDFLARE_API_TOKEN --body "${options.cloudflareToken}"`);
173
+ await execAsync(`gh secret set CLOUDFLARE_API_TOKEN --body "${options.cloudflareToken}"`);
130
174
  }
131
175
  if (options.cloudflareAccount) {
132
176
  this.info("Setting CLOUDFLARE_ACCOUNT_ID in GitHub...");
133
- await runCommand(
177
+ await execAsync(
134
178
  `gh secret set CLOUDFLARE_ACCOUNT_ID --body "${options.cloudflareAccount}"`
135
179
  );
136
180
  }
181
+ this.info(`Setting RAILWAY_SERVICE_NAME to "${options.backendName}" in GitHub...`);
182
+ await execAsync(`gh variable set RAILWAY_SERVICE_NAME --body "${options.backendName}"`);
183
+ this.info(`Setting CLOUDFLARE_PROJECT_NAME to "${options.frontendName}" in GitHub...`);
184
+ await execAsync(`gh variable set CLOUDFLARE_PROJECT_NAME --body "${options.frontendName}"`);
137
185
  } catch (e) {
138
186
  this.warn(
139
- "GitHub Secrets setup failed. Ensure you have the GitHub CLI (gh) installed and are logged in."
187
+ "GitHub configuration failed. Ensure you have the GitHub CLI (gh) installed and are logged in."
140
188
  );
141
189
  throw e;
142
190
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/commands/deploy.ts"],"sourcesContent":["import { BaseCommand, runCommand } from '@nexical/cli-core';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execAsync = promisify(exec);\n\ninterface DeployOptions {\n dryRun: boolean;\n railwayToken?: 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\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: '--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 Secrets Setup\n await this.setupGitHubSecrets(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 this.info('[Dry Run] Would run: railway init');\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 // If they provided a token, we should probably set it in the environment for subsequent calls\n if (options.railwayToken) {\n process.env.RAILWAY_TOKEN = options.railwayToken;\n }\n\n // Check if we are in a railway project\n try {\n await runCommand('railway status');\n } catch {\n this.info('No Railway project detected. Initializing...');\n await runCommand('railway init');\n }\n\n this.info('Adding PostgreSQL service if missing...');\n // railway add --database postgres is usually safe to run twice but we should check status\n const { stdout: status } = await execAsync('railway status');\n if (!status.includes('postgres')) {\n await runCommand('railway add --database postgres');\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 nexical-frontend');\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 // We assume project name 'nexical-frontend' for now, should be configurable.\n const projectName = 'nexical-frontend';\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 setupGitHubSecrets(options: DeployOptions) {\n this.info('Configuring GitHub Secrets...');\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 return;\n }\n\n try {\n // We need the Railway Project Token.\n // User might have provided it, or we try to get it from railway tokens?\n // Railway CLI doesn't easily expose the project token via CLI easily without a lot of parsing.\n // Usually users generate it in the UI.\n // If they provided it via --railway-token, we use it.\n\n if (options.railwayToken) {\n this.info('Setting RAILWAY_TOKEN in GitHub...');\n await runCommand(`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 runCommand(`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 runCommand(\n `gh secret set CLOUDFLARE_ACCOUNT_ID --body \"${options.cloudflareAccount}\"`,\n );\n }\n } catch (e: unknown) {\n this.warn(\n 'GitHub Secrets setup 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 { 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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexical/cli",
3
- "version": "0.11.3",
3
+ "version": "0.11.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "nexical": "./dist/index.js"
@@ -1,18 +1,45 @@
1
- import { BaseCommand, runCommand } from '@nexical/cli-core';
1
+ import { BaseCommand } from '@nexical/cli-core';
2
2
  import { exec } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
+ import path from 'node:path';
4
5
 
5
6
  const execAsync = promisify(exec);
6
7
 
7
8
  interface DeployOptions {
8
9
  dryRun: boolean;
9
10
  railwayToken?: string;
11
+ railwayName?: string;
12
+ backendName: string;
13
+ frontendName: string;
10
14
  cloudflareToken?: string;
11
15
  cloudflareAccount?: string;
12
16
  }
13
17
 
14
18
  export default class DeployCommand extends BaseCommand {
15
- static description = 'Deploy the application to Railway and Cloudflare.';
19
+ static description = `Deploy the application to Railway and Cloudflare.
20
+
21
+ ENVIRONMENT SETUP & PREREQUISITES:
22
+ 1. Install Required CLIs:
23
+ - Railway CLI: npm i -g @railway/cli
24
+ - Wrangler (Cloudflare): npm i -g wrangler
25
+ - GitHub CLI: https://cli.github.com/
26
+
27
+ 2. Authentication:
28
+ - Railway: Run 'railway login'
29
+ - GitHub: Run 'gh auth login'
30
+ - Cloudflare: Obtain an API Token (with Pages edit permissions) and your Account ID from the dashboard.
31
+
32
+ 3. Configuration:
33
+ Run this command with --cloudflare-token and --cloudflare-account to automate the full setup.
34
+ Optional: Use --railway-token if you prefer not to use the interactive login.
35
+ Optional: Use --railway-name to specify a custom Railway project name.
36
+ Optional: Use --backend-name to specify the Railway service name (default: nexical-backend).
37
+ Optional: Use --frontend-name to specify the Cloudflare Pages project name (default: nexical-frontend).
38
+
39
+ PROCESS:
40
+ - Provisions a PostgreSQL database on Railway (if missing).
41
+ - Creates a Cloudflare Pages project for the frontend.
42
+ - Syncs all necessary deployment secrets and variables to GitHub for CI/CD automation.`;
16
43
 
17
44
  static args = {
18
45
  options: [
@@ -25,6 +52,20 @@ export default class DeployCommand extends BaseCommand {
25
52
  name: '--railway-token <token>',
26
53
  description: 'Railway Project Token (optional if already logged in).',
27
54
  },
55
+ {
56
+ name: '--railway-name <name>',
57
+ description: 'Railway Project Name (used during initialization).',
58
+ },
59
+ {
60
+ name: '--backend-name <name>',
61
+ description: 'Backend service name on Railway.',
62
+ default: 'nexical-backend',
63
+ },
64
+ {
65
+ name: '--frontend-name <name>',
66
+ description: 'Frontend project name on Cloudflare.',
67
+ default: 'nexical-frontend',
68
+ },
28
69
  {
29
70
  name: '--cloudflare-token <token>',
30
71
  description: 'Cloudflare API Token.',
@@ -50,8 +91,8 @@ export default class DeployCommand extends BaseCommand {
50
91
  // 2. Cloudflare Setup
51
92
  await this.setupCloudflare(options);
52
93
 
53
- // 3. GitHub Secrets Setup
54
- await this.setupGitHubSecrets(options);
94
+ // 3. GitHub Configuration (Secrets & Variables)
95
+ await this.setupGitHubConfig(options);
55
96
 
56
97
  this.success('Deployment setup complete! Your application is being deployed.');
57
98
  } catch (error: unknown) {
@@ -68,7 +109,10 @@ export default class DeployCommand extends BaseCommand {
68
109
  this.info('Configuring Railway...');
69
110
 
70
111
  if (options.dryRun) {
71
- this.info('[Dry Run] Would run: railway init');
112
+ const initCmd = options.railwayName
113
+ ? `railway init --name ${options.railwayName}`
114
+ : 'railway init';
115
+ this.info(`[Dry Run] Would run: ${initCmd}`);
72
116
  this.info('[Dry Run] Would run: railway add --database postgres');
73
117
  return;
74
118
  }
@@ -79,24 +123,26 @@ export default class DeployCommand extends BaseCommand {
79
123
  // For now, let's assume we use 'railway link' if they passed a token or have it set.
80
124
 
81
125
  this.info('Ensuring Railway project is linked...');
82
- // If they provided a token, we should probably set it in the environment for subsequent calls
83
- if (options.railwayToken) {
84
- process.env.RAILWAY_TOKEN = options.railwayToken;
85
- }
126
+ // Note: We intentionally DO NOT set process.env.RAILWAY_TOKEN here.
127
+ // Management commands (init, status, add) require an interactive session (railway login).
128
+ // The provided --railway-token is reserved for GitHub Secrets setup (CI/CD).
129
+
130
+ const backendDir = path.join(process.cwd(), 'apps/backend');
86
131
 
87
- // Check if we are in a railway project
88
132
  try {
89
- await runCommand('railway status');
133
+ await execAsync('railway status', { cwd: backendDir });
90
134
  } catch {
91
- this.info('No Railway project detected. Initializing...');
92
- await runCommand('railway init');
135
+ const initCmd = options.railwayName
136
+ ? `railway init --name ${options.railwayName}`
137
+ : 'railway init';
138
+ this.info(`No Railway project detected in apps/backend. Initializing with: ${initCmd}`);
139
+ await execAsync(initCmd, { cwd: backendDir });
93
140
  }
94
141
 
95
- this.info('Adding PostgreSQL service if missing...');
96
- // railway add --database postgres is usually safe to run twice but we should check status
97
- const { stdout: status } = await execAsync('railway status');
142
+ this.info(`Adding PostgreSQL service if missing for "${options.backendName}"...`);
143
+ const { stdout: status } = await execAsync('railway status', { cwd: backendDir });
98
144
  if (!status.includes('postgres')) {
99
- await runCommand('railway add --database postgres');
145
+ await execAsync('railway add --database postgres', { cwd: backendDir });
100
146
  }
101
147
  } catch (e: unknown) {
102
148
  this.warn(
@@ -110,7 +156,7 @@ export default class DeployCommand extends BaseCommand {
110
156
  this.info('Configuring Cloudflare Pages...');
111
157
 
112
158
  if (options.dryRun) {
113
- this.info('[Dry Run] Would run: wrangler pages project create nexical-frontend');
159
+ this.info(`[Dry Run] Would run: wrangler pages project create ${options.frontendName}`);
114
160
  return;
115
161
  }
116
162
 
@@ -122,8 +168,7 @@ export default class DeployCommand extends BaseCommand {
122
168
 
123
169
  try {
124
170
  // Use wrangler to create project if it doesn't exist
125
- // We assume project name 'nexical-frontend' for now, should be configurable.
126
- const projectName = 'nexical-frontend';
171
+ const projectName = options.frontendName;
127
172
  this.info(`Ensuring Cloudflare Pages project "${projectName}" exists...`);
128
173
 
129
174
  try {
@@ -143,42 +188,49 @@ export default class DeployCommand extends BaseCommand {
143
188
  }
144
189
  }
145
190
 
146
- private async setupGitHubSecrets(options: DeployOptions) {
147
- this.info('Configuring GitHub Secrets...');
191
+ private async setupGitHubConfig(options: DeployOptions) {
192
+ this.info('Configuring GitHub Secrets and Variables...');
148
193
 
149
194
  if (options.dryRun) {
150
195
  this.info('[Dry Run] Would run: gh secret set RAILWAY_TOKEN');
151
196
  this.info('[Dry Run] Would run: gh secret set CLOUDFLARE_API_TOKEN');
152
197
  this.info('[Dry Run] Would run: gh secret set CLOUDFLARE_ACCOUNT_ID');
198
+ this.info(
199
+ `[Dry Run] Would run: gh variable set RAILWAY_SERVICE_NAME --body "${options.backendName}"`,
200
+ );
201
+ this.info(
202
+ `[Dry Run] Would run: gh variable set CLOUDFLARE_PROJECT_NAME --body "${options.frontendName}"`,
203
+ );
153
204
  return;
154
205
  }
155
206
 
156
207
  try {
157
- // We need the Railway Project Token.
158
- // User might have provided it, or we try to get it from railway tokens?
159
- // Railway CLI doesn't easily expose the project token via CLI easily without a lot of parsing.
160
- // Usually users generate it in the UI.
161
- // If they provided it via --railway-token, we use it.
162
-
163
208
  if (options.railwayToken) {
164
209
  this.info('Setting RAILWAY_TOKEN in GitHub...');
165
- await runCommand(`gh secret set RAILWAY_TOKEN --body "${options.railwayToken}"`);
210
+ await execAsync(`gh secret set RAILWAY_TOKEN --body "${options.railwayToken}"`);
166
211
  }
167
212
 
168
213
  if (options.cloudflareToken) {
169
214
  this.info('Setting CLOUDFLARE_API_TOKEN in GitHub...');
170
- await runCommand(`gh secret set CLOUDFLARE_API_TOKEN --body "${options.cloudflareToken}"`);
215
+ await execAsync(`gh secret set CLOUDFLARE_API_TOKEN --body "${options.cloudflareToken}"`);
171
216
  }
172
217
 
173
218
  if (options.cloudflareAccount) {
174
219
  this.info('Setting CLOUDFLARE_ACCOUNT_ID in GitHub...');
175
- await runCommand(
220
+ await execAsync(
176
221
  `gh secret set CLOUDFLARE_ACCOUNT_ID --body "${options.cloudflareAccount}"`,
177
222
  );
178
223
  }
224
+
225
+ // Set variables
226
+ this.info(`Setting RAILWAY_SERVICE_NAME to "${options.backendName}" in GitHub...`);
227
+ await execAsync(`gh variable set RAILWAY_SERVICE_NAME --body "${options.backendName}"`);
228
+
229
+ this.info(`Setting CLOUDFLARE_PROJECT_NAME to "${options.frontendName}" in GitHub...`);
230
+ await execAsync(`gh variable set CLOUDFLARE_PROJECT_NAME --body "${options.frontendName}"`);
179
231
  } catch (e: unknown) {
180
232
  this.warn(
181
- 'GitHub Secrets setup failed. Ensure you have the GitHub CLI (gh) installed and are logged in.',
233
+ 'GitHub configuration failed. Ensure you have the GitHub CLI (gh) installed and are logged in.',
182
234
  );
183
235
  throw e;
184
236
  }