@nexical/cli 0.11.19 → 0.11.22

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
@@ -18,7 +18,7 @@ import { fileURLToPath } from "url";
18
18
  // package.json
19
19
  var package_default = {
20
20
  name: "@nexical/cli",
21
- version: "0.11.19",
21
+ version: "0.11.22",
22
22
  license: "Apache-2.0",
23
23
  type: "module",
24
24
  bin: {
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';\nimport { filterCommandDirectories } from './src/utils/filter.js';\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\nconst filteredAdditional = filterCommandDirectories(additionalCommands, coreCommandsDir);\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.19\",\n \"license\": \"Apache-2.0\",\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/ai\": \"^0.1.5\",\n \"@nexical/cli-core\": \"^0.1.16\",\n \"dotenv\": \"^17.3.1\",\n \"fast-glob\": \"^3.3.3\",\n \"glob\": \"^13.0.5\",\n \"jiti\": \"^2.6.1\",\n \"minimist\": \"^1.2.8\",\n \"yaml\": \"^2.8.2\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.2\",\n \"@types/fs-extra\": \"^11.0.4\",\n \"@types/minimist\": \"^1.2.5\",\n \"@types/node\": \"^25.3.0\",\n \"@types/nunjucks\": \"^3.2.6\",\n \"@vitest/coverage-v8\": \"^4.0.18\",\n \"eslint\": \"^9.39.2\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"eslint-plugin-astro\": \"^1.6.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.3\",\n \"globals\": \"^17.3.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^16.2.7\",\n \"prettier\": \"^3.8.1\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.9.3\",\n \"typescript-eslint\": \"^8.56.0\",\n \"vitest\": \"^4.0.18\"\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAAA;AACA,SAAS,KAAK,uBAAuB;AACrC,SAAS,qBAAqB;;;ACF9B;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,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,eAAe;AAAA,IACf,qBAAqB;AAAA,IACrB,QAAU;AAAA,IACV,aAAa;AAAA,IACb,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,UAAY;AAAA,IACZ,MAAQ;AAAA,EACV;AAAA,EACA,iBAAmB;AAAA,IACjB,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,mBAAmB;AAAA,IACnB,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;;;AD5DA,OAAO,UAAU;AAGjB,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;AAEjE,IAAM,qBAAqB,yBAAyB,oBAAoB,eAAe;AAEvF,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';\nimport { filterCommandDirectories } from './src/utils/filter.js';\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\nconst filteredAdditional = filterCommandDirectories(additionalCommands, coreCommandsDir);\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.22\",\n \"license\": \"Apache-2.0\",\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/ai\": \"^0.1.5\",\n \"@nexical/cli-core\": \"^0.1.16\",\n \"dotenv\": \"^17.3.1\",\n \"fast-glob\": \"^3.3.3\",\n \"glob\": \"^13.0.5\",\n \"jiti\": \"^2.6.1\",\n \"minimist\": \"^1.2.8\",\n \"yaml\": \"^2.8.2\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.2\",\n \"@types/fs-extra\": \"^11.0.4\",\n \"@types/minimist\": \"^1.2.5\",\n \"@types/node\": \"^25.3.0\",\n \"@types/nunjucks\": \"^3.2.6\",\n \"@vitest/coverage-v8\": \"^4.0.18\",\n \"eslint\": \"^9.39.2\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"eslint-plugin-astro\": \"^1.6.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.3\",\n \"globals\": \"^17.3.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^16.2.7\",\n \"prettier\": \"^3.8.1\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.9.3\",\n \"typescript-eslint\": \"^8.56.0\",\n \"vitest\": \"^4.0.18\"\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAAA;AACA,SAAS,KAAK,uBAAuB;AACrC,SAAS,qBAAqB;;;ACF9B;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,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,eAAe;AAAA,IACf,qBAAqB;AAAA,IACrB,QAAU;AAAA,IACV,aAAa;AAAA,IACb,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,UAAY;AAAA,IACZ,MAAQ;AAAA,EACV;AAAA,EACA,iBAAmB;AAAA,IACjB,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,mBAAmB;AAAA,IACnB,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;;;AD5DA,OAAO,UAAU;AAGjB,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;AAEjE,IAAM,qBAAqB,yBAAyB,oBAAoB,eAAe;AAEvF,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":[]}
@@ -99,14 +99,24 @@ var RailwayProvider = class {
99
99
  return secrets;
100
100
  }
101
101
  async getVariables(context) {
102
- return {};
102
+ const env = context.options.env || "production";
103
+ const baseProjectName = context.config.deploy?.backend?.projectName;
104
+ if (!baseProjectName) {
105
+ throw new Error(
106
+ "Railway project name not found in nexical.yaml. Please configure 'deploy.backend.projectName'."
107
+ );
108
+ }
109
+ const projectName = env === "production" ? baseProjectName : `${baseProjectName}-${env}`;
110
+ return {
111
+ RAILWAY_PROJECT_NAME: projectName
112
+ };
103
113
  }
104
114
  getCIConfig() {
105
115
  return {
106
116
  secrets: ["RAILWAY_API_TOKEN"],
107
117
  variables: [],
108
118
  installSteps: ["npm install -g @railway/cli"],
109
- deploySteps: ["railway up --detach"]
119
+ deploySteps: ["railway up --detach --project=${{ vars.RAILWAY_PROJECT_NAME }}"]
110
120
  };
111
121
  }
112
122
  };
@@ -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 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":["env"]}
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexical/cli",
3
- "version": "0.11.19",
3
+ "version": "0.11.22",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "bin": {
@@ -132,7 +132,19 @@ export class RailwayProvider implements DeploymentProvider {
132
132
  }
133
133
 
134
134
  async getVariables(context: DeploymentContext): Promise<Record<string, string>> {
135
- return {};
135
+ const env = (context.options.env as string) || 'production';
136
+ const baseProjectName = context.config.deploy?.backend?.projectName;
137
+
138
+ if (!baseProjectName) {
139
+ throw new Error(
140
+ "Railway project name not found in nexical.yaml. Please configure 'deploy.backend.projectName'.",
141
+ );
142
+ }
143
+
144
+ const projectName = env === 'production' ? baseProjectName : `${baseProjectName}-${env}`;
145
+ return {
146
+ RAILWAY_PROJECT_NAME: projectName,
147
+ };
136
148
  }
137
149
 
138
150
  getCIConfig(): CIConfig {
@@ -140,7 +152,7 @@ export class RailwayProvider implements DeploymentProvider {
140
152
  secrets: ['RAILWAY_API_TOKEN'],
141
153
  variables: [],
142
154
  installSteps: ['npm install -g @railway/cli'],
143
- deploySteps: ['railway up --detach'],
155
+ deploySteps: ['railway up --detach --project=${{ vars.RAILWAY_PROJECT_NAME }}'],
144
156
  };
145
157
  }
146
158
  }
@@ -37,8 +37,10 @@ describe('PromptCommand', () => {
37
37
  vi.spyOn(command, 'error').mockImplementation(() => {});
38
38
 
39
39
  // Default fs mocks
40
- vi.spyOn(fs, 'pathExists').mockResolvedValue(false);
41
- vi.spyOn(fs, 'readFile').mockResolvedValue('');
40
+ vi.mocked<(p: string) => Promise<boolean>>(fs.pathExists).mockResolvedValue(false);
41
+ vi.mocked(
42
+ fs.readFile as unknown as (p: string, e: string) => Promise<string>,
43
+ ).mockResolvedValue('');
42
44
 
43
45
  // Default PromptRunner mock
44
46
  vi.mocked(PromptRunner.run).mockResolvedValue(0);
@@ -107,9 +109,11 @@ describe('PromptCommand', () => {
107
109
  });
108
110
 
109
111
  it('should include generator agents prompts if they exist', async () => {
110
- vi.spyOn(fs, 'pathExists').mockImplementation(async (p: string | Buffer | URL) => {
111
- return (p as string).includes('packages/generator/prompts/agents');
112
- });
112
+ vi.mocked<(p: string) => Promise<boolean>>(fs.pathExists).mockImplementation(
113
+ async (p: string | Buffer | URL) => {
114
+ return (p as string).includes('packages/generator/prompts/agents');
115
+ },
116
+ );
113
117
 
114
118
  await command.run({ promptName: 'test-prompt' });
115
119
 
@@ -120,9 +124,11 @@ describe('PromptCommand', () => {
120
124
  });
121
125
 
122
126
  it('should resolve frontend module context', async () => {
123
- vi.spyOn(fs, 'pathExists').mockImplementation(async (p: string | Buffer | URL) => {
124
- return (p as string).includes('apps/frontend/modules/test-module');
125
- });
127
+ vi.mocked<(p: string) => Promise<boolean>>(fs.pathExists).mockImplementation(
128
+ async (p: string | Buffer | URL) => {
129
+ return (p as string).includes('apps/frontend/modules/test-module');
130
+ },
131
+ );
126
132
 
127
133
  await command.run({ promptName: 'test-prompt', module: 'test-module' });
128
134
 
@@ -138,9 +144,11 @@ describe('PromptCommand', () => {
138
144
  });
139
145
 
140
146
  it('should resolve backend module context', async () => {
141
- vi.spyOn(fs, 'pathExists').mockImplementation(async (p: string | Buffer | URL) => {
142
- return (p as string).includes('apps/backend/modules/test-module');
143
- });
147
+ vi.mocked<(p: string) => Promise<boolean>>(fs.pathExists).mockImplementation(
148
+ async (p: string | Buffer | URL) => {
149
+ return (p as string).includes('apps/backend/modules/test-module');
150
+ },
151
+ );
144
152
 
145
153
  await command.run({ promptName: 'test-prompt', m: 'test-module' });
146
154
 
@@ -156,7 +164,7 @@ describe('PromptCommand', () => {
156
164
  });
157
165
 
158
166
  it('should fail if module not found', async () => {
159
- vi.mocked(fs.pathExists).mockResolvedValue(false);
167
+ vi.mocked<(p: string) => Promise<boolean>>(fs.pathExists).mockResolvedValue(false);
160
168
 
161
169
  await command.run({ promptName: 'test-prompt', module: 'missing-module' });
162
170
 
@@ -167,10 +175,12 @@ describe('PromptCommand', () => {
167
175
  });
168
176
 
169
177
  it('should load AI config from nexical.yaml', async () => {
170
- vi.spyOn(fs, 'pathExists').mockImplementation(async (p: string | Buffer | URL) =>
171
- (p as string).includes('nexical.yaml'),
178
+ vi.mocked<(p: string) => Promise<boolean>>(fs.pathExists).mockImplementation(
179
+ async (p: string | Buffer | URL) => (p as string).includes('nexical.yaml'),
172
180
  );
173
- vi.spyOn(fs, 'readFile').mockResolvedValue('ai:\n provider: vertex');
181
+ vi.mocked(
182
+ fs.readFile as unknown as (p: string, e: string) => Promise<string>,
183
+ ).mockResolvedValue('ai:\n provider: vertex');
174
184
  vi.mocked(YAML.parse).mockReturnValue({ ai: { provider: 'vertex' } });
175
185
 
176
186
  await command.run({ promptName: 'test-prompt' });
@@ -183,10 +193,12 @@ describe('PromptCommand', () => {
183
193
  });
184
194
 
185
195
  it('should handle missing AI config in nexical.yaml', async () => {
186
- vi.spyOn(fs, 'pathExists').mockImplementation(async (p: string | Buffer | URL) =>
187
- (p as string).includes('nexical.yaml'),
196
+ vi.mocked<(p: string) => Promise<boolean>>(fs.pathExists).mockImplementation(
197
+ async (p: string | Buffer | URL) => (p as string).includes('nexical.yaml'),
188
198
  );
189
- vi.spyOn(fs, 'readFile').mockResolvedValue('name: my-project');
199
+ vi.mocked(
200
+ fs.readFile as unknown as (p: string, e: string) => Promise<string>,
201
+ ).mockResolvedValue('name: my-project');
190
202
  vi.mocked(YAML.parse).mockReturnValue({ name: 'my-project' });
191
203
 
192
204
  await command.run({ promptName: 'test-prompt' });
@@ -199,10 +211,12 @@ describe('PromptCommand', () => {
199
211
  });
200
212
 
201
213
  it('should handle falsy YAML parse result', async () => {
202
- vi.spyOn(fs, 'pathExists').mockImplementation(async (p: string | Buffer | URL) =>
203
- (p as string).includes('nexical.yaml'),
214
+ vi.mocked<(p: string) => Promise<boolean>>(fs.pathExists).mockImplementation(
215
+ async (p: string | Buffer | URL) => (p as string).includes('nexical.yaml'),
204
216
  );
205
- vi.spyOn(fs, 'readFile').mockResolvedValue('');
217
+ vi.mocked(
218
+ fs.readFile as unknown as (p: string, e: string) => Promise<string>,
219
+ ).mockResolvedValue('');
206
220
  vi.mocked(YAML.parse).mockReturnValue(null);
207
221
 
208
222
  await command.run({ promptName: 'test-prompt' });
@@ -215,10 +229,12 @@ describe('PromptCommand', () => {
215
229
  });
216
230
 
217
231
  it('should handle nexical.yaml parse errors', async () => {
218
- vi.spyOn(fs, 'pathExists').mockImplementation(async (p: string | Buffer | URL) =>
219
- (p as string).includes('nexical.yaml'),
232
+ vi.mocked<(p: string) => Promise<boolean>>(fs.pathExists).mockImplementation(
233
+ async (p: string | Buffer | URL) => (p as string).includes('nexical.yaml'),
220
234
  );
221
- vi.spyOn(fs, 'readFile').mockResolvedValue('invalid: yaml: :');
235
+ vi.mocked(
236
+ fs.readFile as unknown as (p: string, e: string) => Promise<string>,
237
+ ).mockResolvedValue('invalid: yaml: :');
222
238
  vi.mocked(YAML.parse).mockImplementation(() => {
223
239
  throw new Error('parse error');
224
240
  });
@@ -315,8 +315,10 @@ describe('RailwayProvider', () => {
315
315
  });
316
316
 
317
317
  describe('getVariables', () => {
318
- it('should return empty', async () => {
319
- expect(await provider.getVariables(mockContext)).toEqual({});
318
+ it('should return project name', async () => {
319
+ expect(await provider.getVariables(mockContext)).toEqual({
320
+ RAILWAY_PROJECT_NAME: 'my-proj',
321
+ });
320
322
  });
321
323
  });
322
324