@nexical/cli 0.11.1 → 0.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { fileURLToPath } from "url";
15
15
  // package.json
16
16
  var package_default = {
17
17
  name: "@nexical/cli",
18
- version: "0.11.1",
18
+ version: "0.11.3",
19
19
  type: "module",
20
20
  bin: {
21
21
  nexical: "./dist/index.js"
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../index.ts","../package.json"],"sourcesContent":["#!/usr/bin/env node\nimport { CLI, findProjectRoot } from '@nexical/cli-core';\nimport { fileURLToPath } from 'node:url';\nimport { discoverCommandDirectories } from './src/utils/discovery.js';\nimport pkg from './package.json';\nimport path from 'node:path';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst commandName = 'nexical';\nconst projectRoot = (await findProjectRoot(commandName, process.cwd())) || process.cwd();\nconst coreCommandsDir = path.resolve(__dirname, './src/commands');\nconst additionalCommands = discoverCommandDirectories(projectRoot);\n\n// Filter out duplicate core commands and source versions\nconst filteredAdditional = additionalCommands.filter((dir) => {\n const resolvedDir = path.resolve(dir);\n const resolvedCore = path.resolve(coreCommandsDir);\n\n if (resolvedDir === resolvedCore) return false;\n\n // Check if this is another instance of the core CLI commands (by checking path suffix)\n const coreSuffix = path.join('@nexical', 'cli', 'dist', 'src', 'commands');\n const coreSuffixSrc = path.join('packages', 'cli', 'dist', 'src', 'commands');\n const coreSuffixRawSrc = path.join('packages', 'cli', 'src', 'commands');\n\n if (\n resolvedDir.endsWith(coreSuffix) ||\n resolvedDir.endsWith(coreSuffixSrc) ||\n resolvedDir.endsWith(coreSuffixRawSrc)\n ) {\n return false;\n }\n\n // Handle mismatch between dist/src and src/\n if (resolvedCore.includes(path.join(path.sep, 'dist', 'src', 'commands'))) {\n const srcVersion = resolvedCore.replace(\n path.join(path.sep, 'dist', 'src', 'commands'),\n path.join(path.sep, 'src', 'commands'),\n );\n if (resolvedDir === srcVersion) return false;\n }\n\n return true;\n});\n\nconst app = new CLI({\n version: pkg.version,\n commandName: commandName,\n searchDirectories: [...new Set([coreCommandsDir, ...filteredAdditional])],\n});\napp.start();\n","{\n \"name\": \"@nexical/cli\",\n \"version\": \"0.11.1\",\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.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":[]}
@@ -0,0 +1,28 @@
1
+ import { BaseCommand } from '@nexical/cli-core';
2
+
3
+ interface DeployOptions {
4
+ dryRun: boolean;
5
+ railwayToken?: string;
6
+ cloudflareToken?: string;
7
+ cloudflareAccount?: string;
8
+ }
9
+ declare class DeployCommand extends BaseCommand {
10
+ static description: string;
11
+ static args: {
12
+ options: ({
13
+ name: string;
14
+ description: string;
15
+ default: boolean;
16
+ } | {
17
+ name: string;
18
+ description: string;
19
+ default?: undefined;
20
+ })[];
21
+ };
22
+ run(options: DeployOptions): Promise<void>;
23
+ private setupRailway;
24
+ private setupCloudflare;
25
+ private setupGitHubSecrets;
26
+ }
27
+
28
+ export { DeployCommand as default };
@@ -0,0 +1,148 @@
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/commands/deploy.ts
7
+ 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);
12
+ var DeployCommand = class extends BaseCommand {
13
+ static description = "Deploy the application to Railway and Cloudflare.";
14
+ static args = {
15
+ options: [
16
+ {
17
+ name: "--dry-run",
18
+ description: "Simulate the deployment process without making changes.",
19
+ default: false
20
+ },
21
+ {
22
+ name: "--railway-token <token>",
23
+ description: "Railway Project Token (optional if already logged in)."
24
+ },
25
+ {
26
+ name: "--cloudflare-token <token>",
27
+ description: "Cloudflare API Token."
28
+ },
29
+ {
30
+ name: "--cloudflare-account <id>",
31
+ description: "Cloudflare Account ID."
32
+ }
33
+ ]
34
+ };
35
+ async run(options) {
36
+ this.info("Starting Nexical Deployment Automation...");
37
+ if (options.dryRun) {
38
+ this.notice("DRY RUN MODE ENABLED");
39
+ }
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);
52
+ }
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;
60
+ }
61
+ 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
+ }
77
+ } 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;
94
+ }
95
+ 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
+ }
109
+ } catch (e) {
110
+ this.warn("Cloudflare setup failed.");
111
+ throw e;
112
+ }
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
+ }
122
+ 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
+ }
137
+ } 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;
142
+ }
143
+ }
144
+ };
145
+ export {
146
+ DeployCommand as default
147
+ };
148
+ //# sourceMappingURL=deploy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/commands/deploy.ts"],"sourcesContent":["import { BaseCommand, runCommand } from '@nexical/cli-core';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execAsync = promisify(exec);\n\ninterface DeployOptions {\n dryRun: boolean;\n railwayToken?: string;\n cloudflareToken?: string;\n cloudflareAccount?: string;\n}\n\nexport default class DeployCommand extends BaseCommand {\n static description = 'Deploy the application to Railway and Cloudflare.';\n\n static args = {\n options: [\n {\n name: '--dry-run',\n description: 'Simulate the deployment process without making changes.',\n default: false,\n },\n {\n name: '--railway-token <token>',\n description: 'Railway Project Token (optional if already logged in).',\n },\n {\n name: '--cloudflare-token <token>',\n description: 'Cloudflare API Token.',\n },\n {\n name: '--cloudflare-account <id>',\n description: 'Cloudflare Account ID.',\n },\n ],\n };\n\n async run(options: DeployOptions) {\n this.info('Starting Nexical Deployment Automation...');\n\n if (options.dryRun) {\n this.notice('DRY RUN MODE ENABLED');\n }\n\n try {\n // 1. Railway Setup\n await this.setupRailway(options);\n\n // 2. Cloudflare Setup\n await this.setupCloudflare(options);\n\n // 3. GitHub Secrets Setup\n await this.setupGitHubSecrets(options);\n\n this.success('Deployment setup complete! Your application is being deployed.');\n } catch (error: unknown) {\n if (error instanceof Error) {\n this.error(`Deployment failed: ${error.message}`);\n } else {\n this.error(`Deployment failed: ${String(error)}`);\n }\n process.exit(1);\n }\n }\n\n private async setupRailway(options: DeployOptions) {\n this.info('Configuring Railway...');\n\n if (options.dryRun) {\n this.info('[Dry Run] Would run: railway init');\n this.info('[Dry Run] Would run: railway add --database postgres');\n return;\n }\n\n try {\n // Check if railway project exists or init\n // Note: railway init might be interactive, so we might need to handle that or assume user has linked.\n // For now, let's assume we use 'railway link' if they passed a token or have it set.\n\n this.info('Ensuring Railway project is linked...');\n // If they provided a token, we should probably set it in the environment for subsequent calls\n if (options.railwayToken) {\n process.env.RAILWAY_TOKEN = options.railwayToken;\n }\n\n // Check if we are in a railway project\n try {\n await runCommand('railway status');\n } catch {\n this.info('No Railway project detected. Initializing...');\n await runCommand('railway init');\n }\n\n this.info('Adding PostgreSQL service if missing...');\n // railway add --database postgres is usually safe to run twice but we should check status\n const { stdout: status } = await execAsync('railway status');\n if (!status.includes('postgres')) {\n await runCommand('railway add --database postgres');\n }\n } catch (e: unknown) {\n this.warn(\n 'Railway setup encountered an issue. Ensure you are logged in with `railway login`.',\n );\n throw e;\n }\n }\n\n private async setupCloudflare(options: DeployOptions) {\n this.info('Configuring Cloudflare Pages...');\n\n if (options.dryRun) {\n this.info('[Dry Run] Would run: wrangler pages project create nexical-frontend');\n return;\n }\n\n if (!options.cloudflareToken || !options.cloudflareAccount) {\n this.warn('Cloudflare credentials missing. Skipping automated Cloudflare setup.');\n this.info('You can manually set up Cloudflare Pages and add the secrets to GitHub.');\n return;\n }\n\n try {\n // Use wrangler to create project if it doesn't exist\n // We assume project name 'nexical-frontend' for now, should be configurable.\n const projectName = 'nexical-frontend';\n this.info(`Ensuring Cloudflare Pages project \"${projectName}\" exists...`);\n\n try {\n await execAsync(`wrangler pages project create ${projectName} --production-branch main`, {\n env: {\n ...process.env,\n CLOUDFLARE_API_TOKEN: options.cloudflareToken,\n CLOUDFLARE_ACCOUNT_ID: options.cloudflareAccount,\n },\n });\n } catch {\n this.info('Cloudflare project might already exist.');\n }\n } catch (e: unknown) {\n this.warn('Cloudflare setup failed.');\n throw e;\n }\n }\n\n private async setupGitHubSecrets(options: DeployOptions) {\n this.info('Configuring GitHub Secrets...');\n\n if (options.dryRun) {\n this.info('[Dry Run] Would run: gh secret set RAILWAY_TOKEN');\n this.info('[Dry Run] Would run: gh secret set CLOUDFLARE_API_TOKEN');\n this.info('[Dry Run] Would run: gh secret set CLOUDFLARE_ACCOUNT_ID');\n return;\n }\n\n try {\n // We need the Railway Project Token.\n // User might have provided it, or we try to get it from railway tokens?\n // Railway CLI doesn't easily expose the project token via CLI easily without a lot of parsing.\n // Usually users generate it in the UI.\n // If they provided it via --railway-token, we use it.\n\n if (options.railwayToken) {\n this.info('Setting RAILWAY_TOKEN in GitHub...');\n await runCommand(`gh secret set RAILWAY_TOKEN --body \"${options.railwayToken}\"`);\n }\n\n if (options.cloudflareToken) {\n this.info('Setting CLOUDFLARE_API_TOKEN in GitHub...');\n await runCommand(`gh secret set CLOUDFLARE_API_TOKEN --body \"${options.cloudflareToken}\"`);\n }\n\n if (options.cloudflareAccount) {\n this.info('Setting CLOUDFLARE_ACCOUNT_ID in GitHub...');\n await runCommand(\n `gh secret set CLOUDFLARE_ACCOUNT_ID --body \"${options.cloudflareAccount}\"`,\n );\n }\n } catch (e: unknown) {\n this.warn(\n 'GitHub Secrets setup failed. Ensure you have the GitHub CLI (gh) installed and are logged in.',\n );\n throw e;\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -21,15 +21,7 @@ var SetupCommand = class extends BaseCommand {
21
21
  process.exit(1);
22
22
  }
23
23
  const apps = ["frontend", "backend"];
24
- const sharedAssets = [
25
- "prisma",
26
- "src",
27
- "public",
28
- "locales",
29
- "scripts",
30
- "astro.config.mjs",
31
- "tsconfig.json"
32
- ];
24
+ const sharedAssets = ["prisma", "src", "public", "locales", "scripts"];
33
25
  for (const app of apps) {
34
26
  const appDir = path.join(rootDir, "apps", app);
35
27
  if (!import_fs_extra.default.existsSync(appDir)) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/commands/setup.ts"],"sourcesContent":["import { BaseCommand, logger } from '@nexical/cli-core';\nimport fs from 'fs-extra';\nimport path from 'path';\n\nexport default class SetupCommand extends BaseCommand {\n static description = 'Setup the application environment by symlinking core assets.';\n\n async run() {\n // We assume we are in the project root\n // But the CLI might be run from anywhere?\n // findProjectRoot in index.ts handles finding the root.\n // BaseCommand has this.projectRoot?\n\n // BaseCommand doesn't expose projectRoot directly in current implementation seen in memory, checking source if possible?\n // InitCommand used process.cwd().\n\n // Let's assume process.cwd() is project root if run via `npm run setup` from root.\n const rootDir = process.cwd();\n\n // Verify we are in the right place\n if (!fs.existsSync(path.join(rootDir, 'core'))) {\n this.error('Could not find \"core\" directory. Are you in the project root?');\n process.exit(1);\n }\n\n const apps = ['frontend', 'backend'];\n const sharedAssets = [\n 'prisma',\n 'src',\n 'public',\n 'locales',\n 'scripts',\n 'astro.config.mjs',\n 'tsconfig.json',\n ]; // tsconfig might be needed if extended\n\n for (const app of apps) {\n const appDir = path.join(rootDir, 'apps', app);\n if (!fs.existsSync(appDir)) {\n this.warn(`App directory ${app} not found. Skipping.`);\n continue;\n }\n\n this.info(`Setting up ${app}...`);\n\n for (const asset of sharedAssets) {\n const source = path.join(rootDir, 'core', asset);\n const dest = path.join(appDir, asset);\n\n if (!fs.existsSync(source)) {\n this.warn(`Source asset ${asset} not found in core.`);\n continue;\n }\n\n try {\n // Remove existing destination if it exists (to ensure clean symlink)\n // Be careful not to delete real files if they aren't symlinks?\n // For now, we assume setup controls these.\n\n const destDir = path.dirname(dest);\n await fs.ensureDir(destDir);\n\n try {\n fs.lstatSync(dest);\n fs.removeSync(dest);\n } catch (e: unknown) {\n if (\n e &&\n typeof e === 'object' &&\n 'code' in e &&\n (e as { code: string }).code !== 'ENOENT'\n )\n throw e;\n }\n\n const relSource = path.relative(destDir, source);\n await fs.symlink(relSource, dest);\n\n logger.debug(`Symlinked ${asset} to ${app}`);\n } catch (e: unknown) {\n if (e instanceof Error) {\n this.error(`Failed to symlink ${asset} to ${app}: ${e.message}`);\n } else {\n this.error(`Failed to symlink ${asset} to ${app}: ${String(e)}`);\n }\n }\n }\n }\n\n this.success('Application setup complete.');\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AACA,sBAAe;AADf,SAAS,aAAa,cAAc;AAEpC,OAAO,UAAU;AAEjB,IAAqB,eAArB,cAA0C,YAAY;AAAA,EACpD,OAAO,cAAc;AAAA,EAErB,MAAM,MAAM;AAUV,UAAM,UAAU,QAAQ,IAAI;AAG5B,QAAI,CAAC,gBAAAA,QAAG,WAAW,KAAK,KAAK,SAAS,MAAM,CAAC,GAAG;AAC9C,WAAK,MAAM,+DAA+D;AAC1E,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,OAAO,CAAC,YAAY,SAAS;AACnC,UAAM,eAAe;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,eAAW,OAAO,MAAM;AACtB,YAAM,SAAS,KAAK,KAAK,SAAS,QAAQ,GAAG;AAC7C,UAAI,CAAC,gBAAAA,QAAG,WAAW,MAAM,GAAG;AAC1B,aAAK,KAAK,iBAAiB,GAAG,uBAAuB;AACrD;AAAA,MACF;AAEA,WAAK,KAAK,cAAc,GAAG,KAAK;AAEhC,iBAAW,SAAS,cAAc;AAChC,cAAM,SAAS,KAAK,KAAK,SAAS,QAAQ,KAAK;AAC/C,cAAM,OAAO,KAAK,KAAK,QAAQ,KAAK;AAEpC,YAAI,CAAC,gBAAAA,QAAG,WAAW,MAAM,GAAG;AAC1B,eAAK,KAAK,gBAAgB,KAAK,qBAAqB;AACpD;AAAA,QACF;AAEA,YAAI;AAKF,gBAAM,UAAU,KAAK,QAAQ,IAAI;AACjC,gBAAM,gBAAAA,QAAG,UAAU,OAAO;AAE1B,cAAI;AACF,4BAAAA,QAAG,UAAU,IAAI;AACjB,4BAAAA,QAAG,WAAW,IAAI;AAAA,UACpB,SAAS,GAAY;AACnB,gBACE,KACA,OAAO,MAAM,YACb,UAAU,KACT,EAAuB,SAAS;AAEjC,oBAAM;AAAA,UACV;AAEA,gBAAM,YAAY,KAAK,SAAS,SAAS,MAAM;AAC/C,gBAAM,gBAAAA,QAAG,QAAQ,WAAW,IAAI;AAEhC,iBAAO,MAAM,aAAa,KAAK,OAAO,GAAG,EAAE;AAAA,QAC7C,SAAS,GAAY;AACnB,cAAI,aAAa,OAAO;AACtB,iBAAK,MAAM,qBAAqB,KAAK,OAAO,GAAG,KAAK,EAAE,OAAO,EAAE;AAAA,UACjE,OAAO;AACL,iBAAK,MAAM,qBAAqB,KAAK,OAAO,GAAG,KAAK,OAAO,CAAC,CAAC,EAAE;AAAA,UACjE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,SAAK,QAAQ,6BAA6B;AAAA,EAC5C;AACF;","names":["fs"]}
1
+ {"version":3,"sources":["../../../src/commands/setup.ts"],"sourcesContent":["import { BaseCommand, logger } from '@nexical/cli-core';\nimport fs from 'fs-extra';\nimport path from 'path';\n\nexport default class SetupCommand extends BaseCommand {\n static description = 'Setup the application environment by symlinking core assets.';\n\n async run() {\n // We assume we are in the project root\n // But the CLI might be run from anywhere?\n // findProjectRoot in index.ts handles finding the root.\n // BaseCommand has this.projectRoot?\n\n // BaseCommand doesn't expose projectRoot directly in current implementation seen in memory, checking source if possible?\n // InitCommand used process.cwd().\n\n // Let's assume process.cwd() is project root if run via `npm run setup` from root.\n const rootDir = process.cwd();\n\n // Verify we are in the right place\n if (!fs.existsSync(path.join(rootDir, 'core'))) {\n this.error('Could not find \"core\" directory. Are you in the project root?');\n process.exit(1);\n }\n\n const apps = ['frontend', 'backend'];\n const sharedAssets = ['prisma', 'src', 'public', 'locales', 'scripts']; // tsconfig might be needed if extended\n\n for (const app of apps) {\n const appDir = path.join(rootDir, 'apps', app);\n if (!fs.existsSync(appDir)) {\n this.warn(`App directory ${app} not found. Skipping.`);\n continue;\n }\n\n this.info(`Setting up ${app}...`);\n\n for (const asset of sharedAssets) {\n const source = path.join(rootDir, 'core', asset);\n const dest = path.join(appDir, asset);\n\n if (!fs.existsSync(source)) {\n this.warn(`Source asset ${asset} not found in core.`);\n continue;\n }\n\n try {\n // Remove existing destination if it exists (to ensure clean symlink)\n // Be careful not to delete real files if they aren't symlinks?\n // For now, we assume setup controls these.\n\n const destDir = path.dirname(dest);\n await fs.ensureDir(destDir);\n\n try {\n fs.lstatSync(dest);\n fs.removeSync(dest);\n } catch (e: unknown) {\n if (\n e &&\n typeof e === 'object' &&\n 'code' in e &&\n (e as { code: string }).code !== 'ENOENT'\n )\n throw e;\n }\n\n const relSource = path.relative(destDir, source);\n await fs.symlink(relSource, dest);\n\n logger.debug(`Symlinked ${asset} to ${app}`);\n } catch (e: unknown) {\n if (e instanceof Error) {\n this.error(`Failed to symlink ${asset} to ${app}: ${e.message}`);\n } else {\n this.error(`Failed to symlink ${asset} to ${app}: ${String(e)}`);\n }\n }\n }\n }\n\n this.success('Application setup complete.');\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AACA,sBAAe;AADf,SAAS,aAAa,cAAc;AAEpC,OAAO,UAAU;AAEjB,IAAqB,eAArB,cAA0C,YAAY;AAAA,EACpD,OAAO,cAAc;AAAA,EAErB,MAAM,MAAM;AAUV,UAAM,UAAU,QAAQ,IAAI;AAG5B,QAAI,CAAC,gBAAAA,QAAG,WAAW,KAAK,KAAK,SAAS,MAAM,CAAC,GAAG;AAC9C,WAAK,MAAM,+DAA+D;AAC1E,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,OAAO,CAAC,YAAY,SAAS;AACnC,UAAM,eAAe,CAAC,UAAU,OAAO,UAAU,WAAW,SAAS;AAErE,eAAW,OAAO,MAAM;AACtB,YAAM,SAAS,KAAK,KAAK,SAAS,QAAQ,GAAG;AAC7C,UAAI,CAAC,gBAAAA,QAAG,WAAW,MAAM,GAAG;AAC1B,aAAK,KAAK,iBAAiB,GAAG,uBAAuB;AACrD;AAAA,MACF;AAEA,WAAK,KAAK,cAAc,GAAG,KAAK;AAEhC,iBAAW,SAAS,cAAc;AAChC,cAAM,SAAS,KAAK,KAAK,SAAS,QAAQ,KAAK;AAC/C,cAAM,OAAO,KAAK,KAAK,QAAQ,KAAK;AAEpC,YAAI,CAAC,gBAAAA,QAAG,WAAW,MAAM,GAAG;AAC1B,eAAK,KAAK,gBAAgB,KAAK,qBAAqB;AACpD;AAAA,QACF;AAEA,YAAI;AAKF,gBAAM,UAAU,KAAK,QAAQ,IAAI;AACjC,gBAAM,gBAAAA,QAAG,UAAU,OAAO;AAE1B,cAAI;AACF,4BAAAA,QAAG,UAAU,IAAI;AACjB,4BAAAA,QAAG,WAAW,IAAI;AAAA,UACpB,SAAS,GAAY;AACnB,gBACE,KACA,OAAO,MAAM,YACb,UAAU,KACT,EAAuB,SAAS;AAEjC,oBAAM;AAAA,UACV;AAEA,gBAAM,YAAY,KAAK,SAAS,SAAS,MAAM;AAC/C,gBAAM,gBAAAA,QAAG,QAAQ,WAAW,IAAI;AAEhC,iBAAO,MAAM,aAAa,KAAK,OAAO,GAAG,EAAE;AAAA,QAC7C,SAAS,GAAY;AACnB,cAAI,aAAa,OAAO;AACtB,iBAAK,MAAM,qBAAqB,KAAK,OAAO,GAAG,KAAK,EAAE,OAAO,EAAE;AAAA,UACjE,OAAO;AACL,iBAAK,MAAM,qBAAqB,KAAK,OAAO,GAAG,KAAK,OAAO,CAAC,CAAC,EAAE;AAAA,UACjE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,SAAK,QAAQ,6BAA6B;AAAA,EAC5C;AACF;","names":["fs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexical/cli",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "nexical": "./dist/index.js"
@@ -0,0 +1,186 @@
1
+ import { BaseCommand, runCommand } from '@nexical/cli-core';
2
+ import { exec } from 'node:child_process';
3
+ import { promisify } from 'node:util';
4
+
5
+ const execAsync = promisify(exec);
6
+
7
+ interface DeployOptions {
8
+ dryRun: boolean;
9
+ railwayToken?: string;
10
+ cloudflareToken?: string;
11
+ cloudflareAccount?: string;
12
+ }
13
+
14
+ export default class DeployCommand extends BaseCommand {
15
+ static description = 'Deploy the application to Railway and Cloudflare.';
16
+
17
+ static args = {
18
+ options: [
19
+ {
20
+ name: '--dry-run',
21
+ description: 'Simulate the deployment process without making changes.',
22
+ default: false,
23
+ },
24
+ {
25
+ name: '--railway-token <token>',
26
+ description: 'Railway Project Token (optional if already logged in).',
27
+ },
28
+ {
29
+ name: '--cloudflare-token <token>',
30
+ description: 'Cloudflare API Token.',
31
+ },
32
+ {
33
+ name: '--cloudflare-account <id>',
34
+ description: 'Cloudflare Account ID.',
35
+ },
36
+ ],
37
+ };
38
+
39
+ async run(options: DeployOptions) {
40
+ this.info('Starting Nexical Deployment Automation...');
41
+
42
+ if (options.dryRun) {
43
+ this.notice('DRY RUN MODE ENABLED');
44
+ }
45
+
46
+ try {
47
+ // 1. Railway Setup
48
+ await this.setupRailway(options);
49
+
50
+ // 2. Cloudflare Setup
51
+ await this.setupCloudflare(options);
52
+
53
+ // 3. GitHub Secrets Setup
54
+ await this.setupGitHubSecrets(options);
55
+
56
+ this.success('Deployment setup complete! Your application is being deployed.');
57
+ } catch (error: unknown) {
58
+ if (error instanceof Error) {
59
+ this.error(`Deployment failed: ${error.message}`);
60
+ } else {
61
+ this.error(`Deployment failed: ${String(error)}`);
62
+ }
63
+ process.exit(1);
64
+ }
65
+ }
66
+
67
+ private async setupRailway(options: DeployOptions) {
68
+ this.info('Configuring Railway...');
69
+
70
+ if (options.dryRun) {
71
+ this.info('[Dry Run] Would run: railway init');
72
+ this.info('[Dry Run] Would run: railway add --database postgres');
73
+ return;
74
+ }
75
+
76
+ try {
77
+ // Check if railway project exists or init
78
+ // Note: railway init might be interactive, so we might need to handle that or assume user has linked.
79
+ // For now, let's assume we use 'railway link' if they passed a token or have it set.
80
+
81
+ this.info('Ensuring Railway project is linked...');
82
+ // If they provided a token, we should probably set it in the environment for subsequent calls
83
+ if (options.railwayToken) {
84
+ process.env.RAILWAY_TOKEN = options.railwayToken;
85
+ }
86
+
87
+ // Check if we are in a railway project
88
+ try {
89
+ await runCommand('railway status');
90
+ } catch {
91
+ this.info('No Railway project detected. Initializing...');
92
+ await runCommand('railway init');
93
+ }
94
+
95
+ this.info('Adding PostgreSQL service if missing...');
96
+ // railway add --database postgres is usually safe to run twice but we should check status
97
+ const { stdout: status } = await execAsync('railway status');
98
+ if (!status.includes('postgres')) {
99
+ await runCommand('railway add --database postgres');
100
+ }
101
+ } catch (e: unknown) {
102
+ this.warn(
103
+ 'Railway setup encountered an issue. Ensure you are logged in with `railway login`.',
104
+ );
105
+ throw e;
106
+ }
107
+ }
108
+
109
+ private async setupCloudflare(options: DeployOptions) {
110
+ this.info('Configuring Cloudflare Pages...');
111
+
112
+ if (options.dryRun) {
113
+ this.info('[Dry Run] Would run: wrangler pages project create nexical-frontend');
114
+ return;
115
+ }
116
+
117
+ if (!options.cloudflareToken || !options.cloudflareAccount) {
118
+ this.warn('Cloudflare credentials missing. Skipping automated Cloudflare setup.');
119
+ this.info('You can manually set up Cloudflare Pages and add the secrets to GitHub.');
120
+ return;
121
+ }
122
+
123
+ try {
124
+ // Use wrangler to create project if it doesn't exist
125
+ // We assume project name 'nexical-frontend' for now, should be configurable.
126
+ const projectName = 'nexical-frontend';
127
+ this.info(`Ensuring Cloudflare Pages project "${projectName}" exists...`);
128
+
129
+ try {
130
+ await execAsync(`wrangler pages project create ${projectName} --production-branch main`, {
131
+ env: {
132
+ ...process.env,
133
+ CLOUDFLARE_API_TOKEN: options.cloudflareToken,
134
+ CLOUDFLARE_ACCOUNT_ID: options.cloudflareAccount,
135
+ },
136
+ });
137
+ } catch {
138
+ this.info('Cloudflare project might already exist.');
139
+ }
140
+ } catch (e: unknown) {
141
+ this.warn('Cloudflare setup failed.');
142
+ throw e;
143
+ }
144
+ }
145
+
146
+ private async setupGitHubSecrets(options: DeployOptions) {
147
+ this.info('Configuring GitHub Secrets...');
148
+
149
+ if (options.dryRun) {
150
+ this.info('[Dry Run] Would run: gh secret set RAILWAY_TOKEN');
151
+ this.info('[Dry Run] Would run: gh secret set CLOUDFLARE_API_TOKEN');
152
+ this.info('[Dry Run] Would run: gh secret set CLOUDFLARE_ACCOUNT_ID');
153
+ return;
154
+ }
155
+
156
+ try {
157
+ // We need the Railway Project Token.
158
+ // User might have provided it, or we try to get it from railway tokens?
159
+ // Railway CLI doesn't easily expose the project token via CLI easily without a lot of parsing.
160
+ // Usually users generate it in the UI.
161
+ // If they provided it via --railway-token, we use it.
162
+
163
+ if (options.railwayToken) {
164
+ this.info('Setting RAILWAY_TOKEN in GitHub...');
165
+ await runCommand(`gh secret set RAILWAY_TOKEN --body "${options.railwayToken}"`);
166
+ }
167
+
168
+ if (options.cloudflareToken) {
169
+ this.info('Setting CLOUDFLARE_API_TOKEN in GitHub...');
170
+ await runCommand(`gh secret set CLOUDFLARE_API_TOKEN --body "${options.cloudflareToken}"`);
171
+ }
172
+
173
+ if (options.cloudflareAccount) {
174
+ this.info('Setting CLOUDFLARE_ACCOUNT_ID in GitHub...');
175
+ await runCommand(
176
+ `gh secret set CLOUDFLARE_ACCOUNT_ID --body "${options.cloudflareAccount}"`,
177
+ );
178
+ }
179
+ } catch (e: unknown) {
180
+ this.warn(
181
+ 'GitHub Secrets setup failed. Ensure you have the GitHub CLI (gh) installed and are logged in.',
182
+ );
183
+ throw e;
184
+ }
185
+ }
186
+ }
@@ -24,15 +24,7 @@ export default class SetupCommand extends BaseCommand {
24
24
  }
25
25
 
26
26
  const apps = ['frontend', 'backend'];
27
- const sharedAssets = [
28
- 'prisma',
29
- 'src',
30
- 'public',
31
- 'locales',
32
- 'scripts',
33
- 'astro.config.mjs',
34
- 'tsconfig.json',
35
- ]; // tsconfig might be needed if extended
27
+ const sharedAssets = ['prisma', 'src', 'public', 'locales', 'scripts']; // tsconfig might be needed if extended
36
28
 
37
29
  for (const app of apps) {
38
30
  const appDir = path.join(rootDir, 'apps', app);
@@ -107,17 +107,9 @@ describe('SetupCommand', () => {
107
107
 
108
108
  // Check symlink calls
109
109
  // We have 2 apps * 7 shared assets = 14 symlinks
110
- // sharedAssets = ['prisma', 'src', 'public', 'locales', 'scripts', 'astro.config.mjs', 'tsconfig.json']
111
-
112
- const assets = [
113
- 'prisma',
114
- 'src',
115
- 'public',
116
- 'locales',
117
- 'scripts',
118
- 'astro.config.mjs',
119
- 'tsconfig.json',
120
- ];
110
+ // sharedAssets = ['prisma', 'src', 'public', 'locales', 'scripts']
111
+
112
+ const assets = ['prisma', 'src', 'public', 'locales', 'scripts'];
121
113
 
122
114
  for (const app of ['frontend', 'backend']) {
123
115
  for (const asset of assets) {