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.
- package/README.md +2 -2
- package/dist/analytics.js +35 -33
- package/dist/api.js +34 -34
- package/dist/auth.js +50 -44
- package/dist/cli.js +2 -2
- package/dist/commands/auth.js +58 -52
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +154 -147
- package/dist/commands/bucket.js +124 -118
- package/dist/commands/checkout.js +49 -49
- package/dist/commands/config.js +212 -88
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +96 -96
- package/dist/commands/databases.js +23 -23
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/dev.js +114 -114
- package/dist/commands/env.js +43 -43
- package/dist/commands/functions.js +97 -98
- package/dist/commands/index.js +26 -26
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +223 -166
- package/dist/commands/neon_auth.js +381 -363
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +101 -99
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +21 -21
- package/dist/commands/schema_diff.js +23 -23
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +17 -17
- package/dist/commands/user.js +5 -5
- package/dist/commands/vpc_endpoints.js +50 -50
- package/dist/config.js +7 -7
- package/dist/config_format.js +5 -5
- package/dist/context.js +23 -16
- package/dist/current_branch_fast_path.js +6 -6
- package/dist/dev/env.js +33 -33
- package/dist/dev/functions.js +4 -4
- package/dist/dev/inputs.js +6 -6
- package/dist/dev/runtime.js +25 -25
- package/dist/env.js +14 -14
- package/dist/env_file.js +13 -13
- package/dist/errors.js +19 -19
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +94 -92
- package/dist/log.js +2 -2
- package/dist/pkg.js +5 -5
- package/dist/psql/cli.js +4 -2
- package/dist/psql/command/cmd_cond.js +61 -61
- package/dist/psql/command/cmd_connect.js +159 -154
- package/dist/psql/command/cmd_copy.js +107 -97
- package/dist/psql/command/cmd_describe.js +368 -363
- package/dist/psql/command/cmd_format.js +276 -263
- package/dist/psql/command/cmd_io.js +269 -263
- package/dist/psql/command/cmd_lo.js +74 -66
- package/dist/psql/command/cmd_meta.js +148 -148
- package/dist/psql/command/cmd_misc.js +17 -17
- package/dist/psql/command/cmd_pipeline.js +142 -135
- package/dist/psql/command/cmd_restrict.js +25 -25
- package/dist/psql/command/cmd_show.js +183 -168
- package/dist/psql/command/dispatch.js +26 -26
- package/dist/psql/command/shared.js +14 -14
- package/dist/psql/complete/filenames.js +16 -16
- package/dist/psql/complete/index.js +4 -4
- package/dist/psql/complete/matcher.js +33 -32
- package/dist/psql/complete/psqlVars.js +173 -173
- package/dist/psql/complete/queries.js +5 -3
- package/dist/psql/complete/rules.js +900 -863
- package/dist/psql/core/common.js +136 -133
- package/dist/psql/core/help.js +343 -343
- package/dist/psql/core/mainloop.js +160 -153
- package/dist/psql/core/prompt.js +126 -123
- package/dist/psql/core/settings.js +111 -111
- package/dist/psql/core/sqlHelp.js +150 -150
- package/dist/psql/core/startup.js +211 -205
- package/dist/psql/core/syncVars.js +14 -14
- package/dist/psql/core/variables.js +24 -24
- package/dist/psql/describe/formatters.js +302 -289
- package/dist/psql/describe/processNamePattern.js +28 -28
- package/dist/psql/describe/queries.js +656 -651
- package/dist/psql/index.js +436 -411
- package/dist/psql/io/history.js +36 -36
- package/dist/psql/io/input.js +15 -15
- package/dist/psql/io/lineEditor/buffer.js +27 -25
- package/dist/psql/io/lineEditor/complete.js +15 -15
- package/dist/psql/io/lineEditor/filename.js +22 -22
- package/dist/psql/io/lineEditor/index.js +65 -62
- package/dist/psql/io/lineEditor/keymap.js +325 -318
- package/dist/psql/io/lineEditor/vt100.js +60 -60
- package/dist/psql/io/pgpass.js +18 -18
- package/dist/psql/io/pgservice.js +14 -14
- package/dist/psql/io/psqlrc.js +46 -46
- package/dist/psql/print/aligned.js +175 -166
- package/dist/psql/print/asciidoc.js +51 -51
- package/dist/psql/print/crosstab.js +34 -31
- package/dist/psql/print/csv.js +25 -22
- package/dist/psql/print/html.js +54 -54
- package/dist/psql/print/json.js +12 -12
- package/dist/psql/print/latex.js +118 -118
- package/dist/psql/print/pager.js +28 -26
- package/dist/psql/print/troff.js +48 -48
- package/dist/psql/print/unaligned.js +15 -14
- package/dist/psql/print/units.js +17 -17
- package/dist/psql/scanner/slash.js +48 -46
- package/dist/psql/scanner/sql.js +88 -84
- package/dist/psql/scanner/stringutils.js +21 -17
- package/dist/psql/types/index.js +7 -7
- package/dist/psql/types/scanner.js +8 -8
- package/dist/psql/wire/connection.js +341 -327
- package/dist/psql/wire/copy.js +7 -7
- package/dist/psql/wire/pipeline.js +26 -24
- package/dist/psql/wire/protocol.js +102 -102
- package/dist/psql/wire/sasl.js +62 -62
- package/dist/psql/wire/tls.js +79 -73
- package/dist/storage_api.js +15 -15
- package/dist/test_utils/fixtures.js +34 -31
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +13 -13
- package/dist/utils/branch_notice.js +5 -5
- package/dist/utils/branch_picker.js +26 -26
- package/dist/utils/compute_units.js +4 -4
- package/dist/utils/enrichers.js +20 -15
- package/dist/utils/esbuild.js +28 -28
- package/dist/utils/formats.js +1 -1
- package/dist/utils/middlewares.js +3 -3
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +12 -12
- package/dist/utils/psql.js +30 -30
- package/dist/utils/string.js +2 -2
- package/dist/utils/ui.js +9 -9
- package/dist/utils/zip.js +1 -1
- package/dist/writer.js +17 -17
- package/package.json +6 -7
package/dist/commands/bucket.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { createReadStream, createWriteStream } from
|
|
2
|
-
import { stat, unlink } from
|
|
3
|
-
import { basename } from
|
|
4
|
-
import { pipeline } from
|
|
5
|
-
import { isNeonApiError, retryOnLock } from
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
const OBJECT_FIELDS = [
|
|
11
|
-
const BUCKET_FIELDS = [
|
|
12
|
-
const ACCESS_LEVELS = [
|
|
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
|
-
|
|
22
|
-
describe:
|
|
23
|
-
type:
|
|
21
|
+
"project-id": {
|
|
22
|
+
describe: "Project ID",
|
|
23
|
+
type: "string",
|
|
24
24
|
},
|
|
25
25
|
branch: {
|
|
26
|
-
describe:
|
|
27
|
-
type:
|
|
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 =
|
|
45
|
-
export const describe =
|
|
46
|
-
export const aliases = [
|
|
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(
|
|
48
|
+
.usage("$0 bucket <sub-command> [options]")
|
|
49
49
|
.options({
|
|
50
|
-
|
|
51
|
-
describe:
|
|
52
|
-
type:
|
|
50
|
+
"project-id": {
|
|
51
|
+
describe: "Project ID",
|
|
52
|
+
type: "string",
|
|
53
53
|
},
|
|
54
54
|
})
|
|
55
55
|
.middleware(fillSingleProject)
|
|
56
|
-
.command(
|
|
57
|
-
.usage(
|
|
58
|
-
.positional(
|
|
59
|
-
describe:
|
|
60
|
-
type:
|
|
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
|
-
|
|
66
|
-
describe:
|
|
67
|
-
type:
|
|
65
|
+
"access-level": {
|
|
66
|
+
describe: "The visibility of the bucket",
|
|
67
|
+
type: "string",
|
|
68
68
|
choices: ACCESS_LEVELS,
|
|
69
|
-
default:
|
|
69
|
+
default: "private",
|
|
70
70
|
},
|
|
71
71
|
}), (args) => createBucket(args))
|
|
72
72
|
.command({
|
|
73
|
-
command:
|
|
74
|
-
aliases: [
|
|
75
|
-
describe:
|
|
76
|
-
builder: (yargs) => yargs.usage(
|
|
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:
|
|
81
|
-
aliases: [
|
|
82
|
-
describe:
|
|
80
|
+
command: "delete <name>",
|
|
81
|
+
aliases: ["rm"],
|
|
82
|
+
describe: "Delete a bucket from a branch",
|
|
83
83
|
builder: (yargs) => yargs
|
|
84
|
-
.usage(
|
|
85
|
-
.positional(
|
|
86
|
-
describe:
|
|
87
|
-
type:
|
|
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(
|
|
94
|
-
.usage(
|
|
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:
|
|
97
|
-
aliases: [
|
|
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(
|
|
101
|
-
.positional(
|
|
102
|
-
describe:
|
|
103
|
-
type:
|
|
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:
|
|
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:
|
|
115
|
+
type: "string",
|
|
116
116
|
},
|
|
117
117
|
cursor: {
|
|
118
|
-
describe:
|
|
119
|
-
type:
|
|
118
|
+
describe: "Pagination cursor returned as next_cursor by a previous call",
|
|
119
|
+
type: "string",
|
|
120
120
|
},
|
|
121
121
|
limit: {
|
|
122
|
-
describe:
|
|
123
|
-
type:
|
|
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(
|
|
129
|
-
.usage(
|
|
130
|
-
.positional(
|
|
131
|
-
describe:
|
|
132
|
-
type:
|
|
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:
|
|
139
|
-
type:
|
|
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(
|
|
143
|
-
.usage(
|
|
144
|
-
.positional(
|
|
145
|
-
describe:
|
|
146
|
-
type:
|
|
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:
|
|
153
|
-
type:
|
|
152
|
+
describe: "Path to the local file to upload",
|
|
153
|
+
type: "string",
|
|
154
154
|
demandOption: true,
|
|
155
155
|
},
|
|
156
|
-
|
|
157
|
-
describe:
|
|
158
|
-
type:
|
|
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:
|
|
163
|
-
aliases: [
|
|
164
|
-
describe:
|
|
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(
|
|
167
|
-
.positional(
|
|
168
|
-
describe:
|
|
169
|
-
type:
|
|
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:
|
|
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 ===
|
|
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:
|
|
211
|
-
emptyMessage:
|
|
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(
|
|
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 ===
|
|
257
|
+
prefix: rest === "" ? undefined : rest,
|
|
258
258
|
delimiter,
|
|
259
259
|
cursor: props.cursor,
|
|
260
260
|
limit: props.limit,
|
|
261
261
|
});
|
|
262
|
-
if (props.output ===
|
|
262
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
263
263
|
writer(props).end(data, {
|
|
264
|
-
fields: [
|
|
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: [
|
|
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:
|
|
275
|
-
emptyMessage:
|
|
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 ===
|
|
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] !==
|
|
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(
|
|
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(
|
|
361
|
+
source.on("end", () => {
|
|
356
362
|
controller.close();
|
|
357
363
|
});
|
|
358
|
-
source.on(
|
|
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(
|
|
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[
|
|
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 ===
|
|
411
|
-
throw new Error(
|
|
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 ===
|
|
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})` :
|
|
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:
|
|
479
|
+
method: "PUT",
|
|
474
480
|
headers: {
|
|
475
481
|
...presign.headers,
|
|
476
|
-
|
|
482
|
+
"Content-Length": String(fileSize),
|
|
477
483
|
},
|
|
478
484
|
body: fileToWebStream(props.file),
|
|
479
|
-
redirect:
|
|
480
|
-
duplex:
|
|
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(
|
|
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, {
|