@nexical/cli 0.11.4 → 0.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/chunk-2FKDEDDE.js +39 -0
  2. package/dist/chunk-2FKDEDDE.js.map +1 -0
  3. package/dist/chunk-2JW5BYZW.js +24 -0
  4. package/dist/chunk-2JW5BYZW.js.map +1 -0
  5. package/dist/chunk-EKCOW7FM.js +118 -0
  6. package/dist/chunk-EKCOW7FM.js.map +1 -0
  7. package/dist/index.js +13 -11
  8. package/dist/index.js.map +1 -1
  9. package/dist/src/commands/deploy.d.ts +2 -18
  10. package/dist/src/commands/deploy.js +100 -150
  11. package/dist/src/commands/deploy.js.map +1 -1
  12. package/dist/src/commands/init.js +3 -3
  13. package/dist/src/commands/module/add.js +3 -3
  14. package/dist/src/deploy/config-manager.d.ts +11 -0
  15. package/dist/src/deploy/config-manager.js +9 -0
  16. package/dist/src/deploy/config-manager.js.map +1 -0
  17. package/dist/src/deploy/providers/cloudflare.d.ts +12 -0
  18. package/dist/src/deploy/providers/cloudflare.js +113 -0
  19. package/dist/src/deploy/providers/cloudflare.js.map +1 -0
  20. package/dist/src/deploy/providers/github.d.ts +10 -0
  21. package/dist/src/deploy/providers/github.js +121 -0
  22. package/dist/src/deploy/providers/github.js.map +1 -0
  23. package/dist/src/deploy/providers/railway.d.ts +13 -0
  24. package/dist/src/deploy/providers/railway.js +114 -0
  25. package/dist/src/deploy/providers/railway.js.map +1 -0
  26. package/dist/src/deploy/registry.d.ts +15 -0
  27. package/dist/src/deploy/registry.js +9 -0
  28. package/dist/src/deploy/registry.js.map +1 -0
  29. package/dist/src/deploy/types.d.ts +47 -0
  30. package/dist/src/deploy/types.js +8 -0
  31. package/dist/src/deploy/types.js.map +1 -0
  32. package/dist/src/deploy/utils.d.ts +6 -0
  33. package/dist/src/deploy/utils.js +11 -0
  34. package/dist/src/deploy/utils.js.map +1 -0
  35. package/package.json +13 -11
  36. package/src/commands/deploy.ts +125 -193
  37. package/src/deploy/config-manager.ts +41 -0
  38. package/src/deploy/providers/cloudflare.ts +143 -0
  39. package/src/deploy/providers/github.ts +135 -0
  40. package/src/deploy/providers/railway.ts +143 -0
  41. package/src/deploy/registry.ts +136 -0
  42. package/src/deploy/types.ts +63 -0
  43. package/src/deploy/utils.ts +13 -0
@@ -0,0 +1,39 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ init_esm_shims
4
+ } from "./chunk-OYFWMYPG.js";
5
+
6
+ // src/deploy/config-manager.ts
7
+ init_esm_shims();
8
+ import fs from "fs/promises";
9
+ import path from "path";
10
+ import YAML from "yaml";
11
+ var ConfigManager = class {
12
+ configPath;
13
+ constructor(cwd) {
14
+ this.configPath = path.join(cwd, "nexical.yaml");
15
+ }
16
+ async load() {
17
+ try {
18
+ const content = await fs.readFile(this.configPath, "utf-8");
19
+ return YAML.parse(content);
20
+ } catch (error) {
21
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
22
+ return {};
23
+ }
24
+ throw error;
25
+ }
26
+ }
27
+ async save(config) {
28
+ const content = YAML.stringify(config);
29
+ await fs.writeFile(this.configPath, content, "utf-8");
30
+ }
31
+ exists() {
32
+ return fs.access(this.configPath).then(() => true).catch(() => false);
33
+ }
34
+ };
35
+
36
+ export {
37
+ ConfigManager
38
+ };
39
+ //# sourceMappingURL=chunk-2FKDEDDE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/deploy/config-manager.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport YAML from 'yaml';\nimport { NexicalConfig } from './types';\n\nexport class ConfigManager {\n private configPath: string;\n\n constructor(cwd: string) {\n this.configPath = path.join(cwd, 'nexical.yaml');\n }\n\n async load(): Promise<NexicalConfig> {\n try {\n const content = await fs.readFile(this.configPath, 'utf-8');\n return YAML.parse(content) as NexicalConfig;\n } catch (error: unknown) {\n if (\n error &&\n typeof error === 'object' &&\n 'code' in error &&\n (error as { code: unknown }).code === 'ENOENT'\n ) {\n return {};\n }\n throw error;\n }\n }\n\n async save(config: NexicalConfig): Promise<void> {\n const content = YAML.stringify(config);\n await fs.writeFile(this.configPath, content, 'utf-8');\n }\n\n exists(): Promise<boolean> {\n return fs\n .access(this.configPath)\n .then(() => true)\n .catch(() => false);\n }\n}\n"],"mappings":";;;;;;AAAA;AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,UAAU;AAGV,IAAM,gBAAN,MAAoB;AAAA,EACjB;AAAA,EAER,YAAY,KAAa;AACvB,SAAK,aAAa,KAAK,KAAK,KAAK,cAAc;AAAA,EACjD;AAAA,EAEA,MAAM,OAA+B;AACnC,QAAI;AACF,YAAM,UAAU,MAAM,GAAG,SAAS,KAAK,YAAY,OAAO;AAC1D,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,SAAS,OAAgB;AACvB,UACE,SACA,OAAO,UAAU,YACjB,UAAU,SACT,MAA4B,SAAS,UACtC;AACA,eAAO,CAAC;AAAA,MACV;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,QAAsC;AAC/C,UAAM,UAAU,KAAK,UAAU,MAAM;AACrC,UAAM,GAAG,UAAU,KAAK,YAAY,SAAS,OAAO;AAAA,EACtD;AAAA,EAEA,SAA2B;AACzB,WAAO,GACJ,OAAO,KAAK,UAAU,EACtB,KAAK,MAAM,IAAI,EACf,MAAM,MAAM,KAAK;AAAA,EACtB;AACF;","names":[]}
@@ -0,0 +1,24 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ init_esm_shims
4
+ } from "./chunk-OYFWMYPG.js";
5
+
6
+ // src/deploy/utils.ts
7
+ init_esm_shims();
8
+ import { exec } from "child_process";
9
+ import { promisify } from "util";
10
+ var execAsync = promisify(exec);
11
+ async function checkCommand(command) {
12
+ try {
13
+ await execAsync(command);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ export {
21
+ execAsync,
22
+ checkCommand
23
+ };
24
+ //# sourceMappingURL=chunk-2JW5BYZW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/deploy/utils.ts"],"sourcesContent":["import { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nexport const execAsync = promisify(exec);\n\nexport async function checkCommand(command: string): Promise<boolean> {\n try {\n await execAsync(command);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;AAAA;AAAA,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAEnB,IAAM,YAAY,UAAU,IAAI;AAEvC,eAAsB,aAAa,SAAmC;AACpE,MAAI;AACF,UAAM,UAAU,OAAO;AACvB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -0,0 +1,118 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ init_esm_shims
4
+ } from "./chunk-OYFWMYPG.js";
5
+
6
+ // src/deploy/registry.ts
7
+ init_esm_shims();
8
+ import path from "path";
9
+ import fs from "fs/promises";
10
+ import { logger } from "@nexical/cli-core";
11
+ var ProviderRegistry = class {
12
+ deploymentProviders = /* @__PURE__ */ new Map();
13
+ repositoryProviders = /* @__PURE__ */ new Map();
14
+ registerDeploymentProvider(provider) {
15
+ this.deploymentProviders.set(provider.name, provider);
16
+ }
17
+ registerRepositoryProvider(provider) {
18
+ this.repositoryProviders.set(provider.name, provider);
19
+ }
20
+ getDeploymentProvider(name) {
21
+ return this.deploymentProviders.get(name);
22
+ }
23
+ getRepositoryProvider(name) {
24
+ return this.repositoryProviders.get(name);
25
+ }
26
+ registerProviderFromModule(module, source) {
27
+ const moduleAny = module;
28
+ let provider = moduleAny.default;
29
+ if (!provider && Object.keys(moduleAny).length > 0) {
30
+ for (const key of Object.keys(moduleAny)) {
31
+ if (typeof moduleAny[key] === "function") {
32
+ provider = moduleAny[key];
33
+ break;
34
+ }
35
+ }
36
+ }
37
+ if (typeof provider === "function") {
38
+ try {
39
+ provider = new provider();
40
+ } catch {
41
+ }
42
+ }
43
+ if (provider) {
44
+ if (typeof provider.provision === "function" && typeof provider.getCIConfig === "function") {
45
+ logger.info(`[Registry] Loaded ${source} deployment provider: ${provider.name}`);
46
+ this.registerDeploymentProvider(provider);
47
+ } else if (typeof provider.configureSecrets === "function" && typeof provider.generateWorkflow === "function") {
48
+ logger.info(`[Registry] Loaded ${source} repository provider: ${provider.name}`);
49
+ this.registerRepositoryProvider(provider);
50
+ }
51
+ }
52
+ }
53
+ async loadCoreProviders() {
54
+ const dirname = path.dirname(new URL(import.meta.url).pathname);
55
+ const candidates = [
56
+ path.join(dirname, "providers"),
57
+ path.join(dirname, "src/deploy/providers")
58
+ ];
59
+ let providersDir = "";
60
+ for (const candidate of candidates) {
61
+ try {
62
+ await fs.access(candidate);
63
+ providersDir = candidate;
64
+ break;
65
+ } catch {
66
+ }
67
+ }
68
+ if (!providersDir) {
69
+ logger.warn(
70
+ `[Registry] Could not locate core providers directory. Checked: ${candidates.join(", ")}`
71
+ );
72
+ return;
73
+ }
74
+ try {
75
+ const files = await fs.readdir(providersDir);
76
+ for (const file of files) {
77
+ if (file.endsWith(".js") || file.endsWith(".ts") && !file.endsWith(".d.ts")) {
78
+ try {
79
+ const providerPath = path.join(providersDir, file);
80
+ const module = await import(providerPath);
81
+ this.registerProviderFromModule(module, "core");
82
+ } catch (e) {
83
+ const message = e instanceof Error ? e.message : String(e);
84
+ logger.warn(`Failed to load core provider from ${file}: ${message}`);
85
+ }
86
+ }
87
+ }
88
+ } catch (e) {
89
+ const message = e instanceof Error ? e.message : String(e);
90
+ logger.warn(`Failed to scan core providers at ${providersDir}: ${message}`);
91
+ }
92
+ }
93
+ async loadLocalProviders(cwd) {
94
+ const deployDir = path.join(cwd, "deploy");
95
+ try {
96
+ const files = await fs.readdir(deployDir);
97
+ for (const file of files) {
98
+ if (file.endsWith(".ts") || file.endsWith(".js")) {
99
+ try {
100
+ const providerPath = path.join(deployDir, file);
101
+ const jiti = (await import("jiti")).createJiti(import.meta.url);
102
+ const module = await jiti.import(providerPath);
103
+ this.registerProviderFromModule(module, "local");
104
+ } catch (e) {
105
+ const message = e instanceof Error ? e.message : String(e);
106
+ logger.warn(`Failed to load local provider from ${file}: ${message}`);
107
+ }
108
+ }
109
+ }
110
+ } catch {
111
+ }
112
+ }
113
+ };
114
+
115
+ export {
116
+ ProviderRegistry
117
+ };
118
+ //# sourceMappingURL=chunk-EKCOW7FM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/deploy/registry.ts"],"sourcesContent":["import path from 'node:path';\nimport fs from 'node:fs/promises';\nimport { logger } from '@nexical/cli-core';\nimport { DeploymentProvider, RepositoryProvider } from './types';\n\nexport class ProviderRegistry {\n private deploymentProviders: Map<string, DeploymentProvider> = new Map();\n private repositoryProviders: Map<string, RepositoryProvider> = new Map();\n\n registerDeploymentProvider(provider: DeploymentProvider) {\n this.deploymentProviders.set(provider.name, provider);\n }\n\n registerRepositoryProvider(provider: RepositoryProvider) {\n this.repositoryProviders.set(provider.name, provider);\n }\n\n getDeploymentProvider(name: string): DeploymentProvider | undefined {\n return this.deploymentProviders.get(name);\n }\n\n getRepositoryProvider(name: string): RepositoryProvider | undefined {\n return this.repositoryProviders.get(name);\n }\n\n private registerProviderFromModule(module: unknown, source: string) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const moduleAny = module as any;\n let provider = moduleAny.default;\n\n // Handle named exports if default is missing (fallback)\n if (!provider && Object.keys(moduleAny).length > 0) {\n // Try to find a class export that looks like a provider\n for (const key of Object.keys(moduleAny)) {\n if (typeof moduleAny[key] === 'function') {\n provider = moduleAny[key];\n break;\n }\n }\n }\n\n // If it's a class, instantiate it\n if (typeof provider === 'function') {\n try {\n provider = new provider();\n } catch {\n // Not a constructor or failed\n }\n }\n\n if (provider) {\n if (typeof provider.provision === 'function' && typeof provider.getCIConfig === 'function') {\n logger.info(`[Registry] Loaded ${source} deployment provider: ${provider.name}`);\n this.registerDeploymentProvider(provider as DeploymentProvider);\n } else if (\n typeof provider.configureSecrets === 'function' &&\n typeof provider.generateWorkflow === 'function'\n ) {\n logger.info(`[Registry] Loaded ${source} repository provider: ${provider.name}`);\n this.registerRepositoryProvider(provider as RepositoryProvider);\n }\n }\n }\n\n async loadCoreProviders() {\n const dirname = path.dirname(new URL(import.meta.url).pathname);\n\n // Try multiple paths to find the providers directory\n // 1. 'providers' - Standard source structure / flattened dist\n // 2. 'src/deploy/providers' - tsup output (chunk in root, files in src/...)\n const candidates = [\n path.join(dirname, 'providers'),\n path.join(dirname, 'src/deploy/providers'),\n ];\n\n let providersDir = '';\n for (const candidate of candidates) {\n try {\n await fs.access(candidate);\n providersDir = candidate;\n break;\n } catch {\n // Ignore missing dir\n }\n }\n\n if (!providersDir) {\n logger.warn(\n `[Registry] Could not locate core providers directory. Checked: ${candidates.join(', ')}`,\n );\n return;\n }\n\n try {\n const files = await fs.readdir(providersDir);\n for (const file of files) {\n if (file.endsWith('.js') || (file.endsWith('.ts') && !file.endsWith('.d.ts'))) {\n try {\n const providerPath = path.join(providersDir, file);\n const module = await import(providerPath);\n this.registerProviderFromModule(module, 'core');\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : String(e);\n logger.warn(`Failed to load core provider from ${file}: ${message}`);\n }\n }\n }\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : String(e);\n logger.warn(`Failed to scan core providers at ${providersDir}: ${message}`);\n }\n }\n\n async loadLocalProviders(cwd: string) {\n const deployDir = path.join(cwd, 'deploy');\n try {\n const files = await fs.readdir(deployDir);\n for (const file of files) {\n if (file.endsWith('.ts') || file.endsWith('.js')) {\n try {\n const providerPath = path.join(deployDir, file);\n // Use jiti to load TS/JS files dynamically\n const jiti = (await import('jiti')).createJiti(import.meta.url);\n const module = (await jiti.import(providerPath)) as unknown;\n this.registerProviderFromModule(module, 'local');\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : String(e);\n logger.warn(`Failed to load local provider from ${file}: ${message}`);\n }\n }\n }\n } catch {\n // Ignore if deploy dir doesn't exist\n }\n }\n}\n"],"mappings":"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","names":[]}
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.4",
18
+ version: "0.11.6",
19
19
  type: "module",
20
20
  bin: {
21
21
  nexical: "./dist/index.js"
@@ -44,31 +44,33 @@ var package_default = {
44
44
  },
45
45
  dependencies: {
46
46
  "@nexical/cli-core": "^0.1.12",
47
- yaml: "^2.3.4",
48
- "fast-glob": "^3.3.3"
47
+ dotenv: "^17.3.1",
48
+ "fast-glob": "^3.3.3",
49
+ jiti: "^2.6.1",
50
+ yaml: "^2.3.4"
49
51
  },
50
52
  devDependencies: {
53
+ "@eslint/js": "^9.39.2",
51
54
  "@types/fs-extra": "^11.0.4",
52
55
  "@types/node": "^20.10.0",
53
56
  "@vitest/coverage-v8": "^4.0.15",
54
- execa: "^9.6.1",
55
- "fs-extra": "^11.3.2",
56
- tsup: "^8.0.1",
57
- tsx: "^4.21.0",
58
- typescript: "^5.3.3",
59
- vitest: "^4.0.15",
60
- "@eslint/js": "^9.39.2",
61
57
  eslint: "^9.39.2",
62
58
  "eslint-config-prettier": "^10.1.8",
63
59
  "eslint-plugin-astro": "^1.5.0",
64
60
  "eslint-plugin-jsx-a11y": "^6.10.2",
65
61
  "eslint-plugin-react": "^7.37.5",
66
62
  "eslint-plugin-react-hooks": "^7.0.1",
63
+ execa: "^9.6.1",
64
+ "fs-extra": "^11.3.2",
67
65
  globals: "^17.2.0",
68
66
  husky: "^9.1.7",
69
67
  "lint-staged": "^16.2.7",
70
68
  prettier: "^3.8.1",
71
- "typescript-eslint": "^8.54.0"
69
+ tsup: "^8.0.1",
70
+ tsx: "^4.21.0",
71
+ typescript: "^5.3.3",
72
+ "typescript-eslint": "^8.54.0",
73
+ vitest: "^4.0.15"
72
74
  }
73
75
  };
74
76
 
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.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":[]}
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":[]}
@@ -1,35 +1,19 @@
1
1
  import { BaseCommand } from '@nexical/cli-core';
2
2
 
3
- interface DeployOptions {
4
- dryRun: boolean;
5
- railwayToken?: string;
6
- railwayName?: string;
7
- backendName: string;
8
- frontendName: string;
9
- cloudflareToken?: string;
10
- cloudflareAccount?: string;
11
- }
12
3
  declare class DeployCommand extends BaseCommand {
13
4
  static description: string;
14
5
  static args: {
15
6
  options: ({
16
- name: string;
17
- description: string;
18
- default: boolean;
19
- } | {
20
7
  name: string;
21
8
  description: string;
22
9
  default?: undefined;
23
10
  } | {
24
11
  name: string;
25
12
  description: string;
26
- default: string;
13
+ default: boolean;
27
14
  })[];
28
15
  };
29
- run(options: DeployOptions): Promise<void>;
30
- private setupRailway;
31
- private setupCloudflare;
32
- private setupGitHubConfig;
16
+ run(options: Record<string, unknown>): Promise<void>;
33
17
  }
34
18
 
35
19
  export { DeployCommand as default };
@@ -1,193 +1,143 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ ConfigManager
4
+ } from "../../chunk-2FKDEDDE.js";
5
+ import {
6
+ ProviderRegistry
7
+ } from "../../chunk-EKCOW7FM.js";
2
8
  import {
3
9
  init_esm_shims
4
10
  } from "../../chunk-OYFWMYPG.js";
5
11
 
6
12
  // src/commands/deploy.ts
7
13
  init_esm_shims();
8
- import { BaseCommand } from "@nexical/cli-core";
9
- import { exec } from "child_process";
10
- import { promisify } from "util";
11
14
  import path from "path";
12
- var execAsync = promisify(exec);
15
+ import dotenv from "dotenv";
16
+ import { BaseCommand } from "@nexical/cli-core";
13
17
  var DeployCommand = class extends BaseCommand {
14
- static description = `Deploy the application to Railway and Cloudflare.
18
+ static description = `Deploy the application based on nexical.yaml configuration.
15
19
 
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/
20
+ This command orchestrates the deployment of your frontend and backend applications
21
+ by interacting with the providers specified in your configuration file.
21
22
 
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.
23
+ CONFIGURATION:
24
+ - Requires a 'nexical.yaml' file in the project root.
25
+ - If the file or specific sections are missing, the CLI will prompt you to run an interactive setup
26
+ and save the configuration for future uses.
27
+ - Supports loading environment variables from a .env file in the project root.
26
28
 
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).
29
+ PROVIDERS:
30
+ - Backend: Railway, etc.
31
+ - Frontend: Cloudflare Pages, etc.
32
+ - Repository: GitHub, GitLab, etc.
33
33
 
34
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.`;
35
+ 1. Loads environment variables from '.env'.
36
+ 2. Loads configuration from 'nexical.yaml'.
37
+ 3. Provisions resources via the selected providers.
38
+ 4. Configures the repository (secrets/variables) for CI/CD.
39
+ 5. Generates CI/CD workflow files.`;
38
40
  static args = {
39
41
  options: [
40
42
  {
41
- name: "--dry-run",
42
- description: "Simulate the deployment process without making changes.",
43
- default: false
44
- },
45
- {
46
- name: "--railway-token <token>",
47
- description: "Railway Project Token (optional if already logged in)."
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"
43
+ name: "--backend <provider>",
44
+ description: "Override backend provider"
57
45
  },
58
46
  {
59
- name: "--frontend-name <name>",
60
- description: "Frontend project name on Cloudflare.",
61
- default: "nexical-frontend"
47
+ name: "--frontend <provider>",
48
+ description: "Override frontend provider"
62
49
  },
63
50
  {
64
- name: "--cloudflare-token <token>",
65
- description: "Cloudflare API Token."
51
+ name: "--repo <provider>",
52
+ description: "Override repositroy provider"
66
53
  },
67
54
  {
68
- name: "--cloudflare-account <id>",
69
- description: "Cloudflare Account ID."
55
+ name: "--dry-run",
56
+ description: "Simulate the deployment process",
57
+ default: false
70
58
  }
71
59
  ]
72
60
  };
73
61
  async run(options) {
74
- this.info("Starting Nexical Deployment Automation...");
75
- if (options.dryRun) {
76
- this.notice("DRY RUN MODE ENABLED");
62
+ this.info("Starting Nexical Deployment...");
63
+ dotenv.config({ path: path.join(process.cwd(), ".env") });
64
+ const configManager = new ConfigManager(process.cwd());
65
+ const config = await configManager.load();
66
+ const registry = new ProviderRegistry();
67
+ await registry.loadCoreProviders();
68
+ await registry.loadLocalProviders(process.cwd());
69
+ const backendProviderName = options.backend || config.deploy?.backend?.provider;
70
+ if (!backendProviderName) {
71
+ this.error(
72
+ "Backend provider not specified. Use --backend flag or configure 'deploy.backend.provider' in nexical.yaml."
73
+ );
77
74
  }
78
- try {
79
- await this.setupRailway(options);
80
- await this.setupCloudflare(options);
81
- await this.setupGitHubConfig(options);
82
- this.success("Deployment setup complete! Your application is being deployed.");
83
- } catch (error) {
84
- if (error instanceof Error) {
85
- this.error(`Deployment failed: ${error.message}`);
86
- } else {
87
- this.error(`Deployment failed: ${String(error)}`);
88
- }
89
- process.exit(1);
75
+ const frontendProviderName = options.frontend || config.deploy?.frontend?.provider;
76
+ if (!frontendProviderName) {
77
+ this.error(
78
+ "Frontend provider not specified. Use --frontend flag or configure 'deploy.frontend.provider' in nexical.yaml."
79
+ );
90
80
  }
91
- }
92
- async setupRailway(options) {
93
- this.info("Configuring Railway...");
94
- if (options.dryRun) {
95
- const initCmd = options.railwayName ? `railway init --name ${options.railwayName}` : "railway init";
96
- this.info(`[Dry Run] Would run: ${initCmd}`);
97
- this.info("[Dry Run] Would run: railway add --database postgres");
98
- return;
81
+ const repoProviderName = options.repo || config.deploy?.repository?.provider;
82
+ if (!repoProviderName) {
83
+ this.error(
84
+ "Repository provider not specified. Use --repo flag or configure 'deploy.repository.provider' in nexical.yaml."
85
+ );
99
86
  }
87
+ const backendProvider = registry.getDeploymentProvider(backendProviderName);
88
+ const frontendProvider = registry.getDeploymentProvider(frontendProviderName);
89
+ const repoProvider = registry.getRepositoryProvider(repoProviderName);
90
+ if (!backendProvider) throw new Error(`Backend provider '${backendProviderName}' not found.`);
91
+ if (!frontendProvider)
92
+ throw new Error(`Frontend provider '${frontendProviderName}' not found.`);
93
+ if (!repoProvider) throw new Error(`Repository provider '${repoProviderName}' not found.`);
94
+ const context = {
95
+ cwd: process.cwd(),
96
+ config,
97
+ options
98
+ };
99
+ this.info(`Provisioning Backend with ${backendProvider.name}...`);
100
+ await backendProvider.provision(context);
101
+ this.info(`Provisioning Frontend with ${frontendProvider.name}...`);
102
+ await frontendProvider.provision(context);
103
+ this.info(`Configuring Repository with ${repoProvider.name}...`);
104
+ const secrets = {};
105
+ this.info(`Resolving secrets from ${backendProvider.name}...`);
100
106
  try {
101
- this.info("Ensuring Railway project is linked...");
102
- const backendDir = path.join(process.cwd(), "apps/backend");
103
- try {
104
- await execAsync("railway status", { cwd: backendDir });
105
- } catch {
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 });
109
- }
110
- this.info(`Adding PostgreSQL service if missing for "${options.backendName}"...`);
111
- const { stdout: status } = await execAsync("railway status", { cwd: backendDir });
112
- if (!status.includes("postgres")) {
113
- await execAsync("railway add --database postgres", { cwd: backendDir });
114
- }
107
+ const backendSecrets = await backendProvider.getSecrets(context);
108
+ Object.assign(secrets, backendSecrets);
115
109
  } catch (e) {
116
- this.warn(
117
- "Railway setup encountered an issue. Ensure you are logged in with `railway login`."
118
- );
119
- throw e;
120
- }
121
- }
122
- async setupCloudflare(options) {
123
- this.info("Configuring Cloudflare Pages...");
124
- if (options.dryRun) {
125
- this.info(`[Dry Run] Would run: wrangler pages project create ${options.frontendName}`);
126
- return;
127
- }
128
- if (!options.cloudflareToken || !options.cloudflareAccount) {
129
- this.warn("Cloudflare credentials missing. Skipping automated Cloudflare setup.");
130
- this.info("You can manually set up Cloudflare Pages and add the secrets to GitHub.");
131
- return;
110
+ const message = e instanceof Error ? e.message : String(e);
111
+ this.error(`Failed to resolve secrets for ${backendProvider.name}: ${message}`);
132
112
  }
113
+ this.info(`Resolving secrets from ${frontendProvider.name}...`);
133
114
  try {
134
- const projectName = options.frontendName;
135
- this.info(`Ensuring Cloudflare Pages project "${projectName}" exists...`);
136
- try {
137
- await execAsync(`wrangler pages project create ${projectName} --production-branch main`, {
138
- env: {
139
- ...process.env,
140
- CLOUDFLARE_API_TOKEN: options.cloudflareToken,
141
- CLOUDFLARE_ACCOUNT_ID: options.cloudflareAccount
142
- }
143
- });
144
- } catch {
145
- this.info("Cloudflare project might already exist.");
146
- }
115
+ const frontendSecrets = await frontendProvider.getSecrets(context);
116
+ Object.assign(secrets, frontendSecrets);
147
117
  } catch (e) {
148
- this.warn("Cloudflare setup failed.");
149
- throw e;
118
+ const message = e instanceof Error ? e.message : String(e);
119
+ this.error(`Failed to resolve secrets for ${frontendProvider.name}: ${message}`);
150
120
  }
151
- }
152
- async setupGitHubConfig(options) {
153
- this.info("Configuring GitHub Secrets and Variables...");
154
- if (options.dryRun) {
155
- this.info("[Dry Run] Would run: gh secret set RAILWAY_TOKEN");
156
- this.info("[Dry Run] Would run: gh secret set CLOUDFLARE_API_TOKEN");
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
- );
164
- return;
121
+ await repoProvider.configureSecrets(context, secrets);
122
+ const variables = {};
123
+ try {
124
+ const backendVars = await backendProvider.getVariables(context);
125
+ Object.assign(variables, backendVars);
126
+ } catch (e) {
127
+ const message = e instanceof Error ? e.message : String(e);
128
+ this.error(`Failed to resolve variables for ${backendProvider.name}: ${message}`);
165
129
  }
166
130
  try {
167
- if (options.railwayToken) {
168
- this.info("Setting RAILWAY_TOKEN in GitHub...");
169
- await execAsync(`gh secret set RAILWAY_TOKEN --body "${options.railwayToken}"`);
170
- }
171
- if (options.cloudflareToken) {
172
- this.info("Setting CLOUDFLARE_API_TOKEN in GitHub...");
173
- await execAsync(`gh secret set CLOUDFLARE_API_TOKEN --body "${options.cloudflareToken}"`);
174
- }
175
- if (options.cloudflareAccount) {
176
- this.info("Setting CLOUDFLARE_ACCOUNT_ID in GitHub...");
177
- await execAsync(
178
- `gh secret set CLOUDFLARE_ACCOUNT_ID --body "${options.cloudflareAccount}"`
179
- );
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}"`);
131
+ const frontendVars = await frontendProvider.getVariables(context);
132
+ Object.assign(variables, frontendVars);
185
133
  } catch (e) {
186
- this.warn(
187
- "GitHub configuration failed. Ensure you have the GitHub CLI (gh) installed and are logged in."
188
- );
189
- throw e;
134
+ const message = e instanceof Error ? e.message : String(e);
135
+ this.error(`Failed to resolve variables for ${frontendProvider.name}: ${message}`);
190
136
  }
137
+ await repoProvider.configureVariables(context, variables);
138
+ this.info("Generating CI/CD Workflows...");
139
+ await repoProvider.generateWorkflow(context, [backendProvider, frontendProvider]);
140
+ this.success("Deployment configuration complete!");
191
141
  }
192
142
  };
193
143
  export {