naro-cli 0.15.0 → 0.17.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.
@@ -5,9 +5,9 @@ import {
5
5
 
6
6
  // src/deploy.ts
7
7
  import { existsSync as existsSync7 } from "fs";
8
- import { cp as cp2, mkdtemp as mkdtemp2, readFile as readFile13, rm as rm3 } from "fs/promises";
8
+ import { cp as cp2, mkdtemp as mkdtemp2, readFile as readFile13, rm as rm4 } from "fs/promises";
9
9
  import { tmpdir as tmpdir2 } from "os";
10
- import { basename as basename3, dirname as dirname4, join as join24, resolve as resolve10 } from "path";
10
+ import { basename as basename3, dirname as dirname5, join as join24, resolve as resolve10 } from "path";
11
11
  import { fileURLToPath } from "url";
12
12
 
13
13
  // ../../packages/build/src/artifact-upload.ts
@@ -26,6 +26,7 @@ function artifactUrlKey(kind, name) {
26
26
  return `${kind}/${name}`;
27
27
  }
28
28
  var ARTIFACT_URL_BATCH_SIZE = 100;
29
+ var ARTIFACT_UPLOAD_CONCURRENCY = 8;
29
30
  function parseArtifactPutUrls(payload) {
30
31
  if (typeof payload !== "object" || payload === null) return null;
31
32
  const { urls, disabled } = payload;
@@ -37,28 +38,55 @@ function parseArtifactPutUrls(payload) {
37
38
  }
38
39
  return resolved;
39
40
  }
41
+ async function eachWithLimit(items, limit, work) {
42
+ let next = 0;
43
+ const lanes = Array.from(
44
+ { length: Math.min(limit, items.length) },
45
+ async () => {
46
+ while (next < items.length) {
47
+ const item = items[next];
48
+ next += 1;
49
+ await work(item);
50
+ }
51
+ }
52
+ );
53
+ await Promise.all(lanes);
54
+ }
40
55
  async function uploadArtifactFiles(artifact, ports) {
41
56
  const entries = artifactUploadEntries(artifact);
42
57
  let proxied = 0;
43
58
  for (let index = 0; index < entries.length; index += ARTIFACT_URL_BATCH_SIZE) {
44
59
  const batch = entries.slice(index, index + ARTIFACT_URL_BATCH_SIZE);
45
60
  const urls = await ports.resolvePutUrls(batch);
46
- for (const entry of batch) {
61
+ const viaProxy = [];
62
+ await eachWithLimit(batch, ARTIFACT_UPLOAD_CONCURRENCY, async (entry) => {
47
63
  const url2 = urls?.get(artifactUrlKey(entry.kind, entry.file.name));
48
64
  if (!url2) {
49
- proxied += 1;
50
- await ports.putProxied(entry);
51
- continue;
65
+ viaProxy.push(entry);
66
+ return;
52
67
  }
53
68
  try {
54
69
  await ports.putSigned(url2, entry);
55
70
  } catch {
56
- proxied += 1;
57
- await ports.putProxied(entry);
71
+ viaProxy.push(entry);
58
72
  }
73
+ });
74
+ for (const entry of viaProxy) {
75
+ proxied += 1;
76
+ await ports.putProxied(entry);
59
77
  }
60
78
  }
61
- return { proxied, total: entries.length };
79
+ const bytes = entries.reduce(
80
+ (total, entry) => total + entry.file.data.byteLength,
81
+ 0
82
+ );
83
+ return { proxied, total: entries.length, bytes };
84
+ }
85
+ function artifactUploadSummary(report, elapsedMs) {
86
+ const kilobytes = Math.ceil(report.bytes / 1e3);
87
+ const size = kilobytes >= 1e3 ? `${(report.bytes / 1e6).toFixed(1)} MB` : `${String(kilobytes)} kB`;
88
+ const files = report.total === 1 ? "file" : "files";
89
+ return `Uploaded ${String(report.total)} artifact ${files} (${size}) in ${(elapsedMs / 1e3).toFixed(1)} s.`;
62
90
  }
63
91
 
64
92
  // ../../packages/build/src/cache.ts
@@ -74,7 +102,7 @@ var ERROR_CODE = {
74
102
  deps: "INSTALL_FAILED",
75
103
  next: "BUILD_FAILED"
76
104
  };
77
- async function computeDepsCacheKey(checkoutDir, packageManager) {
105
+ async function computeDepsCacheKey(checkoutDir, packageManager, extra = []) {
78
106
  for (const name of LOCKFILES) {
79
107
  let content;
80
108
  try {
@@ -82,7 +110,10 @@ async function computeDepsCacheKey(checkoutDir, packageManager) {
82
110
  } catch {
83
111
  continue;
84
112
  }
85
- const hash2 = createHash("sha256").update(name).update("\n").update(content).digest("hex");
113
+ const hasher = createHash("sha256").update(name).update("\n").update(content);
114
+ if (extra.length > 0) hasher.update(`
115
+ ${extra.join("\n")}`);
116
+ const hash2 = hasher.digest("hex");
86
117
  return `${packageManager}-${hash2}`;
87
118
  }
88
119
  return null;
@@ -100,6 +131,7 @@ async function reportUnavailable(scope, ports, observer) {
100
131
  "Build cache is unavailable - this build runs uncached and saves nothing for the next one."
101
132
  );
102
133
  }
134
+ var UNKNOWN_PAX_KEYWORD = /^tar: Ignoring unknown extended header keyword /;
103
135
  async function restoreCache(args) {
104
136
  const { scope, key, targetDir, ports, observer } = args;
105
137
  const urls = await ports.transport.resolveUrls(scope, key);
@@ -132,7 +164,9 @@ async function restoreCache(args) {
132
164
  stage: STAGE[scope],
133
165
  errorCode: ERROR_CODE[scope]
134
166
  },
135
- observer
167
+ {
168
+ onLog: (entry) => UNKNOWN_PAX_KEYWORD.test(entry.message) ? void 0 : observer.onLog(entry)
169
+ }
136
170
  );
137
171
  await log(observer, scope, "INFO", `Restored ${scope} cache (${key}).`);
138
172
  return true;
@@ -160,7 +194,14 @@ async function saveCache(args) {
160
194
  await ports.runCommand(
161
195
  {
162
196
  command: "tar",
163
- args: ["--zstd", "-cf", archive, "-C", sourceDir, "."],
197
+ // --no-xattrs: macOS bsdtar would store extended attributes that the
198
+ // build container's GNU tar warns about per file (UNKNOWN_PAX_KEYWORD).
199
+ // GNU tar 1.27+ and bsdtar 3.x both take it.
200
+ args: ["--no-xattrs", "--zstd", "-cf", archive, "-C", sourceDir, "."],
201
+ // macOS can also write the same metadata as AppleDouble `._name` files,
202
+ // which GNU tar restores as ordinary files (PR #114 review; not seen on
203
+ // bsdtar 3.5.3 here). GNU tar ignores the variable.
204
+ env: { COPYFILE_DISABLE: "1" },
164
205
  cwd: sourceDir,
165
206
  timeoutMs: 12e4,
166
207
  stage: STAGE[scope],
@@ -224,9 +265,9 @@ function hashAsset(data, contentType2) {
224
265
  }
225
266
 
226
267
  // ../../packages/build/src/local-build-executor.ts
227
- import { access as access8, mkdtemp, readFile as readFile12, rm as rm2 } from "fs/promises";
268
+ import { access as access8, mkdtemp, readFile as readFile12, rm as rm3 } from "fs/promises";
228
269
  import { tmpdir } from "os";
229
- import { dirname as dirname3, join as join17, resolve as resolve7 } from "path";
270
+ import { dirname as dirname4, join as join17, resolve as resolve7 } from "path";
230
271
 
231
272
  // ../../packages/core/src/route-patterns.ts
232
273
  var ROUTE_PATTERN = /^\/[A-Za-z0-9\-._~%!$&()*+,;=:@/[\]]*$/;
@@ -257,6 +298,9 @@ var PHASES = [
257
298
  ];
258
299
  var ACTIVE_STATUSES = new Set(PHASES);
259
300
 
301
+ // ../../packages/core/src/deployment-stall.ts
302
+ var STALL_WINDOW_MS = 10 * 6e4;
303
+
260
304
  // ../../packages/core/src/deployment-strategy.ts
261
305
  var MIN_SUPPORTED_NEXT_MAJOR = 15;
262
306
 
@@ -15254,13 +15298,18 @@ function cacheEnv(dir) {
15254
15298
  return {
15255
15299
  npm_config_store_dir: dir,
15256
15300
  npm_config_cache: dir,
15257
- YARN_CACHE_FOLDER: dir
15301
+ YARN_CACHE_FOLDER: dir,
15302
+ YARN_ENABLE_GLOBAL_CACHE: "0"
15258
15303
  };
15259
15304
  }
15260
15305
  function withSharedCache(spec, dir) {
15261
15306
  return {
15262
15307
  ...spec,
15263
- addArgs: (specs) => [...spec.addArgs(specs), ...spec.cacheArgs(dir)],
15308
+ addArgs: (specs) => [
15309
+ ...spec.addArgs(specs),
15310
+ ...spec.sharedCacheAddArgs ?? [],
15311
+ ...spec.cacheArgs(dir)
15312
+ ],
15264
15313
  addEnv: { ...spec.addEnv, ...cacheEnv(dir) }
15265
15314
  };
15266
15315
  }
@@ -15301,7 +15350,24 @@ async function detectPackageManager(root) {
15301
15350
  install: ["npm", "ci"],
15302
15351
  cacheArgs: (dir) => ["--cache", dir],
15303
15352
  // --no-save keeps package.json and the lockfile untouched.
15304
- addArgs: (specs) => ["npm", "install", "--no-save", ...specs]
15353
+ addArgs: (specs) => ["npm", "install", "--no-save", ...specs],
15354
+ // --prefer-offline takes package metadata from the restored cache instead
15355
+ // of asking the registry again for each of the toolchain's ~160 packages
15356
+ // (npm keeps metadata in the same cache, which the deps cache saves).
15357
+ // Measured on one vCPU like the build container: 8.8 s → 7.9 s locally,
15358
+ // and the live add took 17 s against 8.8 s here — the gap is registry
15359
+ // round trips.
15360
+ //
15361
+ // npm trusts cached metadata outright: a cached packument that lacks the
15362
+ // asked-for version fails with ETARGET instead of being fetched again
15363
+ // (pacote leaves that revalidation as a TODO). That cannot happen in the
15364
+ // build's own cache because its key carries the toolchain
15365
+ // (computeDepsCacheKey): a toolchain bump starts from an empty cache, and
15366
+ // the cache restored under a key was filled by this same add over the
15367
+ // same lockfile. A machine's default cache has no such key, so without
15368
+ // the build cache (no zstd, no transport — mostly the CLI) the add stays
15369
+ // online (PR #134 review).
15370
+ sharedCacheAddArgs: ["--prefer-offline"]
15305
15371
  };
15306
15372
  }
15307
15373
  if (await exists(join2(root, "yarn.lock"))) {
@@ -15313,7 +15379,8 @@ async function detectPackageManager(root) {
15313
15379
  "install",
15314
15380
  modern ? "--immutable" : "--frozen-lockfile"
15315
15381
  ],
15316
- // Berry takes no install-time cache flag; YARN_CACHE_FOLDER covers it.
15382
+ // Berry takes no install-time cache flag; cacheEnv covers it
15383
+ // (YARN_CACHE_FOLDER with the global cache switched off).
15317
15384
  cacheArgs: (dir) => modern ? [] : ["--cache-folder", dir],
15318
15385
  addArgs: (specs) => ["yarn", "add", ...specs]
15319
15386
  };
@@ -15648,14 +15715,18 @@ async function pathExists(path) {
15648
15715
  return false;
15649
15716
  }
15650
15717
  }
15718
+ var BUILD_NPMRC = [
15719
+ "# Host package-manager credentials are intentionally unavailable.",
15720
+ "audit=false",
15721
+ "fund=false",
15722
+ "update-notifier=false",
15723
+ ""
15724
+ ].join("\n");
15651
15725
  async function createPackageManagerConfig(appRoot, writer = async (path, contents) => {
15652
15726
  await writeFile3(path, contents, "utf8");
15653
15727
  }) {
15654
15728
  const packageManagerConfigPath = join5(appRoot, ".next-deploy-user.npmrc");
15655
- await writer(
15656
- packageManagerConfigPath,
15657
- "# Host package-manager credentials are intentionally unavailable.\n"
15658
- );
15729
+ await writer(packageManagerConfigPath, BUILD_NPMRC);
15659
15730
  return packageManagerConfigPath;
15660
15731
  }
15661
15732
  async function createPlatformBuildConfig(appRoot, deploymentId, writer = async (path, contents) => {
@@ -17921,8 +17992,11 @@ function createVinextCloudflareStrategy(options) {
17921
17992
  }
17922
17993
 
17923
17994
  // ../../packages/build/src/strategy/workspace.ts
17924
- import { cp } from "fs/promises";
17925
- import { basename as basename2, join as join16, relative as relative5, resolve as resolve6 } from "path";
17995
+ import { execFile } from "child_process";
17996
+ import { cp, mkdir as mkdir2, readdir as readdir4, rm as rm2 } from "fs/promises";
17997
+ import { basename as basename2, dirname as dirname3, join as join16, relative as relative5, resolve as resolve6 } from "path";
17998
+ import { promisify } from "util";
17999
+ var execFileAsync = promisify(execFile);
17926
18000
  var SKIPPED_ANYWHERE = /* @__PURE__ */ new Set(["node_modules"]);
17927
18001
  function shouldCopy(path) {
17928
18002
  const name = basename2(path);
@@ -17937,6 +18011,33 @@ async function copyCheckout(source, target2) {
17937
18011
  filter: (path) => resolve6(path) === origin || shouldCopy(path)
17938
18012
  });
17939
18013
  }
18014
+ async function copyInstalledPackages(source, target2) {
18015
+ const origin = resolve6(source);
18016
+ const installs = [];
18017
+ const walk2 = async (dir) => {
18018
+ for (const entry of await readdir4(dir, { withFileTypes: true })) {
18019
+ if (!entry.isDirectory() || entry.name === ".git") continue;
18020
+ const path = join16(dir, entry.name);
18021
+ if (entry.name === "node_modules") installs.push(path);
18022
+ else await walk2(path);
18023
+ }
18024
+ };
18025
+ await walk2(origin);
18026
+ for (const install of installs)
18027
+ await copyTree(install, join16(target2, relative5(origin, install)));
18028
+ }
18029
+ async function copyTree(from, to) {
18030
+ await mkdir2(dirname3(to), { recursive: true });
18031
+ if (process.platform !== "win32") {
18032
+ try {
18033
+ await execFileAsync("cp", ["-a", from, to]);
18034
+ return;
18035
+ } catch {
18036
+ await rm2(to, { recursive: true, force: true });
18037
+ }
18038
+ }
18039
+ await cp(from, to, { recursive: true, verbatimSymlinks: true });
18040
+ }
17940
18041
  function forkedAppRoot(checkout, appRoot, forkCheckout) {
17941
18042
  return join16(forkCheckout, relative5(checkout, appRoot));
17942
18043
  }
@@ -17961,7 +18062,7 @@ async function withRouteList(artifact, list, appRoot, observer) {
17961
18062
  }
17962
18063
  var defaultLocalBuildDependencies = {
17963
18064
  createWorkspace: () => mkdtemp(join17(tmpdir(), "next-deploy-build-")),
17964
- removeWorkspace: (path) => rm2(path, { recursive: true, force: true }),
18065
+ removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
17965
18066
  runCommand,
17966
18067
  detectPackageManager,
17967
18068
  resolveAppRoot,
@@ -17981,6 +18082,7 @@ var defaultLocalBuildDependencies = {
17981
18082
  readVinextArtifact,
17982
18083
  vinextCommand: "vinext",
17983
18084
  forkWorkspace: copyCheckout,
18085
+ copyInstalledPackages,
17984
18086
  collectProjectFacts,
17985
18087
  listNextRoutes,
17986
18088
  hashFingerprintInputs: hashStrategyInputs,
@@ -18002,7 +18104,7 @@ async function readInstalledNextVersion(appRoot, checkout) {
18002
18104
  } catch {
18003
18105
  }
18004
18106
  if (dir === stop) return null;
18005
- const parent = dirname3(dir);
18107
+ const parent = dirname4(dir);
18006
18108
  if (parent === dir) return null;
18007
18109
  dir = parent;
18008
18110
  }
@@ -18220,7 +18322,11 @@ var LocalBuildExecutor = class {
18220
18322
  let depsKey = null;
18221
18323
  let depsRestored = false;
18222
18324
  if (cachePorts) {
18223
- depsKey = await computeDepsCacheKey(checkout, packageManager.name);
18325
+ depsKey = await computeDepsCacheKey(
18326
+ checkout,
18327
+ packageManager.name,
18328
+ isNext ? VINEXT_TOOLCHAIN : []
18329
+ );
18224
18330
  if (depsKey) {
18225
18331
  depsRestored = await tryRestore(
18226
18332
  "deps",
@@ -18305,6 +18411,7 @@ var LocalBuildExecutor = class {
18305
18411
  const fork = this.dependencies.forkWorkspace ?? defaultFork;
18306
18412
  await fork(checkout, pristine);
18307
18413
  let forks = 0;
18414
+ const resolverConcurrency = this.dependencies.resolverConcurrency ?? 1;
18308
18415
  const forkWorkspace = async (label2) => {
18309
18416
  const target2 = join17(
18310
18417
  workspace,
@@ -18312,7 +18419,9 @@ var LocalBuildExecutor = class {
18312
18419
  `${String(forks++)}-${label2.replace(/[^a-z0-9-]/gi, "-")}`
18313
18420
  );
18314
18421
  await fork(pristine, target2);
18315
- await install(target2);
18422
+ if (packageManager.name === "npm" && resolverConcurrency === 1)
18423
+ await (this.dependencies.copyInstalledPackages ?? copyInstalledPackages)(checkout, target2);
18424
+ else await install(target2);
18316
18425
  return {
18317
18426
  checkout: target2,
18318
18427
  appRoot: forkedAppRoot(checkout, appRoot, target2)
@@ -18355,7 +18464,7 @@ var LocalBuildExecutor = class {
18355
18464
  {
18356
18465
  pinned: input.strategy ?? null,
18357
18466
  fingerprint: fingerprint.hash,
18358
- concurrency: this.dependencies.resolverConcurrency ?? 1,
18467
+ concurrency: resolverConcurrency,
18359
18468
  ...this.dependencies.now ? { now: this.dependencies.now } : {}
18360
18469
  }
18361
18470
  );
@@ -18446,22 +18555,31 @@ function createLogBatcher(options) {
18446
18555
  let buffer = [];
18447
18556
  let timer = null;
18448
18557
  let pending = null;
18558
+ let sending = Promise.resolve();
18449
18559
  function disarm() {
18450
18560
  if (timer === null) return;
18451
18561
  clearTimeout(timer);
18452
18562
  timer = null;
18453
18563
  }
18564
+ function surfacePending() {
18565
+ if (pending === null) return;
18566
+ const { error: error51 } = pending;
18567
+ pending = null;
18568
+ throw error51;
18569
+ }
18454
18570
  async function flush() {
18455
18571
  disarm();
18456
- if (pending !== null) {
18457
- const { error: error51 } = pending;
18458
- pending = null;
18459
- throw error51;
18572
+ surfacePending();
18573
+ if (buffer.length === 0) {
18574
+ await sending;
18575
+ surfacePending();
18576
+ return;
18460
18577
  }
18461
- if (buffer.length === 0) return;
18462
18578
  const batch = buffer;
18463
18579
  buffer = [];
18464
- await options.send(batch);
18580
+ const send = sending.then(() => options.send(batch));
18581
+ sending = send.catch(() => void 0);
18582
+ await send;
18465
18583
  }
18466
18584
  function arm() {
18467
18585
  if (timer !== null || !options.intervalMs) return;
@@ -19481,6 +19599,31 @@ var DomainsApi = class {
19481
19599
  const match = await this.findByHostname(projectId, hostname3);
19482
19600
  return this.verify(projectId, match.id, options);
19483
19601
  }
19602
+ /**
19603
+ * Ownership checks the project opened for domains another project holds
19604
+ * (known-issues #156). Adding such a domain fails with
19605
+ * DOMAIN_VERIFICATION_REQUIRED and its check in `details.claim`.
19606
+ */
19607
+ async listClaims(projectId) {
19608
+ const body = await this.client.request(
19609
+ `/api/projects/${encodeURIComponent(projectId)}/domain-claims`,
19610
+ { context: "List ownership checks" }
19611
+ );
19612
+ return body.claims;
19613
+ }
19614
+ /** Reads the TXT record; when it matches, the domain moves to this project. */
19615
+ async verifyClaim(projectId, claimId) {
19616
+ return this.client.request(
19617
+ `/api/projects/${encodeURIComponent(projectId)}/domain-claims/${encodeURIComponent(claimId)}/verify`,
19618
+ { method: "POST", body: {}, context: "Verify domain ownership" }
19619
+ );
19620
+ }
19621
+ async removeClaim(projectId, claimId) {
19622
+ await this.client.request(
19623
+ `/api/projects/${encodeURIComponent(projectId)}/domain-claims/${encodeURIComponent(claimId)}`,
19624
+ { method: "DELETE", context: "Remove ownership check" }
19625
+ );
19626
+ }
19484
19627
  async removeById(projectId, domainId) {
19485
19628
  await this.client.request(
19486
19629
  `/api/projects/${encodeURIComponent(projectId)}/domains/${encodeURIComponent(domainId)}`,
@@ -19950,6 +20093,14 @@ function bucketFields(fields, into = {}) {
19950
20093
  if (fields[key] !== void 0) into[key] = fields[key];
19951
20094
  return into;
19952
20095
  }
20096
+ function listFields(options) {
20097
+ const body = {};
20098
+ if (options.prefix !== void 0) body.prefix = options.prefix;
20099
+ if (options.limit !== void 0) body.limit = options.limit;
20100
+ if (options.offset !== void 0) body.offset = options.offset;
20101
+ if (options.sortBy !== void 0) body.sortBy = options.sortBy;
20102
+ return body;
20103
+ }
19953
20104
  var BackendApi = class {
19954
20105
  constructor(client) {
19955
20106
  this.client = client;
@@ -20474,6 +20625,60 @@ var StorageApi = class {
20474
20625
  );
20475
20626
  return { deleted: body.deleted, done: body.done };
20476
20627
  }
20628
+ /**
20629
+ * What is directly below a folder of a bucket — its folders first, by
20630
+ * name, then its files — through the control plane's object proxy
20631
+ * (Storage 3-1e): the project's Naro worker lists it with the stored
20632
+ * service key, so every object is listed whatever the policies say. There
20633
+ * is no search. Needs `storage.manage`, Storage on and the backend on and
20634
+ * ready (`STORAGE_NOT_ENABLED`, `BACKEND_OFF`, `BACKEND_NOT_READY`, 409);
20635
+ * the worker's refusals — `BUCKET_NOT_FOUND`, `INVALID_REQUEST` with
20636
+ * `details.path` and `details.reason` — come through as they are.
20637
+ */
20638
+ async listObjects(projectId, bucket, options = {}) {
20639
+ const body = await this.client.request(
20640
+ this.bucketPath(projectId, bucket, "/objects/list"),
20641
+ { method: "POST", body: listFields(options), context: "List objects" }
20642
+ );
20643
+ return body.objects;
20644
+ }
20645
+ /**
20646
+ * A link that downloads one object without a key, for `expiresIn` seconds
20647
+ * (1–3600 through the control plane; `VALIDATION_FAILED` past it), on the
20648
+ * project's URL. Replacing or deleting the object ends it. The path is
20649
+ * held to the object name rules (`INVALID_PATH`); one not there is the
20650
+ * worker's `OBJECT_NOT_FOUND`.
20651
+ */
20652
+ async signObject(projectId, bucket, path, options) {
20653
+ const body = await this.client.request(
20654
+ this.bucketPath(projectId, bucket, "/objects/sign"),
20655
+ {
20656
+ method: "POST",
20657
+ body: {
20658
+ path,
20659
+ expiresIn: options.expiresIn,
20660
+ // false is no download (supabase-js): left out, since the worker
20661
+ // answers it 400 INVALID_REQUEST — it is not a file name.
20662
+ ...options.download === void 0 || options.download === false ? {} : { download: options.download }
20663
+ },
20664
+ context: "Sign an object URL"
20665
+ }
20666
+ );
20667
+ return { signedUrl: body.signedUrl };
20668
+ }
20669
+ /**
20670
+ * Deletes up to 1,000 objects by their full paths and answers the ones it
20671
+ * deleted, in the order asked: a path not there, or being written, is left
20672
+ * out — not an error (supabase-js's `remove`). A folder is not deleted by
20673
+ * its name; list it and delete what is in it.
20674
+ */
20675
+ async removeObjects(projectId, bucket, prefixes) {
20676
+ const body = await this.client.request(
20677
+ this.bucketPath(projectId, bucket, "/objects"),
20678
+ { method: "DELETE", body: { prefixes }, context: "Remove objects" }
20679
+ );
20680
+ return body.objects;
20681
+ }
20477
20682
  };
20478
20683
 
20479
20684
  // ../../packages/sdk/src/database.ts
@@ -21080,6 +21285,10 @@ var CATALOG = {
21080
21285
  status: 404,
21081
21286
  message: ({ bucket, path }) => `Object ${storageName(path)} was not found in bucket ${storageName(bucket)}.`
21082
21287
  },
21288
+ UPLOAD_NOT_FOUND: {
21289
+ status: 404,
21290
+ message: ({ upload }) => `Upload ${storageName(upload)} was not found or has expired.`
21291
+ },
21083
21292
  INVALID_CONTENT_TYPE: {
21084
21293
  status: 400,
21085
21294
  message: ({ type }) => `Content-Type ${storageName(type)} is not a media type like image/png.`
@@ -21094,7 +21303,7 @@ var CATALOG = {
21094
21303
  },
21095
21304
  OBJECT_TOO_LARGE: {
21096
21305
  status: 413,
21097
- message: (vars) => "single" in vars ? "Uploads over 50 MiB are not supported yet." : `The object is ${String(vars.size)} bytes; bucket ${storageName(vars.bucket)} takes objects up to ${String(vars.max)} bytes.`
21306
+ message: (vars) => "single" in vars ? "Uploads over 50 MiB go in parts: use the multipart upload." : `The object is ${String(vars.size)} bytes; bucket ${storageName(vars.bucket)} takes objects up to ${String(vars.max)} bytes.`
21098
21307
  },
21099
21308
  MIME_TYPE_NOT_ALLOWED: {
21100
21309
  status: 415,
@@ -25840,7 +26049,7 @@ function claimUpload(i) {
25840
26049
  const nonce = nonceOf(i.nonce);
25841
26050
  return compose(
25842
26051
  [
25843
- `INSERT INTO ${STORAGE_OBJECTS_TABLE} (id, bucket_id, name, owner, owner_id, metadata, version, parent, size, status, lock_id, lock_expires_at) SELECT ?, ?, ?, ?, ?, ?, NULL, ?, ?, 'uploading', ?, ? WHERE CASE WHEN NOT EXISTS (SELECT 1 FROM ${STORAGE_BUCKETS_TABLE} WHERE id = ? AND ${LIVE_BUCKET}) THEN ${marker("bucket", nonce)} WHEN (`,
26052
+ `INSERT INTO ${STORAGE_OBJECTS_TABLE} (id, bucket_id, name, owner, owner_id, metadata, version, parent, size, status, lock_id, lock_expires_at, pending) SELECT ?, ?, ?, ?, ?, ?, NULL, ?, ?, 'uploading', ?, ?, ? WHERE CASE WHEN NOT EXISTS (SELECT 1 FROM ${STORAGE_BUCKETS_TABLE} WHERE id = ? AND ${LIVE_BUCKET}) THEN ${marker("bucket", nonce)} WHEN (`,
25844
26053
  i.check,
25845
26054
  `) <> 1 THEN 0 WHEN EXISTS (SELECT 1 FROM ${STORAGE_OBJECTS_TABLE} WHERE bucket_id = ? AND name = ? AND status = 'ready') THEN ${marker("exists", nonce)} WHEN EXISTS (SELECT 1 FROM ${STORAGE_OBJECTS_TABLE} WHERE bucket_id = ? AND name = ?) THEN ${marker("busy", nonce)} WHEN ${PROJECT_BYTES} + ? > ? THEN ${marker("quota", nonce)} ELSE 1 END RETURNING id`
25846
26055
  ],
@@ -25856,6 +26065,7 @@ function claimUpload(i) {
25856
26065
  i.size,
25857
26066
  i.lockId,
25858
26067
  i.expiresAt,
26068
+ i.pending ?? null,
25859
26069
  i.bucket
25860
26070
  ],
25861
26071
  [i.bucket, i.name, i.bucket, i.name, i.size, i.limitBytes]
@@ -25879,6 +26089,13 @@ function overwriteCheckRow(i) {
25879
26089
  }
25880
26090
  function claimOverwrite(i) {
25881
26091
  const nonce = nonceOf(i.nonce);
26092
+ const room = i.inParts ? {
26093
+ sql: `${PROJECT_BYTES} + ? > ?`,
26094
+ values: [i.size, i.limitBytes]
26095
+ } : {
26096
+ sql: `? > o.size AND ${PROJECT_BYTES} + ? - o.size > ?`,
26097
+ values: [i.size, i.size, i.limitBytes]
26098
+ };
25882
26099
  return compose(
25883
26100
  [
25884
26101
  `UPDATE ${STORAGE_OBJECTS_TABLE} AS o SET lock_id = ?, lock_expires_at = ?, pending = ? WHERE o.bucket_id = ? AND o.name = ? AND CASE WHEN NOT (`,
@@ -25886,17 +26103,14 @@ function claimOverwrite(i) {
25886
26103
  // The row's own bucket (`o.bucket_id`), so no value is bound for it.
25887
26104
  `) THEN ${marker("hidden", nonce)} WHEN NOT EXISTS (SELECT 1 FROM ${STORAGE_BUCKETS_TABLE} AS b WHERE b.id = o.bucket_id AND b.${LIVE_BUCKET}) THEN ${marker("bucket", nonce)} WHEN o.status <> 'ready' OR o.lock_id IS NOT NULL THEN ${marker("busy", nonce)} WHEN NOT (`,
25888
26105
  i.updateUsing,
25889
- `) THEN ${marker("denied", nonce)} WHEN ? > o.size AND ${PROJECT_BYTES} + ? - o.size > ? THEN ${marker("quota", nonce)} ELSE (`,
26106
+ `) THEN ${marker("denied", nonce)} WHEN ${room.sql} THEN ${marker("quota", nonce)} ELSE (`,
25890
26107
  i.check,
25891
26108
  // RETURNING names the columns bare: it does not see the UPDATE's
25892
26109
  // alias (SQLite 3.53: `no such column: o.id`), and the table is the
25893
26110
  // only one there.
25894
26111
  ") END RETURNING id, version, size, owner, owner_id, created_at"
25895
26112
  ],
25896
- [
25897
- [i.lockId, i.expiresAt, i.pending, i.bucket, i.name],
25898
- [i.size, i.size, i.limitBytes]
25899
- ]
26113
+ [[i.lockId, i.expiresAt, i.pending, i.bucket, i.name], room.values]
25900
26114
  );
25901
26115
  }
25902
26116
  function claimDelete(i) {
@@ -27075,6 +27289,17 @@ function formatRedirectEntry(entry) {
27075
27289
  return entry.path === "/" ? entry.origin : `${entry.origin}${entry.path}`;
27076
27290
  }
27077
27291
 
27292
+ // ../../packages/data-core/src/storage-multipart.ts
27293
+ var MiB = 1024 * 1024;
27294
+ var MULTIPART_PART_SIZES = [16 * MiB, 32 * MiB, 64 * MiB];
27295
+ var MAX_MULTIPART_PARTS = 1e4;
27296
+ var MAX_MULTIPART_BYTES = 64 * MiB * MAX_MULTIPART_PARTS;
27297
+ var MULTIPART_LOCK_MS = 60 * 60 * 1e3;
27298
+ var MULTIPART_PART_EXTEND_BELOW_MS = 30 * 60 * 1e3;
27299
+ var MULTIPART_COMPLETE_LOCK_MS = 15 * 60 * 1e3;
27300
+ var UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
27301
+ var UPLOAD_ID = new RegExp(`^(${UUID})\\.(${UUID})$`);
27302
+
27078
27303
  // ../../packages/data-core/src/system-sql.ts
27079
27304
  async function convertLegacyPolicies(db, found) {
27080
27305
  const rows = rowsOf(await db.prepare(POLICY_SQL.list).all());
@@ -28063,14 +28288,14 @@ async function promptForMissingEnv(ctx, keys) {
28063
28288
  }
28064
28289
 
28065
28290
  // src/git-info.ts
28066
- import { execFile } from "child_process";
28291
+ import { execFile as execFile2 } from "child_process";
28067
28292
  var SUPPORTED_REMOTE = [
28068
28293
  /^https:\/\/[^\s]+$/,
28069
28294
  /^ssh:\/\/[^\s]+$/,
28070
28295
  /^git@[A-Za-z0-9.-]+:[A-Za-z0-9._/-]+(\.git)?$/
28071
28296
  ];
28072
28297
  var defaultExec = (args, cwd) => new Promise((resolve11, reject) => {
28073
- execFile("git", args, { cwd }, (error51, stdout) => {
28298
+ execFile2("git", args, { cwd }, (error51, stdout) => {
28074
28299
  if (error51) reject(error51);
28075
28300
  else resolve11(stdout);
28076
28301
  });
@@ -28203,7 +28428,9 @@ function manifest(artifact) {
28203
28428
  };
28204
28429
  }
28205
28430
  async function uploadArtifact(artifact, opts) {
28206
- await uploadArtifactFiles(artifact, uploadPorts(opts));
28431
+ const started = Date.now();
28432
+ const report = await uploadArtifactFiles(artifact, uploadPorts(opts));
28433
+ const uploadSummary = artifactUploadSummary(report, Date.now() - started);
28207
28434
  const response = await authed(opts, "/artifacts/complete", {
28208
28435
  method: "POST",
28209
28436
  headers: { "Content-Type": "application/json" },
@@ -28214,7 +28441,8 @@ async function uploadArtifact(artifact, opts) {
28214
28441
  throw new Error("Control plane did not return an artifact key.");
28215
28442
  return {
28216
28443
  artifactKey: payload.artifactKey,
28217
- previewUrl: typeof payload.previewUrl === "string" ? payload.previewUrl : null
28444
+ previewUrl: typeof payload.previewUrl === "string" ? payload.previewUrl : null,
28445
+ uploadSummary
28218
28446
  };
28219
28447
  }
28220
28448
 
@@ -28290,7 +28518,8 @@ function eventsObserver(cp3, deploymentId, token, fetchImpl = fetch, logIngestUr
28290
28518
  await flushLogs();
28291
28519
  await post({ type: "metadata", metadata: rest }).catch(() => {
28292
28520
  });
28293
- }
28521
+ },
28522
+ flush: flushLogs
28294
28523
  };
28295
28524
  }
28296
28525
  function slugify2(value) {
@@ -28309,11 +28538,11 @@ async function projectName(sourceDir) {
28309
28538
  }
28310
28539
  var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.turbo|\.naro)([\\/]|$)/;
28311
28540
  function resolveBin(name) {
28312
- let dir = dirname4(fileURLToPath(import.meta.url));
28541
+ let dir = dirname5(fileURLToPath(import.meta.url));
28313
28542
  for (; ; ) {
28314
28543
  const candidate = join24(dir, "node_modules", ".bin", name);
28315
28544
  if (existsSync7(candidate)) return candidate;
28316
- const parent = dirname4(dir);
28545
+ const parent = dirname5(dir);
28317
28546
  if (parent === dir) return name;
28318
28547
  dir = parent;
28319
28548
  }
@@ -28339,7 +28568,7 @@ async function detectPackageManagerLenient(root) {
28339
28568
  function localDependencies(sourceDir) {
28340
28569
  return {
28341
28570
  createWorkspace: () => mkdtemp2(join24(tmpdir2(), "naro-build-")),
28342
- removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
28571
+ removeWorkspace: (path) => rm4(path, { recursive: true, force: true }),
28343
28572
  async runCommand(spec, observer) {
28344
28573
  if (spec.command === "git" && spec.args[0] === "clone") {
28345
28574
  const checkout = spec.args.at(-1);
@@ -28485,12 +28714,19 @@ async function deployDirectory(sourceDir, options, log2) {
28485
28714
  { details: { deploymentId: ctx.deploymentId }, cause: error51 }
28486
28715
  );
28487
28716
  }
28488
- await uploadArtifact(artifact, {
28717
+ const { uploadSummary } = await uploadArtifact(artifact, {
28489
28718
  controlPlaneUrl: cpUrl,
28490
28719
  deploymentId: ctx.deploymentId,
28491
28720
  callbackToken: ctx.callbackToken,
28492
28721
  ...options.fetch ? { fetch: options.fetch } : {}
28493
28722
  });
28723
+ log2(`\u25B8 ${uploadSummary}`);
28724
+ await observer.onLog({
28725
+ level: "INFO",
28726
+ stage: "DEPLOYING",
28727
+ message: uploadSummary
28728
+ });
28729
+ await observer.flush();
28494
28730
  log2("\u25B8 publishing worker");
28495
28731
  const detail = await waitForFinish(client, ctx, log2);
28496
28732
  const url2 = deploymentUrl(detail, ctx, production);
@@ -28568,7 +28804,6 @@ export {
28568
28804
  parseCacheControl,
28569
28805
  STORAGE_OBJECTS_TABLE,
28570
28806
  systemTablePredicate,
28571
- dataError,
28572
28807
  SCHEMA_SQL,
28573
28808
  tableNames,
28574
28809
  snapshotFromRows,