@nexical/cli 0.11.22 → 0.12.0

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 (92) hide show
  1. package/README.md +90 -235
  2. package/dist/{chunk-OYFWMYPG.js → chunk-6DE5Q66O.js} +6 -1
  3. package/dist/{chunk-OYFWMYPG.js.map → chunk-6DE5Q66O.js.map} +1 -1
  4. package/dist/chunk-G66GMEFE.js +31 -0
  5. package/dist/chunk-G66GMEFE.js.map +1 -0
  6. package/dist/{chunk-2FKDEDDE.js → chunk-HOVS7SCD.js} +16 -3
  7. package/dist/chunk-HOVS7SCD.js.map +1 -0
  8. package/dist/{chunk-GUUPSHWC.js → chunk-JEMIKBGX.js} +3 -3
  9. package/dist/chunk-JGAMEJTL.js +4101 -0
  10. package/dist/chunk-JGAMEJTL.js.map +1 -0
  11. package/dist/{chunk-OUGA4CB4.js → chunk-JS6WL5NS.js} +2 -2
  12. package/dist/{chunk-GEESHGE4.js → chunk-L2RUXOL4.js} +2 -2
  13. package/dist/{chunk-54HY52LH.js → chunk-QTJIGPQ3.js} +2 -2
  14. package/dist/{chunk-EKCOW7FM.js → chunk-USP2MI63.js} +41 -23
  15. package/dist/chunk-USP2MI63.js.map +1 -0
  16. package/dist/{chunk-2JW5BYZW.js → chunk-VKE7R2EZ.js} +2 -2
  17. package/dist/{chunk-AC4B3HPJ.js → chunk-XONR27KC.js} +2 -2
  18. package/dist/{chunk-PJIOCW2A.js → chunk-ZWNIZB3Q.js} +2 -2
  19. package/dist/index.js +5 -5
  20. package/dist/index.js.map +1 -1
  21. package/dist/src/commands/deploy.d.ts +3 -3
  22. package/dist/src/commands/deploy.js +134 -78
  23. package/dist/src/commands/deploy.js.map +1 -1
  24. package/dist/src/commands/init.js +5 -5
  25. package/dist/src/commands/module/add.js +4 -4
  26. package/dist/src/commands/module/list.js +2 -2
  27. package/dist/src/commands/module/remove.js +2 -2
  28. package/dist/src/commands/module/update.js +2 -2
  29. package/dist/src/commands/prompt.js +2 -2
  30. package/dist/src/commands/run.js +2 -2
  31. package/dist/src/commands/setup.js +3 -3
  32. package/dist/src/deploy/config-manager.js +3 -2
  33. package/dist/src/deploy/providers/cloudflare.d.ts +13 -8
  34. package/dist/src/deploy/providers/cloudflare.js +161 -52
  35. package/dist/src/deploy/providers/cloudflare.js.map +1 -1
  36. package/dist/src/deploy/providers/dns-cloudflare.d.ts +9 -0
  37. package/dist/src/deploy/providers/dns-cloudflare.js +123 -0
  38. package/dist/src/deploy/providers/dns-cloudflare.js.map +1 -0
  39. package/dist/src/deploy/providers/github.d.ts +6 -2
  40. package/dist/src/deploy/providers/github.js +37 -45
  41. package/dist/src/deploy/providers/github.js.map +1 -1
  42. package/dist/src/deploy/providers/railway.d.ts +17 -8
  43. package/dist/src/deploy/providers/railway.js +106 -45
  44. package/dist/src/deploy/providers/railway.js.map +1 -1
  45. package/dist/src/deploy/registry.d.ts +7 -4
  46. package/dist/src/deploy/registry.js +2 -2
  47. package/dist/src/deploy/schema.d.ts +188 -0
  48. package/dist/src/deploy/schema.js +11 -0
  49. package/dist/src/deploy/schema.js.map +1 -0
  50. package/dist/src/deploy/template-manager.d.ts +12 -0
  51. package/dist/src/deploy/template-manager.js +9 -0
  52. package/dist/src/deploy/template-manager.js.map +1 -0
  53. package/dist/src/deploy/types.d.ts +42 -17
  54. package/dist/src/deploy/types.js +1 -1
  55. package/dist/src/deploy/types.js.map +1 -1
  56. package/dist/src/deploy/utils.js +2 -2
  57. package/dist/src/utils/discovery.js +2 -2
  58. package/dist/src/utils/filter.js +2 -2
  59. package/dist/src/utils/git.js +2 -2
  60. package/dist/src/utils/url-resolver.js +2 -2
  61. package/dist/templates/github-workflow.yaml +23 -0
  62. package/package.json +2 -2
  63. package/src/commands/deploy.ts +157 -93
  64. package/src/deploy/config-manager.ts +14 -1
  65. package/src/deploy/providers/cloudflare.ts +203 -80
  66. package/src/deploy/providers/dns-cloudflare.ts +134 -0
  67. package/src/deploy/providers/github.ts +44 -47
  68. package/src/deploy/providers/railway.ts +135 -55
  69. package/src/deploy/registry.ts +49 -28
  70. package/src/deploy/schema.ts +39 -0
  71. package/src/deploy/template-manager.ts +32 -0
  72. package/src/deploy/templates/github-workflow.yaml +23 -0
  73. package/src/deploy/types.ts +48 -16
  74. package/test/integration/commands/deploy.integration.test.ts +79 -3
  75. package/test/unit/commands/deploy.test.ts +96 -198
  76. package/test/unit/deploy/config-manager.test.ts +9 -5
  77. package/test/unit/deploy/providers/cloudflare.test.ts +95 -96
  78. package/test/unit/deploy/providers/dns-cloudflare.test.ts +148 -0
  79. package/test/unit/deploy/providers/github.test.ts +43 -47
  80. package/test/unit/deploy/providers/railway.test.ts +50 -261
  81. package/test/unit/deploy/registry.test.ts +20 -17
  82. package/tsconfig.json +1 -1
  83. package/tsup.config.ts +3 -0
  84. package/dist/chunk-2FKDEDDE.js.map +0 -1
  85. package/dist/chunk-EKCOW7FM.js.map +0 -1
  86. /package/dist/{chunk-GUUPSHWC.js.map → chunk-JEMIKBGX.js.map} +0 -0
  87. /package/dist/{chunk-OUGA4CB4.js.map → chunk-JS6WL5NS.js.map} +0 -0
  88. /package/dist/{chunk-GEESHGE4.js.map → chunk-L2RUXOL4.js.map} +0 -0
  89. /package/dist/{chunk-54HY52LH.js.map → chunk-QTJIGPQ3.js.map} +0 -0
  90. /package/dist/{chunk-2JW5BYZW.js.map → chunk-VKE7R2EZ.js.map} +0 -0
  91. /package/dist/{chunk-AC4B3HPJ.js.map → chunk-XONR27KC.js.map} +0 -0
  92. /package/dist/{chunk-PJIOCW2A.js.map → chunk-ZWNIZB3Q.js.map} +0 -0
@@ -5,20 +5,20 @@ import {
5
5
  commit,
6
6
  renameRemote,
7
7
  updateSubmodules
8
- } from "../../chunk-GEESHGE4.js";
8
+ } from "../../chunk-L2RUXOL4.js";
9
9
  import {
10
10
  resolveGitUrl
11
- } from "../../chunk-PJIOCW2A.js";
11
+ } from "../../chunk-ZWNIZB3Q.js";
12
12
  import {
13
13
  SetupCommand
14
- } from "../../chunk-GUUPSHWC.js";
14
+ } from "../../chunk-JEMIKBGX.js";
15
15
  import {
16
16
  require_lib
17
- } from "../../chunk-OUGA4CB4.js";
17
+ } from "../../chunk-JS6WL5NS.js";
18
18
  import {
19
19
  __toESM,
20
20
  init_esm_shims
21
- } from "../../chunk-OYFWMYPG.js";
21
+ } from "../../chunk-6DE5Q66O.js";
22
22
 
23
23
  // src/commands/init.ts
24
24
  init_esm_shims();
@@ -3,17 +3,17 @@ import {
3
3
  addSubmodule,
4
4
  clone,
5
5
  getRemoteUrl
6
- } from "../../../chunk-GEESHGE4.js";
6
+ } from "../../../chunk-L2RUXOL4.js";
7
7
  import {
8
8
  resolveGitUrl
9
- } from "../../../chunk-PJIOCW2A.js";
9
+ } from "../../../chunk-ZWNIZB3Q.js";
10
10
  import {
11
11
  require_lib
12
- } from "../../../chunk-OUGA4CB4.js";
12
+ } from "../../../chunk-JS6WL5NS.js";
13
13
  import {
14
14
  __toESM,
15
15
  init_esm_shims
16
- } from "../../../chunk-OYFWMYPG.js";
16
+ } from "../../../chunk-6DE5Q66O.js";
17
17
 
18
18
  // src/commands/module/add.ts
19
19
  init_esm_shims();
@@ -1,11 +1,11 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  require_lib
4
- } from "../../../chunk-OUGA4CB4.js";
4
+ } from "../../../chunk-JS6WL5NS.js";
5
5
  import {
6
6
  __toESM,
7
7
  init_esm_shims
8
- } from "../../../chunk-OYFWMYPG.js";
8
+ } from "../../../chunk-6DE5Q66O.js";
9
9
 
10
10
  // src/commands/module/list.ts
11
11
  init_esm_shims();
@@ -1,11 +1,11 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  require_lib
4
- } from "../../../chunk-OUGA4CB4.js";
4
+ } from "../../../chunk-JS6WL5NS.js";
5
5
  import {
6
6
  __toESM,
7
7
  init_esm_shims
8
- } from "../../../chunk-OYFWMYPG.js";
8
+ } from "../../../chunk-6DE5Q66O.js";
9
9
 
10
10
  // src/commands/module/remove.ts
11
11
  init_esm_shims();
@@ -1,11 +1,11 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  require_lib
4
- } from "../../../chunk-OUGA4CB4.js";
4
+ } from "../../../chunk-JS6WL5NS.js";
5
5
  import {
6
6
  __toESM,
7
7
  init_esm_shims
8
- } from "../../../chunk-OYFWMYPG.js";
8
+ } from "../../../chunk-6DE5Q66O.js";
9
9
 
10
10
  // src/commands/module/update.ts
11
11
  init_esm_shims();
@@ -1,11 +1,11 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  require_lib
4
- } from "../../chunk-OUGA4CB4.js";
4
+ } from "../../chunk-JS6WL5NS.js";
5
5
  import {
6
6
  __toESM,
7
7
  init_esm_shims
8
- } from "../../chunk-OYFWMYPG.js";
8
+ } from "../../chunk-6DE5Q66O.js";
9
9
 
10
10
  // src/commands/prompt.ts
11
11
  init_esm_shims();
@@ -1,11 +1,11 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  require_lib
4
- } from "../../chunk-OUGA4CB4.js";
4
+ } from "../../chunk-JS6WL5NS.js";
5
5
  import {
6
6
  __toESM,
7
7
  init_esm_shims
8
- } from "../../chunk-OYFWMYPG.js";
8
+ } from "../../chunk-6DE5Q66O.js";
9
9
 
10
10
  // src/commands/run.ts
11
11
  init_esm_shims();
@@ -1,9 +1,9 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  SetupCommand
4
- } from "../../chunk-GUUPSHWC.js";
5
- import "../../chunk-OUGA4CB4.js";
6
- import "../../chunk-OYFWMYPG.js";
4
+ } from "../../chunk-JEMIKBGX.js";
5
+ import "../../chunk-JS6WL5NS.js";
6
+ import "../../chunk-6DE5Q66O.js";
7
7
  export {
8
8
  SetupCommand as default
9
9
  };
@@ -1,8 +1,9 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  ConfigManager
4
- } from "../../chunk-2FKDEDDE.js";
5
- import "../../chunk-OYFWMYPG.js";
4
+ } from "../../chunk-HOVS7SCD.js";
5
+ import "../../chunk-JGAMEJTL.js";
6
+ import "../../chunk-6DE5Q66O.js";
6
7
  export {
7
8
  ConfigManager
8
9
  };
@@ -1,12 +1,17 @@
1
- import { DeploymentProvider, DeploymentContext, CIConfig } from '../types.js';
1
+ import { HostingProvider, DeploymentContext, AppConfig, CIConfig } from '../types.js';
2
2
 
3
- declare class CloudflareProvider implements DeploymentProvider {
3
+ interface CloudflareConfig {
4
+ token?: string;
5
+ account?: string;
6
+ }
7
+ declare class CloudflareProvider implements HostingProvider {
4
8
  name: string;
5
- type: "frontend";
6
- provision(context: DeploymentContext): Promise<void>;
7
- getSecrets(context: DeploymentContext): Promise<Record<string, string>>;
8
- getVariables(context: DeploymentContext): Promise<Record<string, string>>;
9
- getCIConfig(): CIConfig;
9
+ provision(context: DeploymentContext, app: AppConfig): Promise<void>;
10
+ getSecrets(context: DeploymentContext, app: AppConfig): Promise<Record<string, string>>;
11
+ getVariables(context: DeploymentContext, app: AppConfig): Promise<Record<string, string>>;
12
+ getCIConfig(repoType: 'github' | 'gitlab', app: AppConfig): CIConfig;
13
+ deploy(context: DeploymentContext, app: AppConfig): Promise<void>;
14
+ getDefaultDnsTarget(app: AppConfig): string | undefined;
10
15
  }
11
16
 
12
- export { CloudflareProvider };
17
+ export { type CloudflareConfig, CloudflareProvider };
@@ -1,117 +1,226 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  execAsync
4
- } from "../../../chunk-2JW5BYZW.js";
4
+ } from "../../../chunk-VKE7R2EZ.js";
5
5
  import {
6
6
  init_esm_shims
7
- } from "../../../chunk-OYFWMYPG.js";
7
+ } from "../../../chunk-6DE5Q66O.js";
8
8
 
9
9
  // src/deploy/providers/cloudflare.ts
10
10
  init_esm_shims();
11
+ import path from "path";
11
12
  import { logger } from "@nexical/cli-core";
12
13
  var CloudflareProvider = class {
13
14
  name = "cloudflare";
14
- type = "frontend";
15
- async provision(context) {
15
+ async provision(context, app) {
16
16
  const env = context.options.env || "production";
17
- const baseProjectName = context.config.deploy?.frontend?.projectName;
17
+ const baseProjectName = app.projectName;
18
18
  if (!baseProjectName) {
19
19
  throw new Error(
20
- "Cloudflare project name not found in nexical.yaml. Please configure 'deploy.frontend.projectName'."
20
+ `Cloudflare project name not found for ${app.name}. Please configure 'projectName'.`
21
21
  );
22
22
  }
23
23
  const projectName = env === "production" ? baseProjectName : `${baseProjectName}-${env}`;
24
- const options = context.config.deploy?.frontend?.options || {};
25
- const apiTokenEnvVar = typeof options.apiTokenEnvVar === "string" ? options.apiTokenEnvVar : void 0;
26
- const apiToken = (typeof context.options.cloudflareToken === "string" ? context.options.cloudflareToken : void 0) || (apiTokenEnvVar ? process.env[apiTokenEnvVar] : void 0) || process.env.CLOUDFLARE_API_TOKEN;
27
- const accountIdEnvVar = typeof options.accountIdEnvVar === "string" ? options.accountIdEnvVar : void 0;
28
- const accountId = (typeof context.options.cloudflareAccount === "string" ? context.options.cloudflareAccount : void 0) || (accountIdEnvVar ? process.env[accountIdEnvVar] : void 0) || process.env.CLOUDFLARE_ACCOUNT_ID;
29
- logger.info("Configuring Cloudflare Pages...");
24
+ logger.info(`Configuring Cloudflare Pages for ${app.name}...`);
30
25
  if (context.options.dryRun) {
31
26
  logger.info(
32
- `[Dry Run] Would check Cloudflare Pages project "${projectName}" and create if missing.`
27
+ `[Dry Run] Would check Cloudflare status and provision project "${projectName}".`
33
28
  );
34
29
  return;
35
30
  }
36
- if (!apiToken || !accountId) {
37
- logger.warn("Cloudflare credentials missing. Skipping automated Cloudflare setup.");
38
- logger.info("You can manually set up Cloudflare Pages and add the secrets to GitHub.");
39
- return;
40
- }
41
31
  try {
32
+ const secrets = await this.getSecrets(context, app).catch(() => void 0);
33
+ if (!secrets) {
34
+ logger.warn(
35
+ `Cloudflare credentials missing for ${app.name}. Skipping provisioning. Ensure CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID are set.`
36
+ );
37
+ return;
38
+ }
39
+ const processEnv = {
40
+ ...process.env,
41
+ ...secrets,
42
+ NODE_OPTIONS: `${process.env.NODE_OPTIONS || ""} --dns-result-order=ipv4first`.trim()
43
+ };
42
44
  logger.info(`Ensuring Cloudflare Pages project "${projectName}" exists...`);
43
45
  try {
44
46
  await execAsync(`wrangler pages project create ${projectName} --production-branch main`, {
45
- env: {
46
- ...process.env,
47
- CLOUDFLARE_API_TOKEN: apiToken,
48
- CLOUDFLARE_ACCOUNT_ID: accountId
49
- }
47
+ env: processEnv
50
48
  });
51
- } catch {
52
- logger.info("Cloudflare project might already exist.");
49
+ } catch (err) {
50
+ const message = err instanceof Error ? err.message : String(err);
51
+ if (message.includes("already exists")) {
52
+ logger.info("Cloudflare project already exists.");
53
+ } else {
54
+ throw err;
55
+ }
56
+ }
57
+ if (app.domain) {
58
+ const domains = Array.isArray(app.domain) ? app.domain : [app.domain];
59
+ logger.info(
60
+ `Linking ${domains.length} domains to Cloudflare Pages project "${projectName}"...`
61
+ );
62
+ const apiToken = secrets.CLOUDFLARE_API_TOKEN;
63
+ const accountId = secrets.CLOUDFLARE_ACCOUNT_ID;
64
+ const listRes = await fetch(
65
+ `https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${projectName}/domains`,
66
+ {
67
+ headers: {
68
+ Authorization: `Bearer ${apiToken}`,
69
+ "Content-Type": "application/json"
70
+ }
71
+ }
72
+ );
73
+ if (!listRes.ok) {
74
+ const errorText = await listRes.text();
75
+ logger.warn(`Failed to fetch existing linked domains: ${errorText}`);
76
+ } else {
77
+ const listJson = await listRes.json();
78
+ const existingDomains = listJson.success ? listJson.result.map((d) => d.domain) : [];
79
+ for (const domain of domains) {
80
+ if (existingDomains.includes(domain)) {
81
+ logger.info(`[Cloudflare Pages] Domain ${domain} is already linked.`);
82
+ continue;
83
+ }
84
+ logger.info(`[Cloudflare Pages] Linking domain ${domain}...`);
85
+ const linkRes = await fetch(
86
+ `https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${projectName}/domains`,
87
+ {
88
+ method: "POST",
89
+ headers: {
90
+ Authorization: `Bearer ${apiToken}`,
91
+ "Content-Type": "application/json"
92
+ },
93
+ body: JSON.stringify({ name: domain })
94
+ // Pages API uses 'name' for the domain string in some versions, but docs suggest 'name' or just object. Let's verify 'name' vs 'domain'.
95
+ // Correction: The API docs say POST body should be { "name": "example.com" }
96
+ }
97
+ );
98
+ if (!linkRes.ok) {
99
+ const errorText = await linkRes.text();
100
+ if (errorText.includes("already exists") || errorText.includes("1008")) {
101
+ logger.info(`[Cloudflare Pages] Domain ${domain} already linked.`);
102
+ } else {
103
+ logger.warn(`[Cloudflare Pages] Failed to link domain ${domain}: ${errorText}`);
104
+ }
105
+ } else {
106
+ logger.success(`[Cloudflare Pages] Linked domain ${domain}.`);
107
+ }
108
+ }
109
+ }
53
110
  }
54
111
  } catch (e) {
55
112
  logger.warn("Cloudflare setup failed.");
56
113
  throw e;
57
114
  }
58
115
  }
59
- async getSecrets(context) {
60
- const options = context.config.deploy?.frontend?.options || {};
61
- const secrets = {};
62
- const apiTokenEnvVar = typeof options.apiTokenEnvVar === "string" ? options.apiTokenEnvVar : void 0;
63
- const apiToken = (apiTokenEnvVar ? process.env[apiTokenEnvVar] : void 0) || process.env.CLOUDFLARE_API_TOKEN;
116
+ async getSecrets(context, app) {
117
+ const cfConfig = app.cloudflare || {};
118
+ const apiTokenEnvVar = cfConfig.token;
119
+ const accountIdEnvVar = cfConfig.account;
120
+ const apiToken = process.env.CLOUDFLARE_API_TOKEN?.trim() || (apiTokenEnvVar ? process.env[apiTokenEnvVar]?.trim() : void 0);
121
+ const accountId = process.env.CLOUDFLARE_ACCOUNT_ID?.trim() || (accountIdEnvVar ? process.env[accountIdEnvVar]?.trim() : void 0);
64
122
  if (!apiToken) {
65
123
  throw new Error(
66
- `Cloudflare API Token not found. Please provide it via:
67
- 1. Configuring 'deploy.frontend.options.apiTokenEnvVar' in nexical.yaml and setting that env var in .env
68
- 2. Setting CLOUDFLARE_API_TOKEN in .env`
124
+ `Cloudflare API Token not found for ${app.name}. Please provide it via:
125
+ 1. Setting CLOUDFLARE_API_TOKEN in .env (Recommended)
126
+ 2. Configuring 'cloudflare.token' and setting that env var in .env`
69
127
  );
70
128
  }
71
- secrets["CLOUDFLARE_API_TOKEN"] = apiToken;
72
- const accountIdEnvVar = typeof options.accountIdEnvVar === "string" ? options.accountIdEnvVar : void 0;
73
- const accountId = (accountIdEnvVar ? process.env[accountIdEnvVar] : void 0) || process.env.CLOUDFLARE_ACCOUNT_ID;
74
129
  if (!accountId) {
75
130
  throw new Error(
76
- `Cloudflare Account ID not found. Please provide it via:
77
- 1. Configuring 'deploy.frontend.options.accountIdEnvVar' in nexical.yaml and setting that env var in .env
78
- 2. Setting CLOUDFLARE_ACCOUNT_ID in .env`
131
+ `Cloudflare Account ID not found for ${app.name}. Please provide it via:
132
+ 1. Setting CLOUDFLARE_ACCOUNT_ID in .env (Recommended)
133
+ 2. Configuring 'cloudflare.account' and setting that env var in .env`
79
134
  );
80
135
  }
81
- secrets["CLOUDFLARE_ACCOUNT_ID"] = accountId;
136
+ const secrets = {
137
+ CLOUDFLARE_API_TOKEN: apiToken,
138
+ CLOUDFLARE_ACCOUNT_ID: accountId
139
+ };
140
+ if (app.secrets) {
141
+ for (const [key, envVar] of Object.entries(app.secrets)) {
142
+ const value = process.env[envVar];
143
+ if (!value) {
144
+ throw new Error(`Custom secret '${key}' mapping failed: Env var '${envVar}' not found.`);
145
+ }
146
+ secrets[key] = value;
147
+ }
148
+ }
82
149
  return secrets;
83
150
  }
84
- async getVariables(context) {
151
+ async getVariables(context, app) {
85
152
  const env = context.options.env || "production";
86
- const baseProjectName = context.config.deploy?.frontend?.projectName;
153
+ const baseProjectName = app.projectName;
87
154
  if (!baseProjectName) {
88
- throw new Error(
89
- "Cloudflare project name not found in nexical.yaml. Please configure 'deploy.frontend.projectName'."
90
- );
155
+ throw new Error(`Cloudflare project name not found for ${app.name}.`);
91
156
  }
92
157
  const projectName = env === "production" ? baseProjectName : `${baseProjectName}-${env}`;
93
- return {
94
- CLOUDFLARE_PROJECT_NAME: projectName
158
+ const varName = `CLOUDFLARE_PROJECT_NAME_${app.name.toUpperCase().replace(/-/g, "_")}`;
159
+ const result = {
160
+ [varName]: projectName
95
161
  };
162
+ if (app.env) {
163
+ for (const [key, value] of Object.entries(app.env)) {
164
+ const resolvedValue = process.env[value] || value;
165
+ result[key] = resolvedValue;
166
+ }
167
+ }
168
+ return result;
96
169
  }
97
- getCIConfig() {
170
+ getCIConfig(repoType, app) {
171
+ const varName = `CLOUDFLARE_PROJECT_NAME_${app.name.toUpperCase().replace(/-/g, "_")}`;
172
+ const artifactPath = app.artifactPath || "dist";
98
173
  return {
99
174
  secrets: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"],
100
- variables: ["CLOUDFLARE_PROJECT_NAME"],
175
+ variables: [varName],
101
176
  deploySteps: [],
102
177
  // Handled by action
103
178
  githubActionStep: {
104
- name: "Deploy to Cloudflare Pages",
179
+ name: `Deploy ${app.name} to Cloudflare Pages`,
105
180
  uses: "cloudflare/wrangler-action@v3",
106
181
  with: {
107
182
  apiToken: "${{ secrets.CLOUDFLARE_API_TOKEN }}",
108
183
  accountId: "${{ secrets.CLOUDFLARE_ACCOUNT_ID }}",
109
- command: "pages deploy dist --project-name=${{ vars.CLOUDFLARE_PROJECT_NAME }}",
110
- workingDirectory: "apps/frontend"
184
+ command: `pages deploy ${artifactPath} --project-name=\${{ vars.${varName} }}`,
185
+ workingDirectory: app.target || "."
111
186
  }
112
187
  }
113
188
  };
114
189
  }
190
+ async deploy(context, app) {
191
+ const env = context.options.env || "production";
192
+ const baseProjectName = app.projectName;
193
+ const artifactPath = app.artifactPath || "dist";
194
+ const targetDir = app.target ? path.resolve(context.cwd, app.target) : context.cwd;
195
+ if (!baseProjectName) {
196
+ throw new Error(`Cloudflare project name not found for ${app.name}.`);
197
+ }
198
+ const projectName = env === "production" ? baseProjectName : `${baseProjectName}-${env}`;
199
+ logger.info(`Deploying ${app.name} to Cloudflare Pages project "${projectName}"...`);
200
+ if (context.options.dryRun) {
201
+ logger.info(
202
+ `[Dry Run] Would deploy directory "${artifactPath}" to Cloudflare project "${projectName}".`
203
+ );
204
+ return;
205
+ }
206
+ const secrets = await this.getSecrets(context, app);
207
+ const processEnv = {
208
+ ...process.env,
209
+ ...secrets,
210
+ NODE_OPTIONS: `${process.env.NODE_OPTIONS || ""} --dns-result-order=ipv4first`.trim()
211
+ };
212
+ await execAsync(`wrangler pages deploy ${artifactPath} --project-name=${projectName}`, {
213
+ cwd: targetDir,
214
+ env: processEnv
215
+ });
216
+ logger.success(`Successfully deployed ${app.name} to Cloudflare Pages.`);
217
+ }
218
+ getDefaultDnsTarget(app) {
219
+ if (app.projectName) {
220
+ return `${app.projectName}.pages.dev`;
221
+ }
222
+ return void 0;
223
+ }
115
224
  };
116
225
  export {
117
226
  CloudflareProvider
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/deploy/providers/cloudflare.ts"],"sourcesContent":["import { logger } from '@nexical/cli-core';\nimport { DeploymentProvider, DeploymentContext, CIConfig } from '../types';\nimport { execAsync } from '../utils';\n\nexport class CloudflareProvider implements DeploymentProvider {\n name = 'cloudflare';\n type = 'frontend' as const;\n\n async provision(context: DeploymentContext): Promise<void> {\n const env = (context.options.env as string) || 'production';\n const baseProjectName = context.config.deploy?.frontend?.projectName;\n\n if (!baseProjectName) {\n throw new Error(\n \"Cloudflare project name not found in nexical.yaml. Please configure 'deploy.frontend.projectName'.\",\n );\n }\n\n const projectName = env === 'production' ? baseProjectName : `${baseProjectName}-${env}`;\n\n const options = context.config.deploy?.frontend?.options || {};\n\n // Resolve credentials:\n // 1. CLI flag (options)\n // 2. Env var defined in config (options.apiTokenEnvVar)\n // 3. Default env var (CLOUDFLARE_API_TOKEN)\n const apiTokenEnvVar =\n typeof options.apiTokenEnvVar === 'string' ? options.apiTokenEnvVar : undefined;\n const apiToken =\n (typeof context.options.cloudflareToken === 'string'\n ? context.options.cloudflareToken\n : undefined) ||\n (apiTokenEnvVar ? process.env[apiTokenEnvVar] : undefined) ||\n process.env.CLOUDFLARE_API_TOKEN;\n\n const accountIdEnvVar =\n typeof options.accountIdEnvVar === 'string' ? options.accountIdEnvVar : undefined;\n const accountId =\n (typeof context.options.cloudflareAccount === 'string'\n ? context.options.cloudflareAccount\n : undefined) ||\n (accountIdEnvVar ? process.env[accountIdEnvVar] : undefined) ||\n process.env.CLOUDFLARE_ACCOUNT_ID;\n\n logger.info('Configuring Cloudflare Pages...');\n\n if (context.options.dryRun) {\n logger.info(\n `[Dry Run] Would check Cloudflare Pages project \"${projectName}\" and create if missing.`,\n );\n return;\n }\n\n if (!apiToken || !accountId) {\n logger.warn('Cloudflare credentials missing. Skipping automated Cloudflare setup.');\n logger.info('You can manually set up Cloudflare Pages and add the secrets to GitHub.');\n return;\n }\n\n try {\n logger.info(`Ensuring Cloudflare Pages project \"${projectName}\" exists...`);\n try {\n await execAsync(`wrangler pages project create ${projectName} --production-branch main`, {\n env: {\n ...process.env,\n CLOUDFLARE_API_TOKEN: apiToken,\n CLOUDFLARE_ACCOUNT_ID: accountId,\n },\n });\n } catch {\n logger.info('Cloudflare project might already exist.');\n }\n } catch (e: unknown) {\n logger.warn('Cloudflare setup failed.');\n throw e;\n }\n }\n\n async getSecrets(context: DeploymentContext): Promise<Record<string, string>> {\n const options = context.config.deploy?.frontend?.options || {};\n const secrets: Record<string, string> = {};\n\n // Resolve API Token\n const apiTokenEnvVar =\n typeof options.apiTokenEnvVar === 'string' ? options.apiTokenEnvVar : undefined;\n const apiToken =\n (apiTokenEnvVar ? process.env[apiTokenEnvVar] : undefined) ||\n process.env.CLOUDFLARE_API_TOKEN;\n\n if (!apiToken) {\n throw new Error(\n `Cloudflare API Token not found. Please provide it via:\\n` +\n `1. Configuring 'deploy.frontend.options.apiTokenEnvVar' in nexical.yaml and setting that env var in .env\\n` +\n `2. Setting CLOUDFLARE_API_TOKEN in .env`,\n );\n }\n secrets['CLOUDFLARE_API_TOKEN'] = apiToken;\n\n // Resolve Account ID\n const accountIdEnvVar =\n typeof options.accountIdEnvVar === 'string' ? options.accountIdEnvVar : undefined;\n const accountId =\n (accountIdEnvVar ? process.env[accountIdEnvVar] : undefined) ||\n process.env.CLOUDFLARE_ACCOUNT_ID;\n\n if (!accountId) {\n throw new Error(\n `Cloudflare Account ID not found. Please provide it via:\\n` +\n `1. Configuring 'deploy.frontend.options.accountIdEnvVar' in nexical.yaml and setting that env var in .env\\n` +\n `2. Setting CLOUDFLARE_ACCOUNT_ID in .env`,\n );\n }\n secrets['CLOUDFLARE_ACCOUNT_ID'] = accountId;\n\n return secrets;\n }\n\n async getVariables(context: DeploymentContext): Promise<Record<string, string>> {\n const env = (context.options.env as string) || 'production';\n const baseProjectName = context.config.deploy?.frontend?.projectName;\n\n if (!baseProjectName) {\n throw new Error(\n \"Cloudflare project name not found in nexical.yaml. Please configure 'deploy.frontend.projectName'.\",\n );\n }\n\n const projectName = env === 'production' ? baseProjectName : `${baseProjectName}-${env}`;\n return {\n CLOUDFLARE_PROJECT_NAME: projectName,\n };\n }\n\n getCIConfig(): CIConfig {\n return {\n secrets: ['CLOUDFLARE_API_TOKEN', 'CLOUDFLARE_ACCOUNT_ID'],\n variables: ['CLOUDFLARE_PROJECT_NAME'],\n deploySteps: [], // Handled by action\n githubActionStep: {\n name: 'Deploy to Cloudflare Pages',\n uses: 'cloudflare/wrangler-action@v3',\n with: {\n apiToken: '${{ secrets.CLOUDFLARE_API_TOKEN }}',\n accountId: '${{ secrets.CLOUDFLARE_ACCOUNT_ID }}',\n command: 'pages deploy dist --project-name=${{ vars.CLOUDFLARE_PROJECT_NAME }}',\n workingDirectory: 'apps/frontend',\n },\n },\n };\n }\n}\n"],"mappings":";;;;;;;;;AAAA;AAAA,SAAS,cAAc;AAIhB,IAAM,qBAAN,MAAuD;AAAA,EAC5D,OAAO;AAAA,EACP,OAAO;AAAA,EAEP,MAAM,UAAU,SAA2C;AACzD,UAAM,MAAO,QAAQ,QAAQ,OAAkB;AAC/C,UAAM,kBAAkB,QAAQ,OAAO,QAAQ,UAAU;AAEzD,QAAI,CAAC,iBAAiB;AACpB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,cAAc,QAAQ,eAAe,kBAAkB,GAAG,eAAe,IAAI,GAAG;AAEtF,UAAM,UAAU,QAAQ,OAAO,QAAQ,UAAU,WAAW,CAAC;AAM7D,UAAM,iBACJ,OAAO,QAAQ,mBAAmB,WAAW,QAAQ,iBAAiB;AACxE,UAAM,YACH,OAAO,QAAQ,QAAQ,oBAAoB,WACxC,QAAQ,QAAQ,kBAChB,YACH,iBAAiB,QAAQ,IAAI,cAAc,IAAI,WAChD,QAAQ,IAAI;AAEd,UAAM,kBACJ,OAAO,QAAQ,oBAAoB,WAAW,QAAQ,kBAAkB;AAC1E,UAAM,aACH,OAAO,QAAQ,QAAQ,sBAAsB,WAC1C,QAAQ,QAAQ,oBAChB,YACH,kBAAkB,QAAQ,IAAI,eAAe,IAAI,WAClD,QAAQ,IAAI;AAEd,WAAO,KAAK,iCAAiC;AAE7C,QAAI,QAAQ,QAAQ,QAAQ;AAC1B,aAAO;AAAA,QACL,mDAAmD,WAAW;AAAA,MAChE;AACA;AAAA,IACF;AAEA,QAAI,CAAC,YAAY,CAAC,WAAW;AAC3B,aAAO,KAAK,sEAAsE;AAClF,aAAO,KAAK,yEAAyE;AACrF;AAAA,IACF;AAEA,QAAI;AACF,aAAO,KAAK,sCAAsC,WAAW,aAAa;AAC1E,UAAI;AACF,cAAM,UAAU,iCAAiC,WAAW,6BAA6B;AAAA,UACvF,KAAK;AAAA,YACH,GAAG,QAAQ;AAAA,YACX,sBAAsB;AAAA,YACtB,uBAAuB;AAAA,UACzB;AAAA,QACF,CAAC;AAAA,MACH,QAAQ;AACN,eAAO,KAAK,yCAAyC;AAAA,MACvD;AAAA,IACF,SAAS,GAAY;AACnB,aAAO,KAAK,0BAA0B;AACtC,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,SAA6D;AAC5E,UAAM,UAAU,QAAQ,OAAO,QAAQ,UAAU,WAAW,CAAC;AAC7D,UAAM,UAAkC,CAAC;AAGzC,UAAM,iBACJ,OAAO,QAAQ,mBAAmB,WAAW,QAAQ,iBAAiB;AACxE,UAAM,YACH,iBAAiB,QAAQ,IAAI,cAAc,IAAI,WAChD,QAAQ,IAAI;AAEd,QAAI,CAAC,UAAU;AACb,YAAM,IAAI;AAAA,QACR;AAAA;AAAA;AAAA,MAGF;AAAA,IACF;AACA,YAAQ,sBAAsB,IAAI;AAGlC,UAAM,kBACJ,OAAO,QAAQ,oBAAoB,WAAW,QAAQ,kBAAkB;AAC1E,UAAM,aACH,kBAAkB,QAAQ,IAAI,eAAe,IAAI,WAClD,QAAQ,IAAI;AAEd,QAAI,CAAC,WAAW;AACd,YAAM,IAAI;AAAA,QACR;AAAA;AAAA;AAAA,MAGF;AAAA,IACF;AACA,YAAQ,uBAAuB,IAAI;AAEnC,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,aAAa,SAA6D;AAC9E,UAAM,MAAO,QAAQ,QAAQ,OAAkB;AAC/C,UAAM,kBAAkB,QAAQ,OAAO,QAAQ,UAAU;AAEzD,QAAI,CAAC,iBAAiB;AACpB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,cAAc,QAAQ,eAAe,kBAAkB,GAAG,eAAe,IAAI,GAAG;AACtF,WAAO;AAAA,MACL,yBAAyB;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,cAAwB;AACtB,WAAO;AAAA,MACL,SAAS,CAAC,wBAAwB,uBAAuB;AAAA,MACzD,WAAW,CAAC,yBAAyB;AAAA,MACrC,aAAa,CAAC;AAAA;AAAA,MACd,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,UAAU;AAAA,UACV,WAAW;AAAA,UACX,SAAS;AAAA,UACT,kBAAkB;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../src/deploy/providers/cloudflare.ts"],"sourcesContent":["import path from 'node:path';\nimport { logger } from '@nexical/cli-core';\nimport { HostingProvider, DeploymentContext, CIConfig, AppConfig } from '../types';\nimport { execAsync } from '../utils';\n\nexport interface CloudflareConfig {\n token?: string;\n account?: string;\n}\n\nexport class CloudflareProvider implements HostingProvider {\n name = 'cloudflare';\n\n async provision(context: DeploymentContext, app: AppConfig): Promise<void> {\n const env = (context.options.env as string) || 'production';\n const baseProjectName = app.projectName;\n\n if (!baseProjectName) {\n throw new Error(\n `Cloudflare project name not found for ${app.name}. Please configure 'projectName'.`,\n );\n }\n\n const projectName = env === 'production' ? baseProjectName : `${baseProjectName}-${env}`;\n\n logger.info(`Configuring Cloudflare Pages for ${app.name}...`);\n\n if (context.options.dryRun) {\n logger.info(\n `[Dry Run] Would check Cloudflare status and provision project \"${projectName}\".`,\n );\n return;\n }\n\n try {\n const secrets = await this.getSecrets(context, app).catch(() => undefined);\n if (!secrets) {\n logger.warn(\n `Cloudflare credentials missing for ${app.name}. Skipping provisioning. ` +\n 'Ensure CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID are set.',\n );\n return;\n }\n\n const processEnv = {\n ...process.env,\n ...secrets,\n NODE_OPTIONS: `${process.env.NODE_OPTIONS || ''} --dns-result-order=ipv4first`.trim(),\n };\n logger.info(`Ensuring Cloudflare Pages project \"${projectName}\" exists...`);\n try {\n await execAsync(`wrangler pages project create ${projectName} --production-branch main`, {\n env: processEnv,\n });\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes('already exists')) {\n logger.info('Cloudflare project already exists.');\n } else {\n throw err;\n }\n }\n\n // Handle Linked Domains\n if (app.domain) {\n const domains = Array.isArray(app.domain) ? app.domain : [app.domain];\n logger.info(\n `Linking ${domains.length} domains to Cloudflare Pages project \"${projectName}\"...`,\n );\n\n const apiToken = secrets.CLOUDFLARE_API_TOKEN;\n const accountId = secrets.CLOUDFLARE_ACCOUNT_ID;\n\n // Fetch existing domains to avoid redundant calls\n const listRes = await fetch(\n `https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${projectName}/domains`,\n {\n headers: {\n Authorization: `Bearer ${apiToken}`,\n 'Content-Type': 'application/json',\n },\n },\n );\n\n if (!listRes.ok) {\n const errorText = await listRes.text();\n logger.warn(`Failed to fetch existing linked domains: ${errorText}`);\n } else {\n const listJson = (await listRes.json()) as {\n success: boolean;\n result: { domain: string }[];\n };\n const existingDomains = listJson.success ? listJson.result.map((d) => d.domain) : [];\n\n for (const domain of domains) {\n if (existingDomains.includes(domain)) {\n logger.info(`[Cloudflare Pages] Domain ${domain} is already linked.`);\n continue;\n }\n\n logger.info(`[Cloudflare Pages] Linking domain ${domain}...`);\n const linkRes = await fetch(\n `https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${projectName}/domains`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ name: domain }), // Pages API uses 'name' for the domain string in some versions, but docs suggest 'name' or just object. Let's verify 'name' vs 'domain'.\n // Correction: The API docs say POST body should be { \"name\": \"example.com\" }\n },\n );\n\n if (!linkRes.ok) {\n const errorText = await linkRes.text();\n // If it failed because it exists but wasn't in list (unlikely but safe)\n if (errorText.includes('already exists') || errorText.includes('1008')) {\n logger.info(`[Cloudflare Pages] Domain ${domain} already linked.`);\n } else {\n logger.warn(`[Cloudflare Pages] Failed to link domain ${domain}: ${errorText}`);\n }\n } else {\n logger.success(`[Cloudflare Pages] Linked domain ${domain}.`);\n }\n }\n }\n }\n } catch (e: unknown) {\n logger.warn('Cloudflare setup failed.');\n throw e;\n }\n }\n\n async getSecrets(context: DeploymentContext, app: AppConfig): Promise<Record<string, string>> {\n const cfConfig = (app.cloudflare as CloudflareConfig) || {};\n const apiTokenEnvVar = cfConfig.token;\n const accountIdEnvVar = cfConfig.account;\n\n const apiToken =\n process.env.CLOUDFLARE_API_TOKEN?.trim() ||\n (apiTokenEnvVar ? process.env[apiTokenEnvVar]?.trim() : undefined);\n const accountId =\n process.env.CLOUDFLARE_ACCOUNT_ID?.trim() ||\n (accountIdEnvVar ? process.env[accountIdEnvVar]?.trim() : undefined);\n\n if (!apiToken) {\n throw new Error(\n `Cloudflare API Token not found for ${app.name}. Please provide it via:\\n` +\n `1. Setting CLOUDFLARE_API_TOKEN in .env (Recommended)\\n` +\n `2. Configuring 'cloudflare.token' and setting that env var in .env`,\n );\n }\n\n if (!accountId) {\n throw new Error(\n `Cloudflare Account ID not found for ${app.name}. Please provide it via:\\n` +\n `1. Setting CLOUDFLARE_ACCOUNT_ID in .env (Recommended)\\n` +\n `2. Configuring 'cloudflare.account' and setting that env var in .env`,\n );\n }\n\n const secrets: Record<string, string> = {\n CLOUDFLARE_API_TOKEN: apiToken,\n CLOUDFLARE_ACCOUNT_ID: accountId,\n };\n\n // Custom mapped secrets\n if (app.secrets) {\n for (const [key, envVar] of Object.entries(app.secrets)) {\n const value = process.env[envVar];\n if (!value) {\n throw new Error(`Custom secret '${key}' mapping failed: Env var '${envVar}' not found.`);\n }\n secrets[key] = value;\n }\n }\n\n return secrets;\n }\n\n async getVariables(context: DeploymentContext, app: AppConfig): Promise<Record<string, string>> {\n const env = (context.options.env as string) || 'production';\n const baseProjectName = app.projectName;\n\n if (!baseProjectName) {\n throw new Error(`Cloudflare project name not found for ${app.name}.`);\n }\n\n const projectName = env === 'production' ? baseProjectName : `${baseProjectName}-${env}`;\n const varName = `CLOUDFLARE_PROJECT_NAME_${app.name.toUpperCase().replace(/-/g, '_')}`;\n const result: Record<string, string> = {\n [varName]: projectName,\n };\n\n // Custom mapped variables\n if (app.env) {\n for (const [key, value] of Object.entries(app.env)) {\n // If it looks like an env var, try to resolve it, otherwise use literal\n const resolvedValue = process.env[value] || value;\n result[key] = resolvedValue;\n }\n }\n\n return result;\n }\n\n getCIConfig(repoType: 'github' | 'gitlab', app: AppConfig): CIConfig {\n const varName = `CLOUDFLARE_PROJECT_NAME_${app.name.toUpperCase().replace(/-/g, '_')}`;\n const artifactPath = app.artifactPath || 'dist';\n return {\n secrets: ['CLOUDFLARE_API_TOKEN', 'CLOUDFLARE_ACCOUNT_ID'],\n variables: [varName],\n deploySteps: [], // Handled by action\n githubActionStep: {\n name: `Deploy ${app.name} to Cloudflare Pages`,\n uses: 'cloudflare/wrangler-action@v3',\n with: {\n apiToken: '${{ secrets.CLOUDFLARE_API_TOKEN }}',\n accountId: '${{ secrets.CLOUDFLARE_ACCOUNT_ID }}',\n command: `pages deploy ${artifactPath} --project-name=\\${{ vars.${varName} }}`,\n workingDirectory: app.target || '.',\n },\n },\n };\n }\n\n async deploy(context: DeploymentContext, app: AppConfig): Promise<void> {\n const env = (context.options.env as string) || 'production';\n const baseProjectName = app.projectName;\n const artifactPath = app.artifactPath || 'dist';\n const targetDir = app.target ? path.resolve(context.cwd, app.target) : context.cwd;\n\n if (!baseProjectName) {\n throw new Error(`Cloudflare project name not found for ${app.name}.`);\n }\n\n const projectName = env === 'production' ? baseProjectName : `${baseProjectName}-${env}`;\n\n logger.info(`Deploying ${app.name} to Cloudflare Pages project \"${projectName}\"...`);\n\n if (context.options.dryRun) {\n logger.info(\n `[Dry Run] Would deploy directory \"${artifactPath}\" to Cloudflare project \"${projectName}\".`,\n );\n return;\n }\n\n const secrets = await this.getSecrets(context, app);\n const processEnv = {\n ...process.env,\n ...secrets,\n NODE_OPTIONS: `${process.env.NODE_OPTIONS || ''} --dns-result-order=ipv4first`.trim(),\n };\n\n await execAsync(`wrangler pages deploy ${artifactPath} --project-name=${projectName}`, {\n cwd: targetDir,\n env: processEnv,\n });\n\n logger.success(`Successfully deployed ${app.name} to Cloudflare Pages.`);\n }\n\n getDefaultDnsTarget(app: AppConfig): string | undefined {\n // Cloudflare pages gives a predictable .pages.dev alias\n // Note: This does not take environment into account for custom domains usually,\n // custom domains are typically linked to the production project alias or a specific branch alias.\n // For standard custom domain linkage, we return the production project alias.\n if (app.projectName) {\n return `${app.projectName}.pages.dev`;\n }\n return undefined;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,9 @@
1
+ import { DnsProvider, DeploymentContext, DnsRecord } from '../types.js';
2
+
3
+ declare class CloudflareDnsProvider implements DnsProvider {
4
+ name: string;
5
+ type: "dns";
6
+ provision(context: DeploymentContext, records: DnsRecord[]): Promise<void>;
7
+ }
8
+
9
+ export { CloudflareDnsProvider, CloudflareDnsProvider as default };
@@ -0,0 +1,123 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ init_esm_shims
4
+ } from "../../../chunk-6DE5Q66O.js";
5
+
6
+ // src/deploy/providers/dns-cloudflare.ts
7
+ init_esm_shims();
8
+ import { logger } from "@nexical/cli-core";
9
+ var CloudflareDnsProvider = class {
10
+ name = "cloudflare";
11
+ type = "dns";
12
+ async provision(context, records) {
13
+ const dnsConfig = context.config.deploy?.dns;
14
+ const cfConfig = dnsConfig?.cloudflare;
15
+ const apiToken = process.env.CLOUDFLARE_API_TOKEN || cfConfig?.token;
16
+ const zoneId = process.env.CLOUDFLARE_ZONE_ID || cfConfig?.zone;
17
+ if (!apiToken) {
18
+ throw new Error(
19
+ "Cloudflare API token not found. Set CLOUDFLARE_API_TOKEN environment variable or deploy.dns.cloudflare.token in nexical.yaml"
20
+ );
21
+ }
22
+ if (!zoneId) {
23
+ throw new Error(
24
+ "Cloudflare Zone ID not found. Set CLOUDFLARE_ZONE_ID environment variable or deploy.dns.cloudflare.zone in nexical.yaml"
25
+ );
26
+ }
27
+ if (records.length === 0) {
28
+ logger.info(`[Cloudflare DNS] No DNS records to provision.`);
29
+ return;
30
+ }
31
+ logger.info(`[Cloudflare DNS] Provisioning ${records.length} records...`);
32
+ if (context.options.dryRun) {
33
+ for (const record of records) {
34
+ logger.info(
35
+ `[Dry Run] Would create/update DNS record: ${record.name} -> ${record.content} (${record.type})`
36
+ );
37
+ }
38
+ return;
39
+ }
40
+ const response = await fetch(
41
+ `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records`,
42
+ {
43
+ headers: {
44
+ Authorization: `Bearer ${apiToken}`,
45
+ "Content-Type": "application/json"
46
+ }
47
+ }
48
+ );
49
+ if (!response.ok) {
50
+ const errorText = await response.text();
51
+ throw new Error(
52
+ `Failed to fetch Cloudflare DNS records: ${response.status} ${response.statusText} - ${errorText}`
53
+ );
54
+ }
55
+ const jsonRes = await response.json();
56
+ if (!jsonRes.success) {
57
+ throw new Error("Cloudflare API returned success: false when fetching DNS records.");
58
+ }
59
+ const existingRecords = jsonRes.result || [];
60
+ for (const record of records) {
61
+ const match = existingRecords.find((r) => r.name === record.name && r.type === record.type);
62
+ const payload = {
63
+ type: record.type,
64
+ name: record.name,
65
+ content: record.content,
66
+ proxied: record.proxied ?? true,
67
+ // Default to proxied for Cloudflare
68
+ ttl: 1
69
+ // Automatic
70
+ };
71
+ if (match) {
72
+ if (match.content !== record.content || match.proxied !== payload.proxied) {
73
+ logger.info(
74
+ `[Cloudflare DNS] Updating ${record.name} (${record.type}) -> ${record.content}`
75
+ );
76
+ const updateRes = await fetch(
77
+ `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records/${match.id}`,
78
+ {
79
+ method: "PUT",
80
+ headers: {
81
+ Authorization: `Bearer ${apiToken}`,
82
+ "Content-Type": "application/json"
83
+ },
84
+ body: JSON.stringify(payload)
85
+ }
86
+ );
87
+ if (!updateRes.ok) {
88
+ const errorText = await updateRes.text();
89
+ throw new Error(`Failed to update DNS record ${record.name}: ${errorText}`);
90
+ }
91
+ } else {
92
+ logger.info(`[Cloudflare DNS] Record ${record.name} is already up to date.`);
93
+ }
94
+ } else {
95
+ logger.info(
96
+ `[Cloudflare DNS] Creating ${record.name} (${record.type}) -> ${record.content}`
97
+ );
98
+ const createRes = await fetch(
99
+ `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records`,
100
+ {
101
+ method: "POST",
102
+ headers: {
103
+ Authorization: `Bearer ${apiToken}`,
104
+ "Content-Type": "application/json"
105
+ },
106
+ body: JSON.stringify(payload)
107
+ }
108
+ );
109
+ if (!createRes.ok) {
110
+ const errorText = await createRes.text();
111
+ throw new Error(`Failed to create DNS record ${record.name}: ${errorText}`);
112
+ }
113
+ }
114
+ }
115
+ logger.success(`[Cloudflare DNS] Finished provisioning DNS records.`);
116
+ }
117
+ };
118
+ var dns_cloudflare_default = CloudflareDnsProvider;
119
+ export {
120
+ CloudflareDnsProvider,
121
+ dns_cloudflare_default as default
122
+ };
123
+ //# sourceMappingURL=dns-cloudflare.js.map