clefbase 1.3.4 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Raw API calls used by the CLI.
3
+ * api.ts — Raw API calls used by the CLI.
4
4
  * Written as plain fetch calls (not using HttpClient) so the CLI can be
5
5
  * bundled independently and doesn't pull in the full SDK at runtime.
6
6
  */
@@ -41,6 +41,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
41
41
  exports.CliApiError = void 0;
42
42
  exports.listSites = listSites;
43
43
  exports.createSite = createSite;
44
+ exports.deleteSite = deleteSite;
45
+ exports.getDnsStatus = getDnsStatus;
46
+ exports.reprovisionDns = reprovisionDns;
44
47
  exports.createDeploy = createDeploy;
45
48
  exports.uploadFileBatch = uploadFileBatch;
46
49
  exports.finalizeDeploy = finalizeDeploy;
@@ -96,6 +99,27 @@ async function createSite(cfg, name, description) {
96
99
  body: JSON.stringify({ name, description }),
97
100
  });
98
101
  }
102
+ /**
103
+ * Delete a site.
104
+ *
105
+ * First call (confirm=false): returns { requiresConfirmation: true, dnsRecord?, message }
106
+ * Second call (confirm=true, deleteDns?): actually deletes
107
+ */
108
+ async function deleteSite(cfg, siteId, opts = {}) {
109
+ const params = new URLSearchParams();
110
+ if (opts.confirm)
111
+ params.set("confirm", "true");
112
+ if (opts.deleteDns)
113
+ params.set("deleteDns", "true");
114
+ const qs = params.toString() ? `?${params}` : "";
115
+ return apiFetch(`${base(cfg)}/api/hosting/databases/${cfg.projectId}/sites/${siteId}${qs}`, { method: "DELETE", headers: adminHeaders(cfg) });
116
+ }
117
+ async function getDnsStatus(cfg, siteId) {
118
+ return apiFetch(`${base(cfg)}/api/hosting/databases/${cfg.projectId}/sites/${siteId}/dns`, { headers: adminHeaders(cfg) });
119
+ }
120
+ async function reprovisionDns(cfg, siteId) {
121
+ return apiFetch(`${base(cfg)}/api/hosting/databases/${cfg.projectId}/sites/${siteId}/dns/provision`, { method: "POST", headers: adminHeaders(cfg), body: JSON.stringify({}) });
122
+ }
99
123
  async function createDeploy(cfg, siteId, entrypoint = "index.html") {
100
124
  return apiFetch(`${base(cfg)}/api/hosting/databases/${cfg.projectId}/sites/${siteId}/deploys`, {
101
125
  method: "POST",
@@ -108,7 +132,6 @@ async function uploadFileBatch(cfg, deployId, files) {
108
132
  const url = `${base(cfg)}/api/hosting/databases/${cfg.projectId}/deploys/${deployId}/files/batch`;
109
133
  let res;
110
134
  if (typeof FormData !== "undefined") {
111
- // Node 18+ native FormData: Buffer must be wrapped in Blob/File
112
135
  const form = new FormData();
113
136
  for (const f of files) {
114
137
  const filename = f.path.split("/").pop() ?? f.path;
@@ -124,7 +147,6 @@ async function uploadFileBatch(cfg, deployId, files) {
124
147
  });
125
148
  }
126
149
  else {
127
- // Older Node: use form-data package which natively accepts Buffers
128
150
  const mod = await Promise.resolve().then(() => __importStar(require("form-data")));
129
151
  const FormDataLegacy = mod.default;
130
152
  const form = new FormDataLegacy();
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.runDeploy = runDeploy;
7
7
  exports.runHostingInit = runHostingInit;
8
8
  exports.runStatus = runStatus;
9
+ exports.runDnsStatus = runDnsStatus;
10
+ exports.runDnsReprovision = runDnsReprovision;
9
11
  const path_1 = __importDefault(require("path"));
10
12
  const fs_1 = __importDefault(require("fs"));
11
13
  const chalk_1 = __importDefault(require("chalk"));
@@ -65,6 +67,9 @@ function fmtBytes(n) {
65
67
  return `${(n / 1024).toFixed(1)} KB`;
66
68
  return `${(n / (1024 * 1024)).toFixed(2)} MB`;
67
69
  }
70
+ function siteUrl(site) {
71
+ return site.customDomain ? `https://${site.customDomain}` : site.previewUrl;
72
+ }
68
73
  // ─── deploy ───────────────────────────────────────────────────────────────────
69
74
  async function runDeploy(opts) {
70
75
  const cwd = opts.cwd ?? process.cwd();
@@ -75,18 +80,24 @@ async function runDeploy(opts) {
75
80
  // ── Resolve / pick a site ─────────────────────────────────────────────────
76
81
  let siteId = opts.site ?? cfg.hosting?.siteId ?? "";
77
82
  let siteName = cfg.hosting?.siteName ?? "";
83
+ let previewUrl = "";
78
84
  if (!siteId) {
79
- siteId = await pickOrCreateSite(cfg);
80
- const sites = await (0, api_1.listSites)(cfg).catch(() => []);
81
- siteName = sites.find(s => s.id === siteId)?.name ?? siteId;
85
+ const site = await pickOrCreateSite(cfg);
86
+ siteId = site.id;
87
+ siteName = site.name;
88
+ previewUrl = site.previewUrl;
82
89
  cfg.hosting = {
83
90
  siteId,
84
91
  siteName,
92
+ previewUrl,
85
93
  distDir: cfg.hosting?.distDir ?? "dist",
86
94
  entrypoint: cfg.hosting?.entrypoint ?? "index.html",
87
95
  };
88
96
  (0, config_1.saveConfig)(cfg, cwd);
89
97
  }
98
+ else {
99
+ previewUrl = cfg.hosting?.previewUrl ?? "";
100
+ }
90
101
  // ── Resolve dist dir ──────────────────────────────────────────────────────
91
102
  const distDir = opts.dir ?? cfg.hosting?.distDir ?? await promptDistDir(cwd);
92
103
  const absDir = path_1.default.isAbsolute(distDir) ? distDir : path_1.default.join(cwd, distDir);
@@ -111,9 +122,10 @@ async function runDeploy(opts) {
111
122
  }
112
123
  // ── Summary ───────────────────────────────────────────────────────────────
113
124
  console.log();
114
- console.log(` ${chalk_1.default.bold("Site:")} ${siteName} ${chalk_1.default.dim(siteId)}`);
115
- console.log(` ${chalk_1.default.bold("From:")} ${path_1.default.relative(cwd, absDir)}`);
116
- console.log(` ${chalk_1.default.bold("Files:")} ${files.length} ${chalk_1.default.dim("(" + fmtBytes(totalBytes) + ")")}`);
125
+ console.log(` ${chalk_1.default.bold("Site:")} ${siteName} ${chalk_1.default.dim(siteId)}`);
126
+ console.log(` ${chalk_1.default.bold("Preview:")} ${chalk_1.default.cyan(previewUrl || "(set after deploy)")}`);
127
+ console.log(` ${chalk_1.default.bold("From:")} ${path_1.default.relative(cwd, absDir)}`);
128
+ console.log(` ${chalk_1.default.bold("Files:")} ${files.length} ${chalk_1.default.dim("(" + fmtBytes(totalBytes) + ")")}`);
117
129
  console.log();
118
130
  // ── Create deploy ─────────────────────────────────────────────────────────
119
131
  const deploySpinner = (0, ora_1.default)("Creating deploy…").start();
@@ -165,10 +177,30 @@ async function runDeploy(opts) {
165
177
  finSpinner.fail(`Finalize failed: ${err.message}`);
166
178
  process.exit(1);
167
179
  }
168
- // ── Result ────────────────────────────────────────────────────────────────
169
- const url = `${cfg.serverUrl.replace(/\/+$/, "")}/hosted/${cfg.projectId}/${siteId}`;
180
+ // ── Result — fetch the canonical URL from the live site record ────────────
181
+ // The previewUrl from the config may be stale (e.g. if the site was renamed)
182
+ // and a custom domain may have been added since init. Always fetch the live
183
+ // site record to get the correct canonical URL.
184
+ let canonicalUrl = previewUrl;
185
+ try {
186
+ // Fetch the current site record to get the canonical URL — it may differ
187
+ // from the config if the site was renamed or a custom domain was added.
188
+ const sites = await (0, api_1.listSites)(cfg);
189
+ const liveSite = sites.find(s => s.id === siteId);
190
+ if (liveSite) {
191
+ canonicalUrl = siteUrl(liveSite);
192
+ // Keep the config preview URL in sync for future deploys.
193
+ if (cfg.hosting && liveSite.previewUrl && cfg.hosting.previewUrl !== liveSite.previewUrl) {
194
+ cfg.hosting.previewUrl = liveSite.previewUrl;
195
+ (0, config_1.saveConfig)(cfg, cwd);
196
+ }
197
+ }
198
+ }
199
+ catch { /* best-effort — fall back to stored previewUrl */ }
170
200
  console.log();
171
- console.log(chalk_1.default.green.bold(` ✓ ${url}`));
201
+ if (canonicalUrl) {
202
+ console.log(chalk_1.default.green.bold(` ✓ ${canonicalUrl}`));
203
+ }
172
204
  console.log();
173
205
  }
174
206
  // ─── hosting:init ─────────────────────────────────────────────────────────────
@@ -177,9 +209,7 @@ async function runHostingInit(cwd = process.cwd()) {
177
209
  console.log();
178
210
  console.log(chalk_1.default.bold.cyan(" Hosting Setup"));
179
211
  console.log();
180
- const siteId = await pickOrCreateSite(cfg);
181
- const sites = await (0, api_1.listSites)(cfg).catch(() => []);
182
- const siteName = sites.find(s => s.id === siteId)?.name ?? siteId;
212
+ const site = await pickOrCreateSite(cfg);
183
213
  const { distDir } = await inquirer_1.default.prompt([{
184
214
  type: "input", name: "distDir",
185
215
  message: "Build output directory",
@@ -192,10 +222,20 @@ async function runHostingInit(cwd = process.cwd()) {
192
222
  default: cfg.hosting?.entrypoint ?? "index.html",
193
223
  }]);
194
224
  cfg.services.hosting = true;
195
- cfg.hosting = { siteId, siteName, distDir: distDir.trim(), entrypoint: entrypoint.trim() };
225
+ cfg.hosting = {
226
+ siteId: site.id,
227
+ siteName: site.name,
228
+ previewUrl: site.previewUrl,
229
+ distDir: distDir.trim(),
230
+ entrypoint: entrypoint.trim(),
231
+ };
196
232
  (0, config_1.saveConfig)(cfg, cwd);
197
233
  console.log();
198
- console.log(chalk_1.default.green(` ✓ Linked to "${siteName}"`));
234
+ console.log(chalk_1.default.green(` ✓ Linked to "${site.name}"`));
235
+ console.log(chalk_1.default.dim(` Preview URL: ${site.previewUrl}`));
236
+ if (site.dnsWarning) {
237
+ console.log(chalk_1.default.yellow(` ⚠ ${site.dnsWarning}`));
238
+ }
199
239
  console.log(chalk_1.default.dim(" Run `clefbase deploy` to go live."));
200
240
  console.log();
201
241
  }
@@ -218,15 +258,91 @@ async function runStatus(cwd = process.cwd()) {
218
258
  else {
219
259
  sp.succeed("Active deploy");
220
260
  const d = active.deploy;
221
- const url = `${cfg.serverUrl.replace(/\/+$/, "")}/hosted/${cfg.projectId}/${cfg.hosting.siteId}`;
222
261
  console.log();
223
262
  console.log(` ${chalk_1.default.bold("Site:")} ${cfg.hosting.siteName} ${chalk_1.default.dim(cfg.hosting.siteId)}`);
224
263
  console.log(` ${chalk_1.default.bold("Deploy:")} ${chalk_1.default.dim(d.id)}`);
225
264
  console.log(` ${chalk_1.default.bold("Status:")} ${chalk_1.default.green(d.status)}`);
226
265
  console.log(` ${chalk_1.default.bold("Files:")} ${d.fileCount} ${chalk_1.default.dim(fmtBytes(d.totalBytes ?? 0))}`);
227
266
  console.log(` ${chalk_1.default.bold("Deployed:")} ${new Date(d.finishedAt ?? d.createdAt).toLocaleString()}`);
228
- console.log(` ${chalk_1.default.bold("URL:")} ${url}`);
267
+ if (cfg.hosting.previewUrl) {
268
+ console.log(` ${chalk_1.default.bold("Preview:")} ${chalk_1.default.cyan(cfg.hosting.previewUrl)}`);
269
+ }
270
+ }
271
+ }
272
+ catch (err) {
273
+ sp.fail(err.message);
274
+ }
275
+ console.log();
276
+ }
277
+ // ─── hosting:dns ──────────────────────────────────────────────────────────────
278
+ async function runDnsStatus(cwd = process.cwd()) {
279
+ const cfg = (0, config_1.requireConfig)(cwd);
280
+ console.log();
281
+ console.log(chalk_1.default.bold(" DNS Status"));
282
+ console.log();
283
+ if (!cfg.hosting?.siteId) {
284
+ console.log(chalk_1.default.yellow(" No site linked. Run `clefbase hosting:init` first.\n"));
285
+ return;
286
+ }
287
+ const sp = (0, ora_1.default)("Checking DNS records…").start();
288
+ try {
289
+ const dns = await (0, api_1.getDnsStatus)(cfg, cfg.hosting.siteId);
290
+ sp.succeed("DNS checked");
291
+ console.log();
292
+ // Preview subdomain
293
+ console.log(` ${chalk_1.default.bold("Preview subdomain:")}`);
294
+ console.log(` URL: ${chalk_1.default.cyan(dns.previewUrl)}`);
295
+ if (dns.preview.isCorrect) {
296
+ console.log(` DNS: ${chalk_1.default.green("✓ Correctly configured")}`);
297
+ }
298
+ else if (dns.preview.isConflict) {
299
+ console.log(` DNS: ${chalk_1.default.red("✗ Conflict — " + dns.preview.message)}`);
300
+ console.log(chalk_1.default.dim(" Run `clefbase hosting:dns:reprovision` to fix."));
301
+ }
302
+ else {
303
+ console.log(` DNS: ${chalk_1.default.yellow("⚠ Not yet provisioned")}`);
304
+ console.log(chalk_1.default.dim(" Run `clefbase hosting:dns:reprovision` to create the record."));
229
305
  }
306
+ // Custom domain
307
+ if (dns.customDomain) {
308
+ console.log();
309
+ console.log(` ${chalk_1.default.bold("Custom domain:")}`);
310
+ console.log(` Domain: ${dns.customDomain.domain}`);
311
+ if (dns.customDomain.cnameOk) {
312
+ console.log(` DNS: ${chalk_1.default.green("✓ CNAME correctly points to " + dns.customDomain.cnameTarget)}`);
313
+ }
314
+ else if (dns.customDomain.conflict) {
315
+ console.log(` DNS: ${chalk_1.default.red("✗ Conflict — existing record points to " + dns.customDomain.cnameTarget)}`);
316
+ }
317
+ else {
318
+ console.log(` DNS: ${chalk_1.default.yellow("⚠ CNAME not yet set")}`);
319
+ console.log(` Add: CNAME ${dns.customDomain.instructions.name} → ${dns.customDomain.instructions.content}`);
320
+ console.log(chalk_1.default.dim(` Note: ${dns.customDomain.instructions.note}`));
321
+ }
322
+ }
323
+ }
324
+ catch (err) {
325
+ sp.fail(err.message);
326
+ }
327
+ console.log();
328
+ }
329
+ // ─── hosting:dns:reprovision ──────────────────────────────────────────────────
330
+ async function runDnsReprovision(cwd = process.cwd()) {
331
+ const cfg = (0, config_1.requireConfig)(cwd);
332
+ if (!cfg.hosting?.siteId) {
333
+ console.error(chalk_1.default.red("\n No site linked. Run `clefbase hosting:init` first.\n"));
334
+ process.exit(1);
335
+ }
336
+ const sp = (0, ora_1.default)(`Provisioning DNS for ${cfg.hosting.siteName}…`).start();
337
+ try {
338
+ const result = await (0, api_1.reprovisionDns)(cfg, cfg.hosting.siteId);
339
+ if (result.created) {
340
+ sp.succeed(chalk_1.default.green(`DNS record created: ${result.subdomain}`));
341
+ }
342
+ else {
343
+ sp.succeed(chalk_1.default.dim(`DNS record already correct: ${result.subdomain}`));
344
+ }
345
+ console.log(chalk_1.default.cyan(` ${result.url}`));
230
346
  }
231
347
  catch (err) {
232
348
  sp.fail(err.message);
@@ -234,6 +350,11 @@ async function runStatus(cwd = process.cwd()) {
234
350
  console.log();
235
351
  }
236
352
  // ─── Helpers ──────────────────────────────────────────────────────────────────
353
+ /**
354
+ * Interactive site picker / creator.
355
+ * Shows existing sites, allows creating a new one.
356
+ * On creation: enforces uniqueness, shows preview URL.
357
+ */
237
358
  async function pickOrCreateSite(cfg) {
238
359
  const sp = (0, ora_1.default)("Fetching sites…").start();
239
360
  let sites = [];
@@ -246,7 +367,10 @@ async function pickOrCreateSite(cfg) {
246
367
  }
247
368
  if (sites.length > 0) {
248
369
  const choices = [
249
- ...sites.map(s => ({ name: `${s.name} ${chalk_1.default.dim(s.id)}`, value: s.id })),
370
+ ...sites.map(s => ({
371
+ name: `${s.name} ${chalk_1.default.dim(s.previewUrl || s.id)}`,
372
+ value: s.id,
373
+ })),
250
374
  { name: chalk_1.default.cyan("+ Create a new site"), value: "__new__" },
251
375
  ];
252
376
  const { chosen } = await inquirer_1.default.prompt([{
@@ -254,18 +378,43 @@ async function pickOrCreateSite(cfg) {
254
378
  message: "Which site should receive this deploy?",
255
379
  choices,
256
380
  }]);
257
- if (chosen !== "__new__")
258
- return chosen;
381
+ if (chosen !== "__new__") {
382
+ return sites.find(s => s.id === chosen);
383
+ }
384
+ }
385
+ return createSiteInteractive(cfg);
386
+ }
387
+ /** Prompt for a new site name, handle conflict errors with retry. */
388
+ async function createSiteInteractive(cfg) {
389
+ while (true) {
390
+ const { name } = await inquirer_1.default.prompt([{
391
+ type: "input", name: "name",
392
+ message: "New site name (used as subdomain, e.g. my-app → my-app.preview.cleforyx.com)",
393
+ validate: (v) => v.trim().length >= 2 || "Must be at least 2 characters",
394
+ }]);
395
+ const s = (0, ora_1.default)(`Creating "${name.trim()}"…`).start();
396
+ try {
397
+ const site = await (0, api_1.createSite)(cfg, name.trim());
398
+ s.succeed(chalk_1.default.green(`Created "${site.name}" → ${chalk_1.default.cyan(site.previewUrl)}`));
399
+ if (site.dnsWarning) {
400
+ console.log(chalk_1.default.yellow(` ⚠ ${site.dnsWarning}`));
401
+ }
402
+ return site;
403
+ }
404
+ catch (err) {
405
+ const message = err.message;
406
+ // 409 conflict — name already taken
407
+ if (message.toLowerCase().includes("already exists") || message.includes("409")) {
408
+ s.fail(chalk_1.default.red(message));
409
+ console.log(chalk_1.default.dim(" Please choose a different name.\n"));
410
+ // Loop to prompt again
411
+ }
412
+ else {
413
+ s.fail(message);
414
+ throw err;
415
+ }
416
+ }
259
417
  }
260
- const { name } = await inquirer_1.default.prompt([{
261
- type: "input", name: "name",
262
- message: "New site name",
263
- validate: (v) => v.trim().length > 0 || "Required",
264
- }]);
265
- const s = (0, ora_1.default)(`Creating "${name}"…`).start();
266
- const site = await (0, api_1.createSite)(cfg, name.trim());
267
- s.succeed(chalk_1.default.green(`Created "${site.name}" ${chalk_1.default.dim(site.id)}`));
268
- return site.id;
269
418
  }
270
419
  async function promptDistDir(cwd) {
271
420
  const { dir } = await inquirer_1.default.prompt([{
@@ -79,6 +79,32 @@ program
79
79
  fatal(err);
80
80
  }
81
81
  });
82
+ // ─── hosting:dns ──────────────────────────────────────────────────────────────
83
+ program
84
+ .command("hosting:dns")
85
+ .alias("dns")
86
+ .description("Show DNS status for the linked site's preview and custom domains")
87
+ .action(async () => {
88
+ try {
89
+ await (0, deploy_1.runDnsStatus)();
90
+ }
91
+ catch (err) {
92
+ fatal(err);
93
+ }
94
+ });
95
+ // ─── hosting:dns:reprovision ──────────────────────────────────────────────────
96
+ program
97
+ .command("hosting:dns:reprovision")
98
+ .alias("dns:reprovision")
99
+ .description("(Re-)provision the Cloudflare CNAME for the linked site")
100
+ .action(async () => {
101
+ try {
102
+ await (0, deploy_1.runDnsReprovision)();
103
+ }
104
+ catch (err) {
105
+ fatal(err);
106
+ }
107
+ });
82
108
  // ─── info ─────────────────────────────────────────────────────────────────────
83
109
  program
84
110
  .command("info")
@@ -94,14 +120,16 @@ program
94
120
  // ─── help footer ─────────────────────────────────────────────────────────────
95
121
  program.addHelpText("after", `
96
122
  ${chalk_1.default.bold("Examples:")}
97
- ${chalk_1.default.cyan("clefbase init")} Set up a new project
98
- ${chalk_1.default.cyan("clefbase deploy")} Deploy your built site
99
- ${chalk_1.default.cyan("clefbase deploy -d ./dist")} Deploy from a specific directory
100
- ${chalk_1.default.cyan("clefbase deploy -m \"v2 release\"")} Deploy with a release note
101
- ${chalk_1.default.cyan("clefbase hosting:init")} Link or create a hosted site
102
- ${chalk_1.default.cyan("clefbase hosting:status")} Show current live deploy
103
- ${chalk_1.default.cyan("clefbase hosting:sites")} List all sites
104
- ${chalk_1.default.cyan("clefbase info")} Show config & connection status
123
+ ${chalk_1.default.cyan("clefbase init")} Set up a new project
124
+ ${chalk_1.default.cyan("clefbase deploy")} Deploy your built site
125
+ ${chalk_1.default.cyan("clefbase deploy -d ./dist")} Deploy from a specific directory
126
+ ${chalk_1.default.cyan("clefbase deploy -m \"v2 release\"")} Deploy with a release note
127
+ ${chalk_1.default.cyan("clefbase hosting:init")} Link or create a hosted site
128
+ ${chalk_1.default.cyan("clefbase hosting:status")} Show current live deploy
129
+ ${chalk_1.default.cyan("clefbase hosting:sites")} List all sites
130
+ ${chalk_1.default.cyan("clefbase hosting:dns")} Show DNS status
131
+ ${chalk_1.default.cyan("clefbase hosting:dns:reprovision")} Fix / create the preview CNAME
132
+ ${chalk_1.default.cyan("clefbase info")} Show config & connection status
105
133
  `);
106
134
  program.parse(process.argv);
107
135
  // ─── Helper ───────────────────────────────────────────────────────────────────