@nexical/cli 0.11.3 → 0.11.5

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 +3 -12
  10. package/dist/src/commands/deploy.js +106 -108
  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 +12 -0
  24. package/dist/src/deploy/providers/railway.js +89 -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 +128 -144
  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 +103 -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.3",
18
+ version: "0.11.5",
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.3\",\n \"type\": \"module\",\n \"bin\": {\n \"nexical\": \"./dist/index.js\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"start\": \"node dist/index.js\",\n \"test\": \"npm run test:unit && npm run test:integration && npm run test:e2e\",\n \"test:unit\": \"vitest run --config vitest.config.ts --coverage\",\n \"test:integration\": \"vitest run --config vitest.integration.config.ts\",\n \"test:e2e\": \"npm run build && vitest run --config vitest.e2e.config.ts\",\n \"test:watch\": \"vitest\",\n \"format\": \"prettier --write .\",\n \"lint\": \"eslint .\",\n \"lint:fix\": \"eslint . --fix\",\n \"prepare\": \"husky\"\n },\n \"lint-staged\": {\n \"**/*\": [\n \"prettier --write --ignore-unknown\"\n ],\n \"**/*.{js,jsx,ts,tsx,astro}\": [\n \"eslint --fix\"\n ]\n },\n \"dependencies\": {\n \"@nexical/cli-core\": \"^0.1.12\",\n \"yaml\": \"^2.3.4\",\n \"fast-glob\": \"^3.3.3\"\n },\n \"devDependencies\": {\n \"@types/fs-extra\": \"^11.0.4\",\n \"@types/node\": \"^20.10.0\",\n \"@vitest/coverage-v8\": \"^4.0.15\",\n \"execa\": \"^9.6.1\",\n \"fs-extra\": \"^11.3.2\",\n \"tsup\": \"^8.0.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.3.3\",\n \"vitest\": \"^4.0.15\",\n \"@eslint/js\": \"^9.39.2\",\n \"eslint\": \"^9.39.2\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"eslint-plugin-astro\": \"^1.5.0\",\n \"eslint-plugin-jsx-a11y\": \"^6.10.2\",\n \"eslint-plugin-react\": \"^7.37.5\",\n \"eslint-plugin-react-hooks\": \"^7.0.1\",\n \"globals\": \"^17.2.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^16.2.7\",\n \"prettier\": \"^3.8.1\",\n \"typescript-eslint\": \"^8.54.0\"\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AACA,SAAS,KAAK,uBAAuB;AACrC,SAAS,qBAAqB;;;ACF9B;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,KAAO;AAAA,IACL,SAAW;AAAA,EACb;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,KAAO;AAAA,IACP,OAAS;AAAA,IACT,MAAQ;AAAA,IACR,aAAa;AAAA,IACb,oBAAoB;AAAA,IACpB,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,QAAU;AAAA,IACV,MAAQ;AAAA,IACR,YAAY;AAAA,IACZ,SAAW;AAAA,EACb;AAAA,EACA,eAAe;AAAA,IACb,QAAQ;AAAA,MACN;AAAA,IACF;AAAA,IACA,8BAA8B;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAAA,EACA,cAAgB;AAAA,IACd,qBAAqB;AAAA,IACrB,MAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,iBAAmB;AAAA,IACjB,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,uBAAuB;AAAA,IACvB,OAAS;AAAA,IACT,YAAY;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,IACP,YAAc;AAAA,IACd,QAAU;AAAA,IACV,cAAc;AAAA,IACd,QAAU;AAAA,IACV,0BAA0B;AAAA,IAC1B,uBAAuB;AAAA,IACvB,0BAA0B;AAAA,IAC1B,uBAAuB;AAAA,IACvB,6BAA6B;AAAA,IAC7B,SAAW;AAAA,IACX,OAAS;AAAA,IACT,eAAe;AAAA,IACf,UAAY;AAAA,IACZ,qBAAqB;AAAA,EACvB;AACF;;;ADpDA,OAAO,UAAU;AAEjB,IAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAE7D,IAAM,cAAc;AACpB,IAAM,cAAe,MAAM,gBAAgB,aAAa,QAAQ,IAAI,CAAC,KAAM,QAAQ,IAAI;AACvF,IAAM,kBAAkB,KAAK,QAAQ,WAAW,gBAAgB;AAChE,IAAM,qBAAqB,2BAA2B,WAAW;AAGjE,IAAM,qBAAqB,mBAAmB,OAAO,CAAC,QAAQ;AAC5D,QAAM,cAAc,KAAK,QAAQ,GAAG;AACpC,QAAM,eAAe,KAAK,QAAQ,eAAe;AAEjD,MAAI,gBAAgB,aAAc,QAAO;AAGzC,QAAM,aAAa,KAAK,KAAK,YAAY,OAAO,QAAQ,OAAO,UAAU;AACzE,QAAM,gBAAgB,KAAK,KAAK,YAAY,OAAO,QAAQ,OAAO,UAAU;AAC5E,QAAM,mBAAmB,KAAK,KAAK,YAAY,OAAO,OAAO,UAAU;AAEvE,MACE,YAAY,SAAS,UAAU,KAC/B,YAAY,SAAS,aAAa,KAClC,YAAY,SAAS,gBAAgB,GACrC;AACA,WAAO;AAAA,EACT;AAGA,MAAI,aAAa,SAAS,KAAK,KAAK,KAAK,KAAK,QAAQ,OAAO,UAAU,CAAC,GAAG;AACzE,UAAM,aAAa,aAAa;AAAA,MAC9B,KAAK,KAAK,KAAK,KAAK,QAAQ,OAAO,UAAU;AAAA,MAC7C,KAAK,KAAK,KAAK,KAAK,OAAO,UAAU;AAAA,IACvC;AACA,QAAI,gBAAgB,WAAY,QAAO;AAAA,EACzC;AAEA,SAAO;AACT,CAAC;AAED,IAAM,MAAM,IAAI,IAAI;AAAA,EAClB,SAAS,gBAAI;AAAA,EACb;AAAA,EACA,mBAAmB,CAAC,GAAG,oBAAI,IAAI,CAAC,iBAAiB,GAAG,kBAAkB,CAAC,CAAC;AAC1E,CAAC;AACD,IAAI,MAAM;","names":[]}
1
+ {"version":3,"sources":["../index.ts","../package.json"],"sourcesContent":["#!/usr/bin/env node\nimport { CLI, findProjectRoot } from '@nexical/cli-core';\nimport { fileURLToPath } from 'node:url';\nimport { discoverCommandDirectories } from './src/utils/discovery.js';\nimport pkg from './package.json';\nimport path from 'node:path';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst commandName = 'nexical';\nconst projectRoot = (await findProjectRoot(commandName, process.cwd())) || process.cwd();\nconst coreCommandsDir = path.resolve(__dirname, './src/commands');\nconst additionalCommands = discoverCommandDirectories(projectRoot);\n\n// Filter out duplicate core commands and source versions\nconst filteredAdditional = additionalCommands.filter((dir) => {\n const resolvedDir = path.resolve(dir);\n const resolvedCore = path.resolve(coreCommandsDir);\n\n if (resolvedDir === resolvedCore) return false;\n\n // Check if this is another instance of the core CLI commands (by checking path suffix)\n const coreSuffix = path.join('@nexical', 'cli', 'dist', 'src', 'commands');\n const coreSuffixSrc = path.join('packages', 'cli', 'dist', 'src', 'commands');\n const coreSuffixRawSrc = path.join('packages', 'cli', 'src', 'commands');\n\n if (\n resolvedDir.endsWith(coreSuffix) ||\n resolvedDir.endsWith(coreSuffixSrc) ||\n resolvedDir.endsWith(coreSuffixRawSrc)\n ) {\n return false;\n }\n\n // Handle mismatch between dist/src and src/\n if (resolvedCore.includes(path.join(path.sep, 'dist', 'src', 'commands'))) {\n const srcVersion = resolvedCore.replace(\n path.join(path.sep, 'dist', 'src', 'commands'),\n path.join(path.sep, 'src', 'commands'),\n );\n if (resolvedDir === srcVersion) return false;\n }\n\n return true;\n});\n\nconst app = new CLI({\n version: pkg.version,\n commandName: commandName,\n searchDirectories: [...new Set([coreCommandsDir, ...filteredAdditional])],\n});\napp.start();\n","{\n \"name\": \"@nexical/cli\",\n \"version\": \"0.11.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,28 +1,19 @@
1
1
  import { BaseCommand } from '@nexical/cli-core';
2
2
 
3
- interface DeployOptions {
4
- dryRun: boolean;
5
- railwayToken?: string;
6
- cloudflareToken?: string;
7
- cloudflareAccount?: string;
8
- }
9
3
  declare class DeployCommand extends BaseCommand {
10
4
  static description: string;
11
5
  static args: {
12
6
  options: ({
13
7
  name: string;
14
8
  description: string;
15
- default: boolean;
9
+ default?: undefined;
16
10
  } | {
17
11
  name: string;
18
12
  description: string;
19
- default?: undefined;
13
+ default: boolean;
20
14
  })[];
21
15
  };
22
- run(options: DeployOptions): Promise<void>;
23
- private setupRailway;
24
- private setupCloudflare;
25
- private setupGitHubSecrets;
16
+ run(options: Record<string, unknown>): Promise<void>;
26
17
  }
27
18
 
28
19
  export { DeployCommand as default };
@@ -1,145 +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, runCommand } from "@nexical/cli-core";
9
- import { exec } from "child_process";
10
- import { promisify } from "util";
11
- var execAsync = promisify(exec);
14
+ import path from "path";
15
+ import dotenv from "dotenv";
16
+ import { BaseCommand } from "@nexical/cli-core";
12
17
  var DeployCommand = class extends BaseCommand {
13
- static description = "Deploy the application to Railway and Cloudflare.";
18
+ static description = `Deploy the application based on nexical.yaml configuration.
19
+
20
+ This command orchestrates the deployment of your frontend and backend applications
21
+ by interacting with the providers specified in your configuration file.
22
+
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.
28
+
29
+ PROVIDERS:
30
+ - Backend: Railway, etc.
31
+ - Frontend: Cloudflare Pages, etc.
32
+ - Repository: GitHub, GitLab, etc.
33
+
34
+ PROCESS:
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.`;
14
40
  static args = {
15
41
  options: [
16
42
  {
17
- name: "--dry-run",
18
- description: "Simulate the deployment process without making changes.",
19
- default: false
43
+ name: "--backend <provider>",
44
+ description: "Override backend provider"
20
45
  },
21
46
  {
22
- name: "--railway-token <token>",
23
- description: "Railway Project Token (optional if already logged in)."
47
+ name: "--frontend <provider>",
48
+ description: "Override frontend provider"
24
49
  },
25
50
  {
26
- name: "--cloudflare-token <token>",
27
- description: "Cloudflare API Token."
51
+ name: "--repo <provider>",
52
+ description: "Override repositroy provider"
28
53
  },
29
54
  {
30
- name: "--cloudflare-account <id>",
31
- description: "Cloudflare Account ID."
55
+ name: "--dry-run",
56
+ description: "Simulate the deployment process",
57
+ default: false
32
58
  }
33
59
  ]
34
60
  };
35
61
  async run(options) {
36
- this.info("Starting Nexical Deployment Automation...");
37
- if (options.dryRun) {
38
- 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
+ );
39
74
  }
40
- try {
41
- await this.setupRailway(options);
42
- await this.setupCloudflare(options);
43
- await this.setupGitHubSecrets(options);
44
- this.success("Deployment setup complete! Your application is being deployed.");
45
- } catch (error) {
46
- if (error instanceof Error) {
47
- this.error(`Deployment failed: ${error.message}`);
48
- } else {
49
- this.error(`Deployment failed: ${String(error)}`);
50
- }
51
- 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
+ );
52
80
  }
53
- }
54
- async setupRailway(options) {
55
- this.info("Configuring Railway...");
56
- if (options.dryRun) {
57
- this.info("[Dry Run] Would run: railway init");
58
- this.info("[Dry Run] Would run: railway add --database postgres");
59
- 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
+ );
60
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}...`);
61
106
  try {
62
- this.info("Ensuring Railway project is linked...");
63
- if (options.railwayToken) {
64
- process.env.RAILWAY_TOKEN = options.railwayToken;
65
- }
66
- try {
67
- await runCommand("railway status");
68
- } catch {
69
- this.info("No Railway project detected. Initializing...");
70
- await runCommand("railway init");
71
- }
72
- this.info("Adding PostgreSQL service if missing...");
73
- const { stdout: status } = await execAsync("railway status");
74
- if (!status.includes("postgres")) {
75
- await runCommand("railway add --database postgres");
76
- }
107
+ const backendSecrets = await backendProvider.getSecrets(context);
108
+ Object.assign(secrets, backendSecrets);
77
109
  } catch (e) {
78
- this.warn(
79
- "Railway setup encountered an issue. Ensure you are logged in with `railway login`."
80
- );
81
- throw e;
82
- }
83
- }
84
- async setupCloudflare(options) {
85
- this.info("Configuring Cloudflare Pages...");
86
- if (options.dryRun) {
87
- this.info("[Dry Run] Would run: wrangler pages project create nexical-frontend");
88
- return;
89
- }
90
- if (!options.cloudflareToken || !options.cloudflareAccount) {
91
- this.warn("Cloudflare credentials missing. Skipping automated Cloudflare setup.");
92
- this.info("You can manually set up Cloudflare Pages and add the secrets to GitHub.");
93
- return;
110
+ const message = e instanceof Error ? e.message : String(e);
111
+ this.error(`Failed to resolve secrets for ${backendProvider.name}: ${message}`);
94
112
  }
113
+ this.info(`Resolving secrets from ${frontendProvider.name}...`);
95
114
  try {
96
- const projectName = "nexical-frontend";
97
- this.info(`Ensuring Cloudflare Pages project "${projectName}" exists...`);
98
- try {
99
- await execAsync(`wrangler pages project create ${projectName} --production-branch main`, {
100
- env: {
101
- ...process.env,
102
- CLOUDFLARE_API_TOKEN: options.cloudflareToken,
103
- CLOUDFLARE_ACCOUNT_ID: options.cloudflareAccount
104
- }
105
- });
106
- } catch {
107
- this.info("Cloudflare project might already exist.");
108
- }
115
+ const frontendSecrets = await frontendProvider.getSecrets(context);
116
+ Object.assign(secrets, frontendSecrets);
109
117
  } catch (e) {
110
- this.warn("Cloudflare setup failed.");
111
- throw e;
118
+ const message = e instanceof Error ? e.message : String(e);
119
+ this.error(`Failed to resolve secrets for ${frontendProvider.name}: ${message}`);
112
120
  }
113
- }
114
- async setupGitHubSecrets(options) {
115
- this.info("Configuring GitHub Secrets...");
116
- if (options.dryRun) {
117
- this.info("[Dry Run] Would run: gh secret set RAILWAY_TOKEN");
118
- this.info("[Dry Run] Would run: gh secret set CLOUDFLARE_API_TOKEN");
119
- this.info("[Dry Run] Would run: gh secret set CLOUDFLARE_ACCOUNT_ID");
120
- 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}`);
121
129
  }
122
130
  try {
123
- if (options.railwayToken) {
124
- this.info("Setting RAILWAY_TOKEN in GitHub...");
125
- await runCommand(`gh secret set RAILWAY_TOKEN --body "${options.railwayToken}"`);
126
- }
127
- if (options.cloudflareToken) {
128
- this.info("Setting CLOUDFLARE_API_TOKEN in GitHub...");
129
- await runCommand(`gh secret set CLOUDFLARE_API_TOKEN --body "${options.cloudflareToken}"`);
130
- }
131
- if (options.cloudflareAccount) {
132
- this.info("Setting CLOUDFLARE_ACCOUNT_ID in GitHub...");
133
- await runCommand(
134
- `gh secret set CLOUDFLARE_ACCOUNT_ID --body "${options.cloudflareAccount}"`
135
- );
136
- }
131
+ const frontendVars = await frontendProvider.getVariables(context);
132
+ Object.assign(variables, frontendVars);
137
133
  } catch (e) {
138
- this.warn(
139
- "GitHub Secrets setup failed. Ensure you have the GitHub CLI (gh) installed and are logged in."
140
- );
141
- throw e;
134
+ const message = e instanceof Error ? e.message : String(e);
135
+ this.error(`Failed to resolve variables for ${frontendProvider.name}: ${message}`);
142
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!");
143
141
  }
144
142
  };
145
143
  export {