vela 0.23.0 → 0.24.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.
package/dist/bin.js CHANGED
@@ -20,7 +20,7 @@ import dotenv2 from "dotenv";
20
20
  // package.json
21
21
  var package_default = {
22
22
  name: "vela",
23
- version: "0.23.0",
23
+ version: "0.24.0",
24
24
  type: "module",
25
25
  description: "A CLI for creating and updating SvelteKit projects",
26
26
  license: "MIT",
@@ -894,7 +894,7 @@ var entrySchema = v2.object({
894
894
  ),
895
895
  /** The schema.org type its JSON-LD describes (`ProfessionalService`, `Restaurant`, …). */
896
896
  schemaType: v2.optional(v2.string()),
897
- /** Tarball location, relative to the index URL. */
897
+ /** Tarball location, relative to the index URL; a paid entry's names velastack.dev's download, which `resolveDownloadUrl` derives itself. */
898
898
  file: v2.string(),
899
899
  sha256: v2.pipe(v2.string(), v2.regex(/^[0-9a-f]{64}$/)),
900
900
  size: v2.optional(v2.number()),
@@ -957,10 +957,11 @@ async function readBytes(url, options) {
957
957
  signal: AbortSignal.timeout(options.timeoutMs)
958
958
  });
959
959
  if (!res.ok) {
960
- throw new Error(`${res.status} ${res.statusText}`.trim());
960
+ throw new DownloadRefused(res.status, res.statusText, await res.text().catch(() => ""));
961
961
  }
962
962
  return new Uint8Array(await res.arrayBuffer());
963
963
  }
964
+ var headersObject = (headers) => headers ? Object.fromEntries(new Headers(headers)) : {};
964
965
  async function fetchTemplateIndex(url, options = {}) {
965
966
  const now = options.now ?? Date.now();
966
967
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
@@ -981,9 +982,47 @@ async function fetchTemplateIndex(url, options = {}) {
981
982
  return { index: null, stale: false, reason };
982
983
  }
983
984
  }
984
- function resolveDownloadUrl(entry, indexUrl) {
985
+ var isPaidEntry = (entry) => (entry.price ?? 0) > 0;
986
+ function resolveDownloadUrl(entry, indexUrl, apiUrl = API_URL) {
987
+ if (isPaidEntry(entry)) return `${apiUrl}/v1/templates/${entry.name}/download`;
985
988
  return new URL(entry.file, indexUrl).href;
986
989
  }
990
+ var DownloadRefused = class extends Error {
991
+ status;
992
+ body;
993
+ constructor(status3, statusText, body) {
994
+ super(`${status3} ${statusText}`.trim());
995
+ this.status = status3;
996
+ this.body = body;
997
+ }
998
+ };
999
+ var TemplateNotPurchased = class extends Error {
1000
+ constructor(entry, url) {
1001
+ super(
1002
+ `${entry.title ?? entry.name} is a paid template (US$${entry.price}). Buy it for your team at ${url}, then run this again.`
1003
+ );
1004
+ this.entry = entry;
1005
+ this.url = url;
1006
+ }
1007
+ entry;
1008
+ url;
1009
+ name = "TemplateNotPurchased";
1010
+ };
1011
+ function paidDownloadError(entry, error) {
1012
+ if (error.status === 402) {
1013
+ let url = `https://velabase.dev/templates/${entry.name}`;
1014
+ try {
1015
+ const parsed = JSON.parse(error.body);
1016
+ if (typeof parsed.url === "string") url = parsed.url;
1017
+ } catch {
1018
+ }
1019
+ return new TemplateNotPurchased(entry, url);
1020
+ }
1021
+ if (error.status === 401) {
1022
+ return new Error(`velastack.dev did not accept your login. Run \`vela login\` and try again.`);
1023
+ }
1024
+ return new Error(`Could not download template ${entry.name}: ${error.message}`);
1025
+ }
987
1026
  function sha256(bytes) {
988
1027
  return createHash("sha256").update(bytes).digest("hex");
989
1028
  }
@@ -1003,11 +1042,19 @@ async function downloadTemplate(entry, indexUrl, options = {}) {
1003
1042
  haveTarball = sha256(cached2) === entry.sha256;
1004
1043
  }
1005
1044
  if (!haveTarball) {
1006
- const url = resolveDownloadUrl(entry, indexUrl);
1045
+ const paid = isPaidEntry(entry);
1046
+ if (paid && !options.apiKey) {
1047
+ throw new Error(
1048
+ `${entry.title ?? entry.name} is a paid template. Run \`vela login\` (or set VELA_API_KEY) so velastack.dev can check it was bought for you.`
1049
+ );
1050
+ }
1051
+ const url = resolveDownloadUrl(entry, indexUrl, options.apiUrl);
1052
+ const headers = paid ? { ...headersObject(options.headers), Authorization: `Bearer ${options.apiKey}` } : options.headers;
1007
1053
  let fetched;
1008
1054
  try {
1009
- fetched = await readBytes(url, { timeoutMs, headers: options.headers });
1055
+ fetched = await readBytes(url, { timeoutMs, headers });
1010
1056
  } catch (e) {
1057
+ if (paid && e instanceof DownloadRefused) throw paidDownloadError(entry, e);
1011
1058
  throw new Error(`Could not download template ${entry.name}: ${e.message}`);
1012
1059
  }
1013
1060
  const digest = sha256(fetched);
@@ -1115,13 +1162,13 @@ function templateChoicesMessage(templates) {
1115
1162
  }
1116
1163
  return [...groups].map(([category, names]) => `${category}: ${names.join(", ")}`).join("; ");
1117
1164
  }
1118
- async function resolveTemplate(template) {
1165
+ async function resolveTemplate(template, options = {}) {
1119
1166
  if (!("entry" in template)) {
1120
1167
  return { ...template, cleanup() {
1121
1168
  } };
1122
1169
  }
1123
1170
  const { entry, indexUrl, ...info } = template;
1124
- const dir = await downloadTemplate(entry, indexUrl);
1171
+ const dir = await downloadTemplate(entry, indexUrl, { apiKey: options.apiKey });
1125
1172
  return {
1126
1173
  ...info,
1127
1174
  dir,
@@ -3745,6 +3792,12 @@ async function createProject(apiKey, args) {
3745
3792
  })
3746
3793
  });
3747
3794
  }
3795
+ async function purchaseTemplate(apiKey, slug2, teamId) {
3796
+ return apiFetch(apiKey, `/v1/templates/${slug2}/purchase`, {
3797
+ method: "POST",
3798
+ body: JSON.stringify({ team: teamId })
3799
+ });
3800
+ }
3748
3801
  async function registerServer(apiKey, input) {
3749
3802
  return apiFetch(apiKey, "/v1/servers", {
3750
3803
  method: "POST",
@@ -6414,7 +6467,7 @@ async function createProject2(cwdArg, options, listing) {
6414
6467
  const template = findTemplate(listing, options.template ?? DEFAULT_TEMPLATE);
6415
6468
  checkFlagsForTemplate(template, options);
6416
6469
  const gallerySchema = options.schema ? findGallerySchema(options.schema) : void 0;
6417
- const resolved = await resolveCreatableTemplate(template);
6470
+ const resolved = await resolveCreatableTemplate(template, options);
6418
6471
  const onCancel2 = () => {
6419
6472
  resolved.cleanup();
6420
6473
  p12.cancel("Operation cancelled.");
@@ -6476,9 +6529,28 @@ async function createProject2(cwdArg, options, listing) {
6476
6529
  }
6477
6530
  return { directory: projectPath, packageManager: packageManager2, installed: installed2, migrated, name, template, link: link2 };
6478
6531
  }
6479
- async function resolveCreatableTemplate(template) {
6532
+ async function resolveCreatableTemplate(template, options = {}) {
6533
+ let apiKey = readApiKey();
6534
+ if ((template.price ?? 0) > 0 && !apiKey) {
6535
+ if (!isInteractive()) {
6536
+ throw new Error(
6537
+ `${template.name} is a paid template. Run \`vela login\` or set VELA_API_KEY, then run this again.`
6538
+ );
6539
+ }
6540
+ p12.log.info(
6541
+ `${template.name} is a paid template (US$${template.price}). Log in to velastack.dev to download it.`
6542
+ );
6543
+ apiKey = await loginInteractively();
6544
+ }
6480
6545
  if (template.source === "remote") p12.log.step(`Downloading template ${template.name}...`);
6481
- const resolved = await resolveTemplate(template);
6546
+ let resolved;
6547
+ try {
6548
+ resolved = await resolveTemplate(template, { apiKey });
6549
+ } catch (error) {
6550
+ if (!(error instanceof TemplateNotPurchased) || !apiKey || !isInteractive()) throw error;
6551
+ await buyTemplate(error, apiKey, options);
6552
+ resolved = await resolveTemplate(template, { apiKey });
6553
+ }
6482
6554
  const blocker = findKit3Blocker(resolved.dir);
6483
6555
  if (blocker) {
6484
6556
  resolved.cleanup();
@@ -6486,6 +6558,31 @@ async function resolveCreatableTemplate(template) {
6486
6558
  }
6487
6559
  return resolved;
6488
6560
  }
6561
+ async function buyTemplate(refusal, apiKey, options) {
6562
+ const { entry } = refusal;
6563
+ const title = entry.title ?? entry.name;
6564
+ p12.log.warn(`${title} is a paid template (US$${entry.price}).`);
6565
+ const team = await resolveTeam(await listTeams(apiKey), {
6566
+ teamId: options.team,
6567
+ interactive: true
6568
+ });
6569
+ const confirmed = await p12.confirm({
6570
+ message: `Buy ${title} for US$${entry.price} for ${team.name}? The card on file at velastack.dev is charged.`
6571
+ });
6572
+ if (p12.isCancel(confirmed) || !confirmed) throw refusal;
6573
+ if (options.link === void 0 || options.link === "new") options.team = team.id;
6574
+ const outcome = await purchaseTemplate(apiKey, entry.name, team.id);
6575
+ if (outcome.status === "purchased") {
6576
+ const card = outcome.last4 ? ` on the card ending ${outcome.last4}` : "";
6577
+ p12.log.success(`Bought ${title} for ${team.name}${card}. It is yours to deploy again any time.`);
6578
+ return;
6579
+ }
6580
+ p12.log.info(`${outcome.message}
6581
+ Buy it in the browser, then come back here:
6582
+ ${outcome.url}`);
6583
+ const done = await p12.confirm({ message: "Bought it? The download is tried again." });
6584
+ if (p12.isCancel(done) || !done) throw refusal;
6585
+ }
6489
6586
  async function scaffoldProject(cwdArg, options, template, resolved, onCancel2) {
6490
6587
  const interactive = isInteractive();
6491
6588
  let directory;