@nexical/cli 0.11.5 → 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/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.5",
18
+ version: "0.11.6",
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.5\",\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 \"dotenv\": \"^17.3.1\",\n \"fast-glob\": \"^3.3.3\",\n \"jiti\": \"^2.6.1\",\n \"yaml\": \"^2.3.4\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.2\",\n \"@types/fs-extra\": \"^11.0.4\",\n \"@types/node\": \"^20.10.0\",\n \"@vitest/coverage-v8\": \"^4.0.15\",\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 \"execa\": \"^9.6.1\",\n \"fs-extra\": \"^11.3.2\",\n \"globals\": \"^17.2.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^16.2.7\",\n \"prettier\": \"^3.8.1\",\n \"tsup\": \"^8.0.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.3.3\",\n \"typescript-eslint\": \"^8.54.0\",\n \"vitest\": \"^4.0.15\"\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,QAAU;AAAA,IACV,aAAa;AAAA,IACb,MAAQ;AAAA,IACR,MAAQ;AAAA,EACV;AAAA,EACA,iBAAmB;AAAA,IACjB,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,uBAAuB;AAAA,IACvB,QAAU;AAAA,IACV,0BAA0B;AAAA,IAC1B,uBAAuB;AAAA,IACvB,0BAA0B;AAAA,IAC1B,uBAAuB;AAAA,IACvB,6BAA6B;AAAA,IAC7B,OAAS;AAAA,IACT,YAAY;AAAA,IACZ,SAAW;AAAA,IACX,OAAS;AAAA,IACT,eAAe;AAAA,IACf,UAAY;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,IACP,YAAc;AAAA,IACd,qBAAqB;AAAA,IACrB,QAAU;AAAA,EACZ;AACF;;;ADtDA,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.6\",\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 \"dotenv\": \"^17.3.1\",\n \"fast-glob\": \"^3.3.3\",\n \"jiti\": \"^2.6.1\",\n \"yaml\": \"^2.3.4\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.2\",\n \"@types/fs-extra\": \"^11.0.4\",\n \"@types/node\": \"^20.10.0\",\n \"@vitest/coverage-v8\": \"^4.0.15\",\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 \"execa\": \"^9.6.1\",\n \"fs-extra\": \"^11.3.2\",\n \"globals\": \"^17.2.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^16.2.7\",\n \"prettier\": \"^3.8.1\",\n \"tsup\": \"^8.0.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.3.3\",\n \"typescript-eslint\": \"^8.54.0\",\n \"vitest\": \"^4.0.15\"\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,QAAU;AAAA,IACV,aAAa;AAAA,IACb,MAAQ;AAAA,IACR,MAAQ;AAAA,EACV;AAAA,EACA,iBAAmB;AAAA,IACjB,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,uBAAuB;AAAA,IACvB,QAAU;AAAA,IACV,0BAA0B;AAAA,IAC1B,uBAAuB;AAAA,IACvB,0BAA0B;AAAA,IAC1B,uBAAuB;AAAA,IACvB,6BAA6B;AAAA,IAC7B,OAAS;AAAA,IACT,YAAY;AAAA,IACZ,SAAW;AAAA,IACX,OAAS;AAAA,IACT,eAAe;AAAA,IACf,UAAY;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,IACP,YAAc;AAAA,IACd,qBAAqB;AAAA,IACrB,QAAU;AAAA,EACZ;AACF;;;ADtDA,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":[]}
@@ -4,6 +4,7 @@ declare class RailwayProvider implements DeploymentProvider {
4
4
  name: string;
5
5
  type: "backend";
6
6
  provision(context: DeploymentContext): Promise<void>;
7
+ private resolveToken;
7
8
  getSecrets(context: DeploymentContext): Promise<Record<string, string>>;
8
9
  getVariables(context: DeploymentContext): Promise<Record<string, string>>;
9
10
  getCIConfig(): CIConfig;
@@ -21,65 +21,90 @@ var RailwayProvider = class {
21
21
  "Railway project name not found in nexical.yaml. Please configure 'deploy.backend.projectName'."
22
22
  );
23
23
  }
24
- const serviceName = railwayName || "nexical-backend";
25
24
  logger.info("Configuring Railway...");
26
25
  if (context.options.dryRun) {
27
26
  logger.info("[Dry Run] Would check Railway status and init project.");
28
27
  return;
29
28
  }
30
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).");
31
34
  try {
32
- await execAsync("railway status", { cwd: backendDir });
33
- } catch {
34
- const initCmd = railwayName ? `railway init --name ${railwayName}` : "railway init";
35
- logger.info(`No Railway project detected in apps/backend. Initializing with: ${initCmd}`);
36
- await execAsync(initCmd, { cwd: backendDir });
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
+ }
37
55
  }
38
- logger.info(`Adding PostgreSQL service if missing for "${serviceName}"...`);
39
- const { stdout: status } = await execAsync("railway status", { cwd: backendDir });
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
+ );
40
60
  if (!status.includes("postgres")) {
41
- await execAsync("railway add --database postgres", { cwd: backendDir });
61
+ try {
62
+ await execAsync("railway add --database postgres", { cwd: backendDir, env });
63
+ } catch {
64
+ logger.warn("Failed to auto-add PostgreSQL.");
65
+ }
42
66
  }
43
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}`);
44
75
  logger.warn(
45
- "Railway setup encountered an issue. Ensure you are logged in with `railway login`."
76
+ "Railway setup encountered an issue. Ensure you are logged in or have a valid token."
46
77
  );
47
- throw e;
48
78
  }
49
79
  }
50
- async getSecrets(context) {
80
+ resolveToken(context) {
51
81
  const options = context.config.deploy?.backend?.options || {};
52
- const secrets = {};
53
82
  const tokenEnvVar = typeof options.tokenEnvVar === "string" ? options.tokenEnvVar : void 0;
54
- const token = (tokenEnvVar ? process.env[tokenEnvVar] : void 0) || process.env.RAILWAY_TOKEN;
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 = {};
55
88
  if (!token) {
56
89
  throw new Error(
57
90
  `Railway Token not found. Please provide it via:
58
- 1. Configuring 'deploy.backend.options.tokenEnvVar' in nexical.yaml and setting that env var in .env
59
- 2. Setting RAILWAY_TOKEN in .env`
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`
60
94
  );
61
95
  }
62
- secrets["RAILWAY_TOKEN"] = token;
96
+ secrets["RAILWAY_API_TOKEN"] = token;
63
97
  return secrets;
64
98
  }
65
99
  async getVariables(context) {
66
- const railwayName = context.config.deploy?.backend?.projectName;
67
- if (!railwayName) {
68
- throw new Error(
69
- "Railway project name not found in nexical.yaml. Please configure 'deploy.backend.projectName'."
70
- );
71
- }
72
- const serviceName = railwayName || "nexical-backend";
73
- return {
74
- RAILWAY_SERVICE_NAME: serviceName
75
- };
100
+ return {};
76
101
  }
77
102
  getCIConfig() {
78
103
  return {
79
- secrets: ["RAILWAY_TOKEN"],
80
- variables: ["RAILWAY_SERVICE_NAME"],
104
+ secrets: ["RAILWAY_API_TOKEN"],
105
+ variables: [],
81
106
  installSteps: ["npm install -g @railway/cli"],
82
- deploySteps: ["railway up --service ${{ vars.RAILWAY_SERVICE_NAME }} --detach"]
107
+ deploySteps: ["railway up --detach"]
83
108
  };
84
109
  }
85
110
  };
@@ -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 // Resolve project name/token\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 // Service name defaults to project name for the primary backend\n const serviceName = railwayName || 'nexical-backend';\n // Note: Token is usually handled by `railway login` for CLI, but for CI we need it.\n // The provider might not need to know the token for `provision` if we rely on CLI auth.\n // However, we might need to export it for GitHub secrets.\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 try {\n await execAsync('railway status', { cwd: backendDir });\n } catch {\n const initCmd = railwayName ? `railway init --name ${railwayName}` : 'railway init';\n logger.info(`No Railway project detected in apps/backend. Initializing with: ${initCmd}`);\n await execAsync(initCmd, { cwd: backendDir });\n }\n\n logger.info(`Adding PostgreSQL service if missing for \"${serviceName}\"...`);\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 logger.warn(\n 'Railway setup encountered an issue. Ensure you are logged in with `railway login`.',\n );\n throw e;\n }\n }\n\n async getSecrets(context: DeploymentContext): Promise<Record<string, string>> {\n const options = context.config.deploy?.backend?.options || {};\n const secrets: Record<string, string> = {};\n\n // Resolve Railway Token\n // Priority: Configured Env Var > Default Env Var\n const tokenEnvVar = typeof options.tokenEnvVar === 'string' ? options.tokenEnvVar : undefined;\n const token = (tokenEnvVar ? process.env[tokenEnvVar] : undefined) || process.env.RAILWAY_TOKEN;\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. Configuring 'deploy.backend.options.tokenEnvVar' in nexical.yaml and setting that env var in .env\\n` +\n `2. Setting RAILWAY_TOKEN in .env`,\n );\n }\n\n secrets['RAILWAY_TOKEN'] = token;\n return secrets;\n }\n\n async getVariables(context: DeploymentContext): Promise<Record<string, string>> {\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 // Service name defaults to project name\n const serviceName = railwayName || 'nexical-backend';\n\n return {\n RAILWAY_SERVICE_NAME: serviceName,\n };\n }\n\n getCIConfig(): CIConfig {\n return {\n secrets: ['RAILWAY_TOKEN'],\n variables: ['RAILWAY_SERVICE_NAME'],\n installSteps: ['npm install -g @railway/cli'],\n deploySteps: ['railway up --service ${{ vars.RAILWAY_SERVICE_NAME }} --detach'],\n };\n }\n}\n"],"mappings":";;;;;;;;;AAAA;AAAA,OAAO,UAAU;AACjB,SAAS,cAAc;AAIhB,IAAM,kBAAN,MAAoD;AAAA,EACzD,OAAO;AAAA,EACP,OAAO;AAAA,EAEP,MAAM,UAAU,SAA2C;AACzD,UAAM,aAAa,KAAK,KAAK,QAAQ,KAAK,cAAc;AAExD,UAAM,cAAc,QAAQ,OAAO,QAAQ,SAAS;AAEpD,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAGA,UAAM,cAAc,eAAe;AAKnC,WAAO,KAAK,wBAAwB;AAEpC,QAAI,QAAQ,QAAQ,QAAQ;AAC1B,aAAO,KAAK,wDAAwD;AACpE;AAAA,IACF;AAEA,QAAI;AACF,UAAI;AACF,cAAM,UAAU,kBAAkB,EAAE,KAAK,WAAW,CAAC;AAAA,MACvD,QAAQ;AACN,cAAM,UAAU,cAAc,uBAAuB,WAAW,KAAK;AACrE,eAAO,KAAK,mEAAmE,OAAO,EAAE;AACxF,cAAM,UAAU,SAAS,EAAE,KAAK,WAAW,CAAC;AAAA,MAC9C;AAEA,aAAO,KAAK,6CAA6C,WAAW,MAAM;AAC1E,YAAM,EAAE,QAAQ,OAAO,IAAI,MAAM,UAAU,kBAAkB,EAAE,KAAK,WAAW,CAAC;AAChF,UAAI,CAAC,OAAO,SAAS,UAAU,GAAG;AAChC,cAAM,UAAU,mCAAmC,EAAE,KAAK,WAAW,CAAC;AAAA,MACxE;AAAA,IACF,SAAS,GAAY;AACnB,aAAO;AAAA,QACL;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,SAA6D;AAC5E,UAAM,UAAU,QAAQ,OAAO,QAAQ,SAAS,WAAW,CAAC;AAC5D,UAAM,UAAkC,CAAC;AAIzC,UAAM,cAAc,OAAO,QAAQ,gBAAgB,WAAW,QAAQ,cAAc;AACpF,UAAM,SAAS,cAAc,QAAQ,IAAI,WAAW,IAAI,WAAc,QAAQ,IAAI;AAElF,QAAI,CAAC,OAAO;AAEV,YAAM,IAAI;AAAA,QACR;AAAA;AAAA;AAAA,MAGF;AAAA,IACF;AAEA,YAAQ,eAAe,IAAI;AAC3B,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,aAAa,SAA6D;AAC9E,UAAM,cAAc,QAAQ,OAAO,QAAQ,SAAS;AAEpD,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAGA,UAAM,cAAc,eAAe;AAEnC,WAAO;AAAA,MACL,sBAAsB;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,cAAwB;AACtB,WAAO;AAAA,MACL,SAAS,CAAC,eAAe;AAAA,MACzB,WAAW,CAAC,sBAAsB;AAAA,MAClC,cAAc,CAAC,6BAA6B;AAAA,MAC5C,aAAa,CAAC,gEAAgE;AAAA,IAChF;AAAA,EACF;AACF;","names":[]}
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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexical/cli",
3
- "version": "0.11.5",
3
+ "version": "0.11.6",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "nexical": "./dist/index.js"
@@ -9,7 +9,6 @@ export class RailwayProvider implements DeploymentProvider {
9
9
 
10
10
  async provision(context: DeploymentContext): Promise<void> {
11
11
  const backendDir = path.join(context.cwd, 'apps/backend');
12
- // Resolve project name/token
13
12
  const railwayName = context.config.deploy?.backend?.projectName;
14
13
 
15
14
  if (!railwayName) {
@@ -18,12 +17,6 @@ export class RailwayProvider implements DeploymentProvider {
18
17
  );
19
18
  }
20
19
 
21
- // Service name defaults to project name for the primary backend
22
- const serviceName = railwayName || 'nexical-backend';
23
- // Note: Token is usually handled by `railway login` for CLI, but for CI we need it.
24
- // The provider might not need to know the token for `provision` if we rely on CLI auth.
25
- // However, we might need to export it for GitHub secrets.
26
-
27
20
  logger.info('Configuring Railway...');
28
21
 
29
22
  if (context.options.dryRun) {
@@ -32,72 +25,119 @@ export class RailwayProvider implements DeploymentProvider {
32
25
  }
33
26
 
34
27
  try {
28
+ // We consciously DO NOT pass any RAILWAY_API_TOKEN to the subprocess.
29
+ // The user may have an invalid token in their .env file (which process.env inherits).
30
+ // We want to force the Railway CLI to use the locally logged-in user's credentials.
31
+ const env = { ...process.env };
32
+ delete env.RAILWAY_API_TOKEN;
33
+ delete env.RAILWAY_TOKEN;
34
+
35
+ logger.info('Using local Railway CLI credentials (environment variables stripped).');
36
+
37
+ // Check status to see if we are linked to a project
35
38
  try {
36
- await execAsync('railway status', { cwd: backendDir });
37
- } catch {
38
- const initCmd = railwayName ? `railway init --name ${railwayName}` : 'railway init';
39
- logger.info(`No Railway project detected in apps/backend. Initializing with: ${initCmd}`);
40
- await execAsync(initCmd, { cwd: backendDir });
39
+ await execAsync('railway status', { cwd: backendDir, env });
40
+ } catch (error: unknown) {
41
+ const errMsg = error instanceof Error ? error.message : String(error);
42
+ const stderr = (error as { stderr?: string }).stderr || '';
43
+ const stdout = (error as { stdout?: string }).stdout || '';
44
+ const fullError = `${errMsg} ${stderr} ${stdout}`;
45
+
46
+ // If status fails, likely project doesn't exist locally or we aren't linked.
47
+ if (
48
+ fullError.includes('Project not found') ||
49
+ fullError.includes('No project') ||
50
+ fullError.includes('Project is deleted')
51
+ ) {
52
+ if (fullError.includes('Project is deleted')) {
53
+ logger.info('[Railway] Project is deleted. Unlinking...');
54
+ // If it's deleted, we might need to unlink first to clean up local config
55
+ await execAsync('railway unlink', { cwd: backendDir }).catch(() => {});
56
+ }
57
+ const initCmd = `railway init --name ${railwayName}`;
58
+ logger.info(`No active Railway project linked. Initializing with: ${initCmd}`);
59
+ await execAsync(initCmd, { cwd: backendDir, env });
60
+ } else if (
61
+ fullError.includes('Invalid RAILWAY_API_TOKEN') ||
62
+ fullError.includes('Unauthorized')
63
+ ) {
64
+ throw new Error('Railway authentication failed during status check.');
65
+ } else {
66
+ // Some other error (e.g. timeout), warn and try to proceed
67
+ logger.warn(`Railway status check failed: ${errMsg}. Proceeding.`);
68
+ }
41
69
  }
42
70
 
43
- logger.info(`Adding PostgreSQL service if missing for "${serviceName}"...`);
44
- const { stdout: status } = await execAsync('railway status', { cwd: backendDir });
71
+ logger.info(`Adding PostgreSQL service if missing for "${railwayName}"...`);
72
+ const { stdout: status } = await execAsync('railway status', { cwd: backendDir, env }).catch(
73
+ () => ({ stdout: '' }),
74
+ );
45
75
  if (!status.includes('postgres')) {
46
- await execAsync('railway add --database postgres', { cwd: backendDir });
76
+ try {
77
+ await execAsync('railway add --database postgres', { cwd: backendDir, env });
78
+ } catch {
79
+ logger.warn('Failed to auto-add PostgreSQL.');
80
+ }
47
81
  }
48
82
  } catch (e: unknown) {
83
+ // Rethrow explicit auth errors, otherwise warn
84
+ const errMsg = e instanceof Error ? e.message : String(e);
85
+
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ const stderr = (e as any).stderr || '';
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ const stdout = (e as any).stdout || '';
90
+
91
+ if (errMsg.includes('Railway authentication failed')) throw e;
92
+
93
+ logger.error(`Railway setup failed with error: ${errMsg}`);
94
+ if (stderr) logger.error(`[Railway stderr]: ${stderr}`);
95
+ if (stdout) logger.info(`[Railway stdout]: ${stdout}`);
96
+
49
97
  logger.warn(
50
- 'Railway setup encountered an issue. Ensure you are logged in with `railway login`.',
98
+ 'Railway setup encountered an issue. Ensure you are logged in or have a valid token.',
51
99
  );
52
- throw e;
53
100
  }
54
101
  }
55
102
 
56
- async getSecrets(context: DeploymentContext): Promise<Record<string, string>> {
103
+ private resolveToken(context: DeploymentContext): string | undefined {
57
104
  const options = context.config.deploy?.backend?.options || {};
58
- const secrets: Record<string, string> = {};
59
-
60
- // Resolve Railway Token
61
- // Priority: Configured Env Var > Default Env Var
62
105
  const tokenEnvVar = typeof options.tokenEnvVar === 'string' ? options.tokenEnvVar : undefined;
63
- const token = (tokenEnvVar ? process.env[tokenEnvVar] : undefined) || process.env.RAILWAY_TOKEN;
106
+ return (
107
+ process.env.RAILWAY_API_TOKEN?.trim() ||
108
+ (tokenEnvVar ? process.env[tokenEnvVar]?.trim() : undefined) ||
109
+ process.env.RAILWAY_TOKEN?.trim()
110
+ );
111
+ }
112
+
113
+ async getSecrets(context: DeploymentContext): Promise<Record<string, string>> {
114
+ const token = this.resolveToken(context);
115
+ const secrets: Record<string, string> = {};
64
116
 
65
117
  if (!token) {
66
118
  // Strict check: Error if token is missing
67
119
  throw new Error(
68
120
  `Railway Token not found. Please provide it via:\n` +
69
- `1. Configuring 'deploy.backend.options.tokenEnvVar' in nexical.yaml and setting that env var in .env\n` +
70
- `2. Setting RAILWAY_TOKEN in .env`,
121
+ `1. Setting RAILWAY_API_TOKEN in .env (Recommended)\n` +
122
+ `2. Configuring 'deploy.backend.options.tokenEnvVar' in nexical.yaml\n` +
123
+ `3. Setting RAILWAY_TOKEN in .env`,
71
124
  );
72
125
  }
73
126
 
74
- secrets['RAILWAY_TOKEN'] = token;
127
+ secrets['RAILWAY_API_TOKEN'] = token;
75
128
  return secrets;
76
129
  }
77
130
 
78
131
  async getVariables(context: DeploymentContext): Promise<Record<string, string>> {
79
- const railwayName = context.config.deploy?.backend?.projectName;
80
-
81
- if (!railwayName) {
82
- throw new Error(
83
- "Railway project name not found in nexical.yaml. Please configure 'deploy.backend.projectName'.",
84
- );
85
- }
86
-
87
- // Service name defaults to project name
88
- const serviceName = railwayName || 'nexical-backend';
89
-
90
- return {
91
- RAILWAY_SERVICE_NAME: serviceName,
92
- };
132
+ return {};
93
133
  }
94
134
 
95
135
  getCIConfig(): CIConfig {
96
136
  return {
97
- secrets: ['RAILWAY_TOKEN'],
98
- variables: ['RAILWAY_SERVICE_NAME'],
137
+ secrets: ['RAILWAY_API_TOKEN'],
138
+ variables: [],
99
139
  installSteps: ['npm install -g @railway/cli'],
100
- deploySteps: ['railway up --service ${{ vars.RAILWAY_SERVICE_NAME }} --detach'],
140
+ deploySteps: ['railway up --detach'],
101
141
  };
102
142
  }
103
143
  }