neonctl 2.28.0 → 2.29.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 (135) hide show
  1. package/README.md +2 -2
  2. package/dist/analytics.js +35 -33
  3. package/dist/api.js +34 -34
  4. package/dist/auth.js +50 -44
  5. package/dist/cli.js +2 -2
  6. package/dist/commands/auth.js +58 -52
  7. package/dist/commands/bootstrap.js +115 -157
  8. package/dist/commands/branches.js +154 -147
  9. package/dist/commands/bucket.js +124 -118
  10. package/dist/commands/checkout.js +49 -49
  11. package/dist/commands/config.js +212 -88
  12. package/dist/commands/connection_string.js +62 -62
  13. package/dist/commands/data_api.js +96 -96
  14. package/dist/commands/databases.js +23 -23
  15. package/dist/commands/deploy.js +12 -12
  16. package/dist/commands/dev.js +114 -114
  17. package/dist/commands/env.js +43 -43
  18. package/dist/commands/functions.js +97 -98
  19. package/dist/commands/index.js +26 -26
  20. package/dist/commands/init.js +23 -22
  21. package/dist/commands/ip_allow.js +29 -29
  22. package/dist/commands/link.js +223 -166
  23. package/dist/commands/neon_auth.js +381 -363
  24. package/dist/commands/operations.js +11 -11
  25. package/dist/commands/orgs.js +8 -8
  26. package/dist/commands/projects.js +101 -99
  27. package/dist/commands/psql.js +31 -31
  28. package/dist/commands/roles.js +21 -21
  29. package/dist/commands/schema_diff.js +23 -23
  30. package/dist/commands/set_context.js +17 -17
  31. package/dist/commands/status.js +17 -17
  32. package/dist/commands/user.js +5 -5
  33. package/dist/commands/vpc_endpoints.js +50 -50
  34. package/dist/config.js +7 -7
  35. package/dist/config_format.js +5 -5
  36. package/dist/context.js +23 -16
  37. package/dist/current_branch_fast_path.js +6 -6
  38. package/dist/dev/env.js +33 -33
  39. package/dist/dev/functions.js +4 -4
  40. package/dist/dev/inputs.js +6 -6
  41. package/dist/dev/runtime.js +25 -25
  42. package/dist/env.js +14 -14
  43. package/dist/env_file.js +13 -13
  44. package/dist/errors.js +19 -19
  45. package/dist/functions_api.js +10 -10
  46. package/dist/help.js +15 -15
  47. package/dist/index.js +94 -92
  48. package/dist/log.js +2 -2
  49. package/dist/pkg.js +5 -5
  50. package/dist/psql/cli.js +4 -2
  51. package/dist/psql/command/cmd_cond.js +61 -61
  52. package/dist/psql/command/cmd_connect.js +159 -154
  53. package/dist/psql/command/cmd_copy.js +107 -97
  54. package/dist/psql/command/cmd_describe.js +368 -363
  55. package/dist/psql/command/cmd_format.js +276 -263
  56. package/dist/psql/command/cmd_io.js +269 -263
  57. package/dist/psql/command/cmd_lo.js +74 -66
  58. package/dist/psql/command/cmd_meta.js +148 -148
  59. package/dist/psql/command/cmd_misc.js +17 -17
  60. package/dist/psql/command/cmd_pipeline.js +142 -135
  61. package/dist/psql/command/cmd_restrict.js +25 -25
  62. package/dist/psql/command/cmd_show.js +183 -168
  63. package/dist/psql/command/dispatch.js +26 -26
  64. package/dist/psql/command/shared.js +14 -14
  65. package/dist/psql/complete/filenames.js +16 -16
  66. package/dist/psql/complete/index.js +4 -4
  67. package/dist/psql/complete/matcher.js +33 -32
  68. package/dist/psql/complete/psqlVars.js +173 -173
  69. package/dist/psql/complete/queries.js +5 -3
  70. package/dist/psql/complete/rules.js +900 -863
  71. package/dist/psql/core/common.js +136 -133
  72. package/dist/psql/core/help.js +343 -343
  73. package/dist/psql/core/mainloop.js +160 -153
  74. package/dist/psql/core/prompt.js +126 -123
  75. package/dist/psql/core/settings.js +111 -111
  76. package/dist/psql/core/sqlHelp.js +150 -150
  77. package/dist/psql/core/startup.js +211 -205
  78. package/dist/psql/core/syncVars.js +14 -14
  79. package/dist/psql/core/variables.js +24 -24
  80. package/dist/psql/describe/formatters.js +302 -289
  81. package/dist/psql/describe/processNamePattern.js +28 -28
  82. package/dist/psql/describe/queries.js +656 -651
  83. package/dist/psql/index.js +436 -411
  84. package/dist/psql/io/history.js +36 -36
  85. package/dist/psql/io/input.js +15 -15
  86. package/dist/psql/io/lineEditor/buffer.js +27 -25
  87. package/dist/psql/io/lineEditor/complete.js +15 -15
  88. package/dist/psql/io/lineEditor/filename.js +22 -22
  89. package/dist/psql/io/lineEditor/index.js +65 -62
  90. package/dist/psql/io/lineEditor/keymap.js +325 -318
  91. package/dist/psql/io/lineEditor/vt100.js +60 -60
  92. package/dist/psql/io/pgpass.js +18 -18
  93. package/dist/psql/io/pgservice.js +14 -14
  94. package/dist/psql/io/psqlrc.js +46 -46
  95. package/dist/psql/print/aligned.js +175 -166
  96. package/dist/psql/print/asciidoc.js +51 -51
  97. package/dist/psql/print/crosstab.js +34 -31
  98. package/dist/psql/print/csv.js +25 -22
  99. package/dist/psql/print/html.js +54 -54
  100. package/dist/psql/print/json.js +12 -12
  101. package/dist/psql/print/latex.js +118 -118
  102. package/dist/psql/print/pager.js +28 -26
  103. package/dist/psql/print/troff.js +48 -48
  104. package/dist/psql/print/unaligned.js +15 -14
  105. package/dist/psql/print/units.js +17 -17
  106. package/dist/psql/scanner/slash.js +48 -46
  107. package/dist/psql/scanner/sql.js +88 -84
  108. package/dist/psql/scanner/stringutils.js +21 -17
  109. package/dist/psql/types/index.js +7 -7
  110. package/dist/psql/types/scanner.js +8 -8
  111. package/dist/psql/wire/connection.js +341 -327
  112. package/dist/psql/wire/copy.js +7 -7
  113. package/dist/psql/wire/pipeline.js +26 -24
  114. package/dist/psql/wire/protocol.js +102 -102
  115. package/dist/psql/wire/sasl.js +62 -62
  116. package/dist/psql/wire/tls.js +79 -73
  117. package/dist/storage_api.js +15 -15
  118. package/dist/test_utils/fixtures.js +34 -31
  119. package/dist/test_utils/oauth_server.js +5 -5
  120. package/dist/utils/api_enums.js +13 -13
  121. package/dist/utils/branch_notice.js +5 -5
  122. package/dist/utils/branch_picker.js +26 -26
  123. package/dist/utils/compute_units.js +4 -4
  124. package/dist/utils/enrichers.js +20 -15
  125. package/dist/utils/esbuild.js +28 -28
  126. package/dist/utils/formats.js +1 -1
  127. package/dist/utils/middlewares.js +3 -3
  128. package/dist/utils/package_manager.js +68 -0
  129. package/dist/utils/point_in_time.js +12 -12
  130. package/dist/utils/psql.js +30 -30
  131. package/dist/utils/string.js +2 -2
  132. package/dist/utils/ui.js +9 -9
  133. package/dist/utils/zip.js +1 -1
  134. package/dist/writer.js +17 -17
  135. package/package.json +6 -7
@@ -1,15 +1,15 @@
1
- import { createReadStream, createWriteStream } from 'node:fs';
2
- import { stat, unlink } from 'node:fs/promises';
3
- import { basename } from 'node:path';
4
- import { pipeline } from 'node:stream/promises';
5
- import { isNeonApiError, retryOnLock } from '../api.js';
6
- import { branchIdFromProps, fillSingleProject } from '../utils/enrichers.js';
7
- import { log } from '../log.js';
8
- import { writer } from '../writer.js';
9
- import { createProjectBranchBucket, listProjectBranchBuckets, deleteProjectBranchBucket, listProjectBranchBucketObjects, getProjectBranchBucketObject, deleteProjectBranchBucketObject, deleteProjectBranchBucketObjectsByPrefix, presignUpload, } from '../storage_api.js';
10
- const OBJECT_FIELDS = ['key', 'size', 'last_modified', 'etag'];
11
- const BUCKET_FIELDS = ['name', 'access_level'];
12
- const ACCESS_LEVELS = ['private', 'public_read'];
1
+ import { createReadStream, createWriteStream } from "node:fs";
2
+ import { stat, unlink } from "node:fs/promises";
3
+ import { basename } from "node:path";
4
+ import { pipeline } from "node:stream/promises";
5
+ import { isNeonApiError, retryOnLock } from "../api.js";
6
+ import { log } from "../log.js";
7
+ import { createProjectBranchBucket, deleteProjectBranchBucket, deleteProjectBranchBucketObject, deleteProjectBranchBucketObjectsByPrefix, getProjectBranchBucketObject, listProjectBranchBucketObjects, listProjectBranchBuckets, presignUpload, } from "../storage_api.js";
8
+ import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
9
+ import { writer } from "../writer.js";
10
+ const OBJECT_FIELDS = ["key", "size", "last_modified", "etag"];
11
+ const BUCKET_FIELDS = ["name", "access_level"];
12
+ const ACCESS_LEVELS = ["private", "public_read"];
13
13
  // Single-PUT upload cap. Objects larger than this must use multipart upload,
14
14
  // which is out of scope for v1; we reject them client-side before any HTTP so
15
15
  // the user gets an immediate, clear error rather than a server-side rejection
@@ -18,13 +18,13 @@ const MAX_OBJECT_BYTES = 100 * 1024 * 1024; // 100 MB
18
18
  // Ambient scope shared by every bucket sub-command. The bucket name (and the
19
19
  // object key/prefix) is always a positional, never a flag.
20
20
  const scopeOptions = {
21
- 'project-id': {
22
- describe: 'Project ID',
23
- type: 'string',
21
+ "project-id": {
22
+ describe: "Project ID",
23
+ type: "string",
24
24
  },
25
25
  branch: {
26
- describe: 'Branch ID or name',
27
- type: 'string',
26
+ describe: "Branch ID or name",
27
+ type: "string",
28
28
  },
29
29
  };
30
30
  // Split an object target into its bucket and the remainder (key or prefix) on
@@ -32,156 +32,156 @@ const scopeOptions = {
32
32
  // remainder may contain further slashes and is returned verbatim. When the
33
33
  // target has no slash, `rest` is the empty string.
34
34
  export const splitBucketTarget = (target) => {
35
- const slash = target.indexOf('/');
35
+ const slash = target.indexOf("/");
36
36
  if (slash === -1) {
37
- return { bucket: target, rest: '' };
37
+ return { bucket: target, rest: "" };
38
38
  }
39
39
  return {
40
40
  bucket: target.slice(0, slash),
41
41
  rest: target.slice(slash + 1),
42
42
  };
43
43
  };
44
- export const command = 'buckets';
45
- export const describe = 'Manage branch object-storage buckets and their objects';
46
- export const aliases = ['bucket'];
44
+ export const command = "buckets";
45
+ export const describe = "Manage branch object-storage buckets and their objects";
46
+ export const aliases = ["bucket"];
47
47
  export const builder = (argv) => argv
48
- .usage('$0 bucket <sub-command> [options]')
48
+ .usage("$0 bucket <sub-command> [options]")
49
49
  .options({
50
- 'project-id': {
51
- describe: 'Project ID',
52
- type: 'string',
50
+ "project-id": {
51
+ describe: "Project ID",
52
+ type: "string",
53
53
  },
54
54
  })
55
55
  .middleware(fillSingleProject)
56
- .command('create <name>', 'Create a bucket on a branch', (yargs) => yargs
57
- .usage('$0 bucket create <name> [options]')
58
- .positional('name', {
59
- describe: 'The bucket name to create',
60
- type: 'string',
56
+ .command("create <name>", "Create a bucket on a branch", (yargs) => yargs
57
+ .usage("$0 bucket create <name> [options]")
58
+ .positional("name", {
59
+ describe: "The bucket name to create",
60
+ type: "string",
61
61
  demandOption: true,
62
62
  })
63
63
  .options({
64
64
  ...scopeOptions,
65
- 'access-level': {
66
- describe: 'The visibility of the bucket',
67
- type: 'string',
65
+ "access-level": {
66
+ describe: "The visibility of the bucket",
67
+ type: "string",
68
68
  choices: ACCESS_LEVELS,
69
- default: 'private',
69
+ default: "private",
70
70
  },
71
71
  }), (args) => createBucket(args))
72
72
  .command({
73
- command: 'list',
74
- aliases: ['ls'],
75
- describe: 'List the buckets on a branch',
76
- builder: (yargs) => yargs.usage('$0 bucket list [options]').options(scopeOptions),
73
+ command: "list",
74
+ aliases: ["ls"],
75
+ describe: "List the buckets on a branch",
76
+ builder: (yargs) => yargs.usage("$0 bucket list [options]").options(scopeOptions),
77
77
  handler: (args) => listBuckets(args),
78
78
  })
79
79
  .command({
80
- command: 'delete <name>',
81
- aliases: ['rm'],
82
- describe: 'Delete a bucket from a branch',
80
+ command: "delete <name>",
81
+ aliases: ["rm"],
82
+ describe: "Delete a bucket from a branch",
83
83
  builder: (yargs) => yargs
84
- .usage('$0 bucket delete <name> [options]')
85
- .positional('name', {
86
- describe: 'The bucket name to delete',
87
- type: 'string',
84
+ .usage("$0 bucket delete <name> [options]")
85
+ .positional("name", {
86
+ describe: "The bucket name to delete",
87
+ type: "string",
88
88
  demandOption: true,
89
89
  })
90
90
  .options(scopeOptions),
91
91
  handler: (args) => deleteBucket(args),
92
92
  })
93
- .command('object <sub-command>', 'List, download, upload or delete objects in a bucket', (yargs) => yargs
94
- .usage('$0 bucket object <sub-command> [options]')
93
+ .command("object <sub-command>", "List, download, upload or delete objects in a bucket", (yargs) => yargs
94
+ .usage("$0 bucket object <sub-command> [options]")
95
95
  .command({
96
- command: 'list <target>',
97
- aliases: ['ls'],
96
+ command: "list <target>",
97
+ aliases: ["ls"],
98
98
  describe: 'List objects in a bucket. By default folders are collapsed (like "aws s3 ls"); pass --recursive for a flat listing of every key',
99
99
  builder: (yargs) => yargs
100
- .usage('$0 bucket object list <bucket>[/<prefix>] [options]')
101
- .positional('target', {
102
- describe: 'The bucket to list, optionally with a key prefix: <bucket>[/<prefix>]',
103
- type: 'string',
100
+ .usage("$0 bucket object list <bucket>[/<prefix>] [options]")
101
+ .positional("target", {
102
+ describe: "The bucket to list, optionally with a key prefix: <bucket>[/<prefix>]",
103
+ type: "string",
104
104
  demandOption: true,
105
105
  })
106
106
  .options({
107
107
  ...scopeOptions,
108
108
  recursive: {
109
109
  describe: 'List every key flat, descending into nested folders (no delimiter). Mutually exclusive with --delimiter. Mirrors "aws s3 ls --recursive"',
110
- type: 'boolean',
110
+ type: "boolean",
111
111
  default: false,
112
112
  },
113
113
  delimiter: {
114
114
  describe: 'Collapse keys sharing this prefix separator into folders. Defaults to "/" (folder view); ignored when --recursive is set',
115
- type: 'string',
115
+ type: "string",
116
116
  },
117
117
  cursor: {
118
- describe: 'Pagination cursor returned as next_cursor by a previous call',
119
- type: 'string',
118
+ describe: "Pagination cursor returned as next_cursor by a previous call",
119
+ type: "string",
120
120
  },
121
121
  limit: {
122
- describe: 'Maximum number of items (objects + folders) to return',
123
- type: 'number',
122
+ describe: "Maximum number of items (objects + folders) to return",
123
+ type: "number",
124
124
  },
125
125
  }),
126
126
  handler: (args) => listObjects(args),
127
127
  })
128
- .command('get <target>', 'Download an object from a bucket to a local file', (yargs) => yargs
129
- .usage('$0 bucket object get <bucket>/<key> [options]')
130
- .positional('target', {
131
- describe: 'The object to download: <bucket>/<key>',
132
- type: 'string',
128
+ .command("get <target>", "Download an object from a bucket to a local file", (yargs) => yargs
129
+ .usage("$0 bucket object get <bucket>/<key> [options]")
130
+ .positional("target", {
131
+ describe: "The object to download: <bucket>/<key>",
132
+ type: "string",
133
133
  demandOption: true,
134
134
  })
135
135
  .options({
136
136
  ...scopeOptions,
137
137
  file: {
138
- describe: 'Path to write the downloaded object to (defaults to the object filename in the current directory)',
139
- type: 'string',
138
+ describe: "Path to write the downloaded object to (defaults to the object filename in the current directory)",
139
+ type: "string",
140
140
  },
141
141
  }), (args) => getObject(args))
142
- .command('put <target>', 'Upload a local file to a bucket as an object', (yargs) => yargs
143
- .usage('$0 bucket object put <bucket>/<key> [options]')
144
- .positional('target', {
145
- describe: 'The object to upload to: <bucket>/<key>',
146
- type: 'string',
142
+ .command("put <target>", "Upload a local file to a bucket as an object", (yargs) => yargs
143
+ .usage("$0 bucket object put <bucket>/<key> [options]")
144
+ .positional("target", {
145
+ describe: "The object to upload to: <bucket>/<key>",
146
+ type: "string",
147
147
  demandOption: true,
148
148
  })
149
149
  .options({
150
150
  ...scopeOptions,
151
151
  file: {
152
- describe: 'Path to the local file to upload',
153
- type: 'string',
152
+ describe: "Path to the local file to upload",
153
+ type: "string",
154
154
  demandOption: true,
155
155
  },
156
- 'content-type': {
157
- describe: 'Content-Type to store the object with (e.g. text/plain)',
158
- type: 'string',
156
+ "content-type": {
157
+ describe: "Content-Type to store the object with (e.g. text/plain)",
158
+ type: "string",
159
159
  },
160
160
  }), (args) => putObject(args))
161
161
  .command({
162
- command: 'delete <target>',
163
- aliases: ['rm'],
164
- describe: 'Delete an object, or every object under a prefix',
162
+ command: "delete <target>",
163
+ aliases: ["rm"],
164
+ describe: "Delete an object, or every object under a prefix",
165
165
  builder: (yargs) => yargs
166
- .usage('$0 bucket object delete <bucket>/<key> [options]')
167
- .positional('target', {
168
- describe: 'The object to delete: <bucket>/<key>, or <bucket>/<prefix>/ with --recursive',
169
- type: 'string',
166
+ .usage("$0 bucket object delete <bucket>/<key> [options]")
167
+ .positional("target", {
168
+ describe: "The object to delete: <bucket>/<key>, or <bucket>/<prefix>/ with --recursive",
169
+ type: "string",
170
170
  demandOption: true,
171
171
  })
172
172
  .options({
173
173
  ...scopeOptions,
174
174
  recursive: {
175
175
  describe: 'Delete every object under the given prefix. The prefix must end with "/"',
176
- type: 'boolean',
176
+ type: "boolean",
177
177
  default: false,
178
178
  },
179
179
  }),
180
180
  handler: (args) => deleteObject(args),
181
181
  })
182
- .demandCommand(1, '')
182
+ .demandCommand(1, "")
183
183
  .strictCommands())
184
- .demandCommand(1, '');
184
+ .demandCommand(1, "");
185
185
  export const handler = (args) => {
186
186
  return args;
187
187
  };
@@ -201,14 +201,14 @@ const listBuckets = async (props) => {
201
201
  projectId: props.projectId,
202
202
  branchId,
203
203
  });
204
- if (props.output === 'json' || props.output === 'yaml') {
204
+ if (props.output === "json" || props.output === "yaml") {
205
205
  writer(props).end(data.buckets, { fields: BUCKET_FIELDS });
206
206
  return;
207
207
  }
208
208
  writer(props).end(data.buckets, {
209
209
  fields: BUCKET_FIELDS,
210
- title: 'buckets',
211
- emptyMessage: 'No buckets found.',
210
+ title: "buckets",
211
+ emptyMessage: "No buckets found.",
212
212
  });
213
213
  };
214
214
  const deleteBucket = async (props) => {
@@ -236,7 +236,7 @@ const deleteBucket = async (props) => {
236
236
  // rejected client-side before any HTTP request is made.
237
237
  export const resolveListDelimiter = (props) => {
238
238
  if (props.recursive && props.delimiter !== undefined) {
239
- throw new Error('--recursive and --delimiter cannot be used together. Use --recursive for a flat listing, or --delimiter to collapse on a separator.');
239
+ throw new Error("--recursive and --delimiter cannot be used together. Use --recursive for a flat listing, or --delimiter to collapse on a separator.");
240
240
  }
241
241
  if (props.recursive) {
242
242
  return undefined;
@@ -244,7 +244,7 @@ export const resolveListDelimiter = (props) => {
244
244
  if (props.delimiter !== undefined) {
245
245
  return props.delimiter;
246
246
  }
247
- return '/';
247
+ return "/";
248
248
  };
249
249
  const listObjects = async (props) => {
250
250
  const delimiter = resolveListDelimiter(props);
@@ -254,25 +254,31 @@ const listObjects = async (props) => {
254
254
  projectId: props.projectId,
255
255
  branchId,
256
256
  bucketName: bucket,
257
- prefix: rest === '' ? undefined : rest,
257
+ prefix: rest === "" ? undefined : rest,
258
258
  delimiter,
259
259
  cursor: props.cursor,
260
260
  limit: props.limit,
261
261
  });
262
- if (props.output === 'json' || props.output === 'yaml') {
262
+ if (props.output === "json" || props.output === "yaml") {
263
263
  writer(props).end(data, {
264
- fields: ['folders', 'objects', 'prefix', 'next_cursor', 'is_truncated'],
264
+ fields: [
265
+ "folders",
266
+ "objects",
267
+ "prefix",
268
+ "next_cursor",
269
+ "is_truncated",
270
+ ],
265
271
  });
266
272
  return;
267
273
  }
268
274
  const w = writer(props);
269
275
  if (data.folders.length > 0) {
270
- w.write(data.folders.map((name) => ({ name })), { fields: ['name'], title: 'folders' });
276
+ w.write(data.folders.map((name) => ({ name })), { fields: ["name"], title: "folders" });
271
277
  }
272
278
  w.write(data.objects, {
273
279
  fields: OBJECT_FIELDS,
274
- title: 'objects',
275
- emptyMessage: 'No objects found.',
280
+ title: "objects",
281
+ emptyMessage: "No objects found.",
276
282
  });
277
283
  w.end();
278
284
  if (data.is_truncated && data.next_cursor) {
@@ -304,7 +310,7 @@ const filenameFromContentDisposition = (contentDisposition, key) => {
304
310
  // the body is absent, not an object, or carries no usable message.
305
311
  const serverErrorMessage = (body) => {
306
312
  const message = body?.message;
307
- return typeof message === 'string' && message.trim() !== ''
313
+ return typeof message === "string" && message.trim() !== ""
308
314
  ? message
309
315
  : undefined;
310
316
  };
@@ -312,7 +318,7 @@ const serverErrorMessage = (body) => {
312
318
  // and parse its `message`. Returns undefined on any read/parse failure so the
313
319
  // caller falls back to its default message.
314
320
  const streamErrorMessage = async (stream) => {
315
- if (typeof stream?.[Symbol.asyncIterator] !== 'function') {
321
+ if (typeof stream?.[Symbol.asyncIterator] !== "function") {
316
322
  return undefined;
317
323
  }
318
324
  try {
@@ -347,15 +353,15 @@ const fileToWebStream = (path) => {
347
353
  const source = createReadStream(path);
348
354
  return new ReadableStream({
349
355
  start(controller) {
350
- source.on('data', (chunk) => {
356
+ source.on("data", (chunk) => {
351
357
  controller.enqueue(new Uint8Array(chunk));
352
358
  if ((controller.desiredSize ?? 0) <= 0)
353
359
  source.pause();
354
360
  });
355
- source.on('end', () => {
361
+ source.on("end", () => {
356
362
  controller.close();
357
363
  });
358
- source.on('error', (err) => {
364
+ source.on("error", (err) => {
359
365
  controller.error(err instanceof Error ? err : new Error(String(err)));
360
366
  });
361
367
  },
@@ -370,8 +376,8 @@ const fileToWebStream = (path) => {
370
376
  const getObject = async (props) => {
371
377
  const branchId = await branchIdFromProps(props);
372
378
  const { bucket, rest: key } = splitBucketTarget(props.target);
373
- if (key === '') {
374
- throw new Error('Object target must be in the form <bucket>/<key>.');
379
+ if (key === "") {
380
+ throw new Error("Object target must be in the form <bucket>/<key>.");
375
381
  }
376
382
  let response;
377
383
  try {
@@ -392,7 +398,7 @@ const getObject = async (props) => {
392
398
  }
393
399
  throw err;
394
400
  }
395
- const contentDisposition = response.headers['content-disposition'];
401
+ const contentDisposition = response.headers["content-disposition"];
396
402
  const destination = props.file ?? filenameFromContentDisposition(contentDisposition, key);
397
403
  try {
398
404
  await pipeline(response.data, createWriteStream(destination));
@@ -407,8 +413,8 @@ const getObject = async (props) => {
407
413
  const putObject = async (props) => {
408
414
  const branchId = await branchIdFromProps(props);
409
415
  const { bucket, rest: key } = splitBucketTarget(props.target);
410
- if (bucket === '' || key === '') {
411
- throw new Error('Object target must be in the form <bucket>/<key>.');
416
+ if (bucket === "" || key === "") {
417
+ throw new Error("Object target must be in the form <bucket>/<key>.");
412
418
  }
413
419
  // Stat the file first so we fail fast on a missing/unreadable file and can
414
420
  // enforce the single-PUT size cap BEFORE any network round-trip. We also
@@ -423,7 +429,7 @@ const putObject = async (props) => {
423
429
  fileSize = fileStat.size;
424
430
  }
425
431
  catch (err) {
426
- if (err?.code === 'ENOENT') {
432
+ if (err?.code === "ENOENT") {
427
433
  throw new Error(`File "${props.file}" does not exist.`);
428
434
  }
429
435
  throw err;
@@ -455,7 +461,7 @@ const putObject = async (props) => {
455
461
  // usable message, fall back to a clean status-bearing error.
456
462
  const serverMessage = serverErrorMessage(err.data);
457
463
  throw new Error(serverMessage ??
458
- `Failed to presign upload for "${key}" in bucket "${bucket}" on branch ${branchId}${status !== undefined ? ` (HTTP ${status})` : ''}: ${err.message}`);
464
+ `Failed to presign upload for "${key}" in bucket "${bucket}" on branch ${branchId}${status !== undefined ? ` (HTTP ${status})` : ""}: ${err.message}`);
459
465
  }
460
466
  throw err;
461
467
  }
@@ -470,14 +476,14 @@ const putObject = async (props) => {
470
476
  // endpoint were to answer with a redirect. `duplex: 'half'` is required by
471
477
  // fetch when streaming a request body.
472
478
  const upload = {
473
- method: 'PUT',
479
+ method: "PUT",
474
480
  headers: {
475
481
  ...presign.headers,
476
- 'Content-Length': String(fileSize),
482
+ "Content-Length": String(fileSize),
477
483
  },
478
484
  body: fileToWebStream(props.file),
479
- redirect: 'error',
480
- duplex: 'half',
485
+ redirect: "error",
486
+ duplex: "half",
481
487
  };
482
488
  let uploadResponse;
483
489
  try {
@@ -501,10 +507,10 @@ const deleteObject = async (props) => {
501
507
  const branchId = await branchIdFromProps(props);
502
508
  const { bucket, rest } = splitBucketTarget(props.target);
503
509
  if (props.recursive) {
504
- if (rest === '') {
510
+ if (rest === "") {
505
511
  throw new Error('Recursive delete requires a non-empty prefix ending in "/".');
506
512
  }
507
- if (!rest.endsWith('/')) {
513
+ if (!rest.endsWith("/")) {
508
514
  throw new Error(`Recursive delete requires a prefix ending in "/" (got "${rest}").`);
509
515
  }
510
516
  const { data } = await retryOnLock(() => deleteProjectBranchBucketObjectsByPrefix(props.apiClient, {
@@ -516,8 +522,8 @@ const deleteObject = async (props) => {
516
522
  log.info(`Deleted ${data.deleted} object(s) under prefix "${rest}" from bucket "${bucket}" on branch ${branchId}`);
517
523
  return;
518
524
  }
519
- if (rest === '') {
520
- throw new Error('Object target must be in the form <bucket>/<key>.');
525
+ if (rest === "") {
526
+ throw new Error("Object target must be in the form <bucket>/<key>.");
521
527
  }
522
528
  try {
523
529
  await retryOnLock(() => deleteProjectBranchBucketObject(props.apiClient, {