neon 3.0.0 → 3.1.1
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 +70 -5
- package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
- package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
- package/dist/_chunks/credentials-MYdHdKah.js +188 -0
- package/dist/_chunks/env-NbA61JR3.js +585 -0
- package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
- package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
- package/dist/_chunks/profiles-Ir29rqns.js +217 -0
- package/dist/_chunks/psql-DWH-kc69.js +2169 -0
- package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +255 -305
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +615 -846
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +2 -446
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +2 -51
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +728 -984
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +2 -2193
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +2 -1148
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +260 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +2 -1253
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1303
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +2 -846
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +2 -2030
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +8 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +39 -45
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +11 -6
- package/dist/_shared/auth_selection.js +0 -86
- package/dist/_shared/credentials.js +0 -209
- package/dist/_shared/env-core/env.js +0 -558
- package/dist/_shared/env-core/reuse-secrets.js +0 -223
- package/dist/_shared/paths.js +0 -148
- package/dist/_shared/profiles.js +0 -276
- package/dist/_shared/secure_file.js +0 -43
package/dist/commands/bucket.js
CHANGED
|
@@ -1,549 +1,409 @@
|
|
|
1
|
+
import { t as __exportAll } from "../_chunks/rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import { log } from "../log.js";
|
|
3
|
+
import { isNeonApiError, retryOnLock } from "../api.js";
|
|
4
|
+
import { writer } from "../writer.js";
|
|
5
|
+
import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
|
|
6
|
+
import { createProjectBranchBucket, deleteProjectBranchBucket, deleteProjectBranchBucketObject, deleteProjectBranchBucketObjectsByPrefix, getProjectBranchBucketObject, listProjectBranchBucketObjects, listProjectBranchBuckets, presignUpload } from "../storage_api.js";
|
|
1
7
|
import { createReadStream, createWriteStream } from "node:fs";
|
|
2
|
-
import { stat, unlink } from "node:fs/promises";
|
|
3
8
|
import { basename } from "node:path";
|
|
9
|
+
import { stat, unlink } from "node:fs/promises";
|
|
4
10
|
import { pipeline } from "node:stream/promises";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
//#region src/commands/bucket.ts
|
|
12
|
+
var bucket_exports = /* @__PURE__ */ __exportAll({
|
|
13
|
+
aliases: () => aliases,
|
|
14
|
+
builder: () => builder,
|
|
15
|
+
command: () => command,
|
|
16
|
+
describe: () => describe,
|
|
17
|
+
handler: () => handler,
|
|
18
|
+
resolveListDelimiter: () => resolveListDelimiter,
|
|
19
|
+
splitBucketTarget: () => splitBucketTarget
|
|
20
|
+
});
|
|
21
|
+
const OBJECT_FIELDS = [
|
|
22
|
+
"key",
|
|
23
|
+
"size",
|
|
24
|
+
"last_modified",
|
|
25
|
+
"etag"
|
|
26
|
+
];
|
|
11
27
|
const BUCKET_FIELDS = ["name", "access_level"];
|
|
12
28
|
const ACCESS_LEVELS = ["private", "public_read"];
|
|
13
|
-
|
|
14
|
-
// which is out of scope for v1; we reject them client-side before any HTTP so
|
|
15
|
-
// the user gets an immediate, clear error rather than a server-side rejection
|
|
16
|
-
// part-way through a large transfer.
|
|
17
|
-
const MAX_OBJECT_BYTES = 100 * 1024 * 1024; // 100 MB
|
|
18
|
-
// Ambient scope shared by every bucket sub-command. The bucket name (and the
|
|
19
|
-
// object key/prefix) is always a positional, never a flag.
|
|
29
|
+
const MAX_OBJECT_BYTES = 104857600;
|
|
20
30
|
const scopeOptions = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
"project-id": {
|
|
32
|
+
describe: "Project ID",
|
|
33
|
+
type: "string"
|
|
34
|
+
},
|
|
35
|
+
branch: {
|
|
36
|
+
describe: "Branch ID or name",
|
|
37
|
+
type: "string"
|
|
38
|
+
}
|
|
29
39
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
bucket: target.slice(0, slash),
|
|
41
|
-
rest: target.slice(slash + 1),
|
|
42
|
-
};
|
|
40
|
+
const splitBucketTarget = (target) => {
|
|
41
|
+
const slash = target.indexOf("/");
|
|
42
|
+
if (slash === -1) return {
|
|
43
|
+
bucket: target,
|
|
44
|
+
rest: ""
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
bucket: target.slice(0, slash),
|
|
48
|
+
rest: target.slice(slash + 1)
|
|
49
|
+
};
|
|
43
50
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
describe: "Path to the local file to upload",
|
|
153
|
-
type: "string",
|
|
154
|
-
demandOption: true,
|
|
155
|
-
},
|
|
156
|
-
"content-type": {
|
|
157
|
-
describe: "Content-Type to store the object with (e.g. text/plain)",
|
|
158
|
-
type: "string",
|
|
159
|
-
},
|
|
160
|
-
}), (args) => putObject(args))
|
|
161
|
-
.command({
|
|
162
|
-
command: "delete <target>",
|
|
163
|
-
aliases: ["rm"],
|
|
164
|
-
describe: "Delete an object, or every object under a prefix",
|
|
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",
|
|
170
|
-
demandOption: true,
|
|
171
|
-
})
|
|
172
|
-
.options({
|
|
173
|
-
...scopeOptions,
|
|
174
|
-
recursive: {
|
|
175
|
-
describe: 'Delete every object under the given prefix. The prefix must end with "/"',
|
|
176
|
-
type: "boolean",
|
|
177
|
-
default: false,
|
|
178
|
-
},
|
|
179
|
-
}),
|
|
180
|
-
handler: (args) => deleteObject(args),
|
|
181
|
-
})
|
|
182
|
-
.demandCommand(1, "")
|
|
183
|
-
.strictCommands())
|
|
184
|
-
.demandCommand(1, "");
|
|
185
|
-
export const handler = (args) => {
|
|
186
|
-
return args;
|
|
51
|
+
const command = "buckets";
|
|
52
|
+
const describe = "Manage branch object-storage buckets and their objects";
|
|
53
|
+
const aliases = ["bucket"];
|
|
54
|
+
const builder = (argv) => argv.usage("$0 bucket <sub-command> [options]").options({ "project-id": {
|
|
55
|
+
describe: "Project ID",
|
|
56
|
+
type: "string"
|
|
57
|
+
} }).middleware(fillSingleProject).command("create <name>", "Create a bucket on a branch", (yargs) => yargs.usage("$0 bucket create <name> [options]").positional("name", {
|
|
58
|
+
describe: "The bucket name to create",
|
|
59
|
+
type: "string",
|
|
60
|
+
demandOption: true
|
|
61
|
+
}).options({
|
|
62
|
+
...scopeOptions,
|
|
63
|
+
"access-level": {
|
|
64
|
+
describe: "The visibility of the bucket",
|
|
65
|
+
type: "string",
|
|
66
|
+
choices: ACCESS_LEVELS,
|
|
67
|
+
default: "private"
|
|
68
|
+
}
|
|
69
|
+
}), (args) => createBucket(args)).command({
|
|
70
|
+
command: "list",
|
|
71
|
+
aliases: ["ls"],
|
|
72
|
+
describe: "List the buckets on a branch",
|
|
73
|
+
builder: (yargs) => yargs.usage("$0 bucket list [options]").options(scopeOptions),
|
|
74
|
+
handler: (args) => listBuckets(args)
|
|
75
|
+
}).command({
|
|
76
|
+
command: "delete <name>",
|
|
77
|
+
aliases: ["rm"],
|
|
78
|
+
describe: "Delete a bucket from a branch",
|
|
79
|
+
builder: (yargs) => yargs.usage("$0 bucket delete <name> [options]").positional("name", {
|
|
80
|
+
describe: "The bucket name to delete",
|
|
81
|
+
type: "string",
|
|
82
|
+
demandOption: true
|
|
83
|
+
}).options(scopeOptions),
|
|
84
|
+
handler: (args) => deleteBucket(args)
|
|
85
|
+
}).command("object <sub-command>", "List, download, upload or delete objects in a bucket", (yargs) => yargs.usage("$0 bucket object <sub-command> [options]").command({
|
|
86
|
+
command: "list <target>",
|
|
87
|
+
aliases: ["ls"],
|
|
88
|
+
describe: "List objects in a bucket. By default folders are collapsed (like \"aws s3 ls\"); pass --recursive for a flat listing of every key",
|
|
89
|
+
builder: (yargs) => yargs.usage("$0 bucket object list <bucket>[/<prefix>] [options]").positional("target", {
|
|
90
|
+
describe: "The bucket to list, optionally with a key prefix: <bucket>[/<prefix>]",
|
|
91
|
+
type: "string",
|
|
92
|
+
demandOption: true
|
|
93
|
+
}).options({
|
|
94
|
+
...scopeOptions,
|
|
95
|
+
recursive: {
|
|
96
|
+
describe: "List every key flat, descending into nested folders (no delimiter). Mutually exclusive with --delimiter. Mirrors \"aws s3 ls --recursive\"",
|
|
97
|
+
type: "boolean",
|
|
98
|
+
default: false
|
|
99
|
+
},
|
|
100
|
+
delimiter: {
|
|
101
|
+
describe: "Collapse keys sharing this prefix separator into folders. Defaults to \"/\" (folder view); ignored when --recursive is set",
|
|
102
|
+
type: "string"
|
|
103
|
+
},
|
|
104
|
+
cursor: {
|
|
105
|
+
describe: "Pagination cursor returned as next_cursor by a previous call",
|
|
106
|
+
type: "string"
|
|
107
|
+
},
|
|
108
|
+
limit: {
|
|
109
|
+
describe: "Maximum number of items (objects + folders) to return",
|
|
110
|
+
type: "number"
|
|
111
|
+
}
|
|
112
|
+
}),
|
|
113
|
+
handler: (args) => listObjects(args)
|
|
114
|
+
}).command("get <target>", "Download an object from a bucket to a local file", (yargs) => yargs.usage("$0 bucket object get <bucket>/<key> [options]").positional("target", {
|
|
115
|
+
describe: "The object to download: <bucket>/<key>",
|
|
116
|
+
type: "string",
|
|
117
|
+
demandOption: true
|
|
118
|
+
}).options({
|
|
119
|
+
...scopeOptions,
|
|
120
|
+
file: {
|
|
121
|
+
describe: "Path to write the downloaded object to (defaults to the object filename in the current directory)",
|
|
122
|
+
type: "string"
|
|
123
|
+
}
|
|
124
|
+
}), (args) => getObject(args)).command("put <target>", "Upload a local file to a bucket as an object", (yargs) => yargs.usage("$0 bucket object put <bucket>/<key> [options]").positional("target", {
|
|
125
|
+
describe: "The object to upload to: <bucket>/<key>",
|
|
126
|
+
type: "string",
|
|
127
|
+
demandOption: true
|
|
128
|
+
}).options({
|
|
129
|
+
...scopeOptions,
|
|
130
|
+
file: {
|
|
131
|
+
describe: "Path to the local file to upload",
|
|
132
|
+
type: "string",
|
|
133
|
+
demandOption: true
|
|
134
|
+
},
|
|
135
|
+
"content-type": {
|
|
136
|
+
describe: "Content-Type to store the object with (e.g. text/plain)",
|
|
137
|
+
type: "string"
|
|
138
|
+
}
|
|
139
|
+
}), (args) => putObject(args)).command({
|
|
140
|
+
command: "delete <target>",
|
|
141
|
+
aliases: ["rm"],
|
|
142
|
+
describe: "Delete an object, or every object under a prefix",
|
|
143
|
+
builder: (yargs) => yargs.usage("$0 bucket object delete <bucket>/<key> [options]").positional("target", {
|
|
144
|
+
describe: "The object to delete: <bucket>/<key>, or <bucket>/<prefix>/ with --recursive",
|
|
145
|
+
type: "string",
|
|
146
|
+
demandOption: true
|
|
147
|
+
}).options({
|
|
148
|
+
...scopeOptions,
|
|
149
|
+
recursive: {
|
|
150
|
+
describe: "Delete every object under the given prefix. The prefix must end with \"/\"",
|
|
151
|
+
type: "boolean",
|
|
152
|
+
default: false
|
|
153
|
+
}
|
|
154
|
+
}),
|
|
155
|
+
handler: (args) => deleteObject(args)
|
|
156
|
+
}).demandCommand(1, "").strictCommands()).demandCommand(1, "");
|
|
157
|
+
const handler = (args) => {
|
|
158
|
+
return args;
|
|
187
159
|
};
|
|
188
160
|
const createBucket = async (props) => {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
161
|
+
const branchId = await branchIdFromProps(props);
|
|
162
|
+
const { data } = await retryOnLock(() => createProjectBranchBucket(props.apiClient, {
|
|
163
|
+
projectId: props.projectId,
|
|
164
|
+
branchId,
|
|
165
|
+
name: props.name,
|
|
166
|
+
accessLevel: props.accessLevel
|
|
167
|
+
}));
|
|
168
|
+
log.info(`Bucket "${data.bucket.name}" (${data.bucket.access_level}) created on branch ${branchId}`);
|
|
197
169
|
};
|
|
198
170
|
const listBuckets = async (props) => {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
171
|
+
const branchId = await branchIdFromProps(props);
|
|
172
|
+
const { data } = await listProjectBranchBuckets(props.apiClient, {
|
|
173
|
+
projectId: props.projectId,
|
|
174
|
+
branchId
|
|
175
|
+
});
|
|
176
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
177
|
+
writer(props).end(data.buckets, { fields: BUCKET_FIELDS });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
writer(props).end(data.buckets, {
|
|
181
|
+
fields: BUCKET_FIELDS,
|
|
182
|
+
title: "buckets",
|
|
183
|
+
emptyMessage: "No buckets found."
|
|
184
|
+
});
|
|
213
185
|
};
|
|
214
186
|
const deleteBucket = async (props) => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
throw err;
|
|
228
|
-
}
|
|
229
|
-
log.info(`Bucket "${props.name}" deleted from branch ${branchId}`);
|
|
187
|
+
const branchId = await branchIdFromProps(props);
|
|
188
|
+
try {
|
|
189
|
+
await retryOnLock(() => deleteProjectBranchBucket(props.apiClient, {
|
|
190
|
+
projectId: props.projectId,
|
|
191
|
+
branchId,
|
|
192
|
+
bucketName: props.name
|
|
193
|
+
}));
|
|
194
|
+
} catch (err) {
|
|
195
|
+
if (isNeonApiError(err) && err.status === 404) throw new Error(`Bucket "${props.name}" not found on branch ${branchId}.`);
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
log.info(`Bucket "${props.name}" deleted from branch ${branchId}`);
|
|
230
199
|
};
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
// rejected client-side before any HTTP request is made.
|
|
237
|
-
export const resolveListDelimiter = (props) => {
|
|
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.");
|
|
240
|
-
}
|
|
241
|
-
if (props.recursive) {
|
|
242
|
-
return undefined;
|
|
243
|
-
}
|
|
244
|
-
if (props.delimiter !== undefined) {
|
|
245
|
-
return props.delimiter;
|
|
246
|
-
}
|
|
247
|
-
return "/";
|
|
200
|
+
const resolveListDelimiter = (props) => {
|
|
201
|
+
if (props.recursive && props.delimiter !== void 0) throw new Error("--recursive and --delimiter cannot be used together. Use --recursive for a flat listing, or --delimiter to collapse on a separator.");
|
|
202
|
+
if (props.recursive) return;
|
|
203
|
+
if (props.delimiter !== void 0) return props.delimiter;
|
|
204
|
+
return "/";
|
|
248
205
|
};
|
|
249
206
|
const listObjects = async (props) => {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (data.is_truncated && data.next_cursor) {
|
|
285
|
-
log.info(`More results available. Re-run with --cursor ${data.next_cursor} to fetch the next page.`);
|
|
286
|
-
}
|
|
207
|
+
const delimiter = resolveListDelimiter(props);
|
|
208
|
+
const branchId = await branchIdFromProps(props);
|
|
209
|
+
const { bucket, rest } = splitBucketTarget(props.target);
|
|
210
|
+
const { data } = await listProjectBranchBucketObjects(props.apiClient, {
|
|
211
|
+
projectId: props.projectId,
|
|
212
|
+
branchId,
|
|
213
|
+
bucketName: bucket,
|
|
214
|
+
prefix: rest === "" ? void 0 : rest,
|
|
215
|
+
delimiter,
|
|
216
|
+
cursor: props.cursor,
|
|
217
|
+
limit: props.limit
|
|
218
|
+
});
|
|
219
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
220
|
+
writer(props).end(data, { fields: [
|
|
221
|
+
"folders",
|
|
222
|
+
"objects",
|
|
223
|
+
"prefix",
|
|
224
|
+
"next_cursor",
|
|
225
|
+
"is_truncated"
|
|
226
|
+
] });
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const w = writer(props);
|
|
230
|
+
if (data.folders.length > 0) w.write(data.folders.map((name) => ({ name })), {
|
|
231
|
+
fields: ["name"],
|
|
232
|
+
title: "folders"
|
|
233
|
+
});
|
|
234
|
+
w.write(data.objects, {
|
|
235
|
+
fields: OBJECT_FIELDS,
|
|
236
|
+
title: "objects",
|
|
237
|
+
emptyMessage: "No objects found."
|
|
238
|
+
});
|
|
239
|
+
w.end();
|
|
240
|
+
if (data.is_truncated && data.next_cursor) log.info(`More results available. Re-run with --cursor ${data.next_cursor} to fetch the next page.`);
|
|
287
241
|
};
|
|
288
|
-
// Pull a filename out of a `Content-Disposition` header, falling back to the
|
|
289
|
-
// last segment of the object key. Handles the plain and RFC 5987 (`filename*=`)
|
|
290
|
-
// forms the download endpoint may emit.
|
|
291
242
|
const filenameFromContentDisposition = (contentDisposition, key) => {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
const plain = /filename="?([^";]+)"?/i.exec(contentDisposition);
|
|
303
|
-
if (plain?.[1]) {
|
|
304
|
-
return basename(plain[1].trim());
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
return basename(key) || key;
|
|
243
|
+
if (contentDisposition) {
|
|
244
|
+
const extended = /filename\*=(?:UTF-8'')?([^;]+)/i.exec(contentDisposition);
|
|
245
|
+
if (extended?.[1]) try {
|
|
246
|
+
return basename(decodeURIComponent(extended[1].trim()));
|
|
247
|
+
} catch {}
|
|
248
|
+
const plain = /filename="?([^";]+)"?/i.exec(contentDisposition);
|
|
249
|
+
if (plain?.[1]) return basename(plain[1].trim());
|
|
250
|
+
}
|
|
251
|
+
return basename(key) || key;
|
|
308
252
|
};
|
|
309
|
-
// Pull the `message` field out of a server error body, returning undefined when
|
|
310
|
-
// the body is absent, not an object, or carries no usable message.
|
|
311
253
|
const serverErrorMessage = (body) => {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
? message
|
|
315
|
-
: undefined;
|
|
254
|
+
const message = body?.message;
|
|
255
|
+
return typeof message === "string" && message.trim() !== "" ? message : void 0;
|
|
316
256
|
};
|
|
317
|
-
// Drain a streamed error body (the form an `octet-stream` download 404 takes)
|
|
318
|
-
// and parse its `message`. Returns undefined on any read/parse failure so the
|
|
319
|
-
// caller falls back to its default message.
|
|
320
257
|
const streamErrorMessage = async (stream) => {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
return serverErrorMessage(JSON.parse(Buffer.concat(chunks).toString()));
|
|
330
|
-
}
|
|
331
|
-
catch {
|
|
332
|
-
return undefined;
|
|
333
|
-
}
|
|
258
|
+
if (typeof stream?.[Symbol.asyncIterator] !== "function") return;
|
|
259
|
+
try {
|
|
260
|
+
const chunks = [];
|
|
261
|
+
for await (const chunk of stream) chunks.push(Buffer.from(chunk));
|
|
262
|
+
return serverErrorMessage(JSON.parse(Buffer.concat(chunks).toString()));
|
|
263
|
+
} catch {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
334
266
|
};
|
|
335
267
|
const objectNotFoundFallback = (key, bucket, branchId) => `Object "${key}" not found in bucket "${bucket}" on branch ${branchId}.`;
|
|
336
|
-
// Prefer the server's error message when present so a missing bucket is not
|
|
337
|
-
// misreported as a missing object; otherwise fall back to a clean default. Used
|
|
338
|
-
// for the JSON (non-streamed) endpoints where the body is already parsed.
|
|
339
268
|
const objectNotFoundMessage = (err, key, bucket, branchId) => {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
return objectNotFoundFallback(key, bucket, branchId);
|
|
269
|
+
if (isNeonApiError(err)) {
|
|
270
|
+
const serverMessage = serverErrorMessage(err.data);
|
|
271
|
+
if (serverMessage !== void 0) return serverMessage;
|
|
272
|
+
}
|
|
273
|
+
return objectNotFoundFallback(key, bucket, branchId);
|
|
347
274
|
};
|
|
348
|
-
// Stream a file from disk as a WHATWG `ReadableStream` suitable for a `fetch`
|
|
349
|
-
// request body, applying backpressure so we never read faster than the upload
|
|
350
|
-
// drains. Uses the global `ReadableStream` (what `fetch` expects) directly, so
|
|
351
|
-
// there's no Node-vs-DOM stream type bridging.
|
|
352
275
|
const fileToWebStream = (path) => {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
source.resume();
|
|
376
|
-
},
|
|
377
|
-
cancel() {
|
|
378
|
-
source.destroy();
|
|
379
|
-
},
|
|
380
|
-
});
|
|
276
|
+
const source = createReadStream(path);
|
|
277
|
+
return new ReadableStream({
|
|
278
|
+
start(controller) {
|
|
279
|
+
source.on("data", (chunk) => {
|
|
280
|
+
const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk);
|
|
281
|
+
controller.enqueue(bytes);
|
|
282
|
+
if ((controller.desiredSize ?? 0) <= 0) source.pause();
|
|
283
|
+
});
|
|
284
|
+
source.on("end", () => {
|
|
285
|
+
controller.close();
|
|
286
|
+
});
|
|
287
|
+
source.on("error", (err) => {
|
|
288
|
+
controller.error(err instanceof Error ? err : new Error(String(err)));
|
|
289
|
+
});
|
|
290
|
+
},
|
|
291
|
+
pull() {
|
|
292
|
+
source.resume();
|
|
293
|
+
},
|
|
294
|
+
cancel() {
|
|
295
|
+
source.destroy();
|
|
296
|
+
}
|
|
297
|
+
});
|
|
381
298
|
};
|
|
382
299
|
const getObject = async (props) => {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
await pipeline(response.data, createWriteStream(destination));
|
|
411
|
-
}
|
|
412
|
-
catch (err) {
|
|
413
|
-
// Best-effort cleanup of the partial file before rethrowing.
|
|
414
|
-
await unlink(destination).catch(() => undefined);
|
|
415
|
-
throw err;
|
|
416
|
-
}
|
|
417
|
-
log.info(`Object "${key}" downloaded from bucket "${bucket}" on branch ${branchId} to ${destination}`);
|
|
300
|
+
const branchId = await branchIdFromProps(props);
|
|
301
|
+
const { bucket, rest: key } = splitBucketTarget(props.target);
|
|
302
|
+
if (key === "") throw new Error("Object target must be in the form <bucket>/<key>.");
|
|
303
|
+
let response;
|
|
304
|
+
try {
|
|
305
|
+
response = await getProjectBranchBucketObject(props.apiClient, {
|
|
306
|
+
projectId: props.projectId,
|
|
307
|
+
branchId,
|
|
308
|
+
bucketName: bucket,
|
|
309
|
+
objectKey: key
|
|
310
|
+
});
|
|
311
|
+
} catch (err) {
|
|
312
|
+
if (isNeonApiError(err) && err.status === 404) {
|
|
313
|
+
const serverMessage = await streamErrorMessage(err.data);
|
|
314
|
+
throw new Error(serverMessage ?? objectNotFoundFallback(key, bucket, branchId));
|
|
315
|
+
}
|
|
316
|
+
throw err;
|
|
317
|
+
}
|
|
318
|
+
const contentDisposition = response.headers["content-disposition"];
|
|
319
|
+
const destination = props.file ?? filenameFromContentDisposition(contentDisposition, key);
|
|
320
|
+
try {
|
|
321
|
+
await pipeline(response.data, createWriteStream(destination));
|
|
322
|
+
} catch (err) {
|
|
323
|
+
await unlink(destination).catch(() => void 0);
|
|
324
|
+
throw err;
|
|
325
|
+
}
|
|
326
|
+
log.info(`Object "${key}" downloaded from bucket "${bucket}" on branch ${branchId} to ${destination}`);
|
|
418
327
|
};
|
|
419
328
|
const putObject = async (props) => {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
throw new Error(serverMessage ??
|
|
470
|
-
`Failed to presign upload for "${key}" in bucket "${bucket}" on branch ${branchId}${status !== undefined ? ` (HTTP ${status})` : ""}: ${err.message}`);
|
|
471
|
-
}
|
|
472
|
-
throw err;
|
|
473
|
-
}
|
|
474
|
-
// Stream the file straight into the PUT body via `fetch`; never buffer the
|
|
475
|
-
// whole file. The presigned URL targets the branch S3 data-plane endpoint
|
|
476
|
-
// directly, so this PUT bypasses the console API entirely.
|
|
477
|
-
//
|
|
478
|
-
// `presign.headers` carries the signature-relevant headers (e.g. host,
|
|
479
|
-
// content-type); the server does not sign Content-Length, so we set it
|
|
480
|
-
// ourselves from the stat'd size. `redirect: 'error'` ensures we never resend
|
|
481
|
-
// the file bytes and signed headers to a different host if the data-plane
|
|
482
|
-
// endpoint were to answer with a redirect. `duplex: 'half'` is required by
|
|
483
|
-
// fetch when streaming a request body.
|
|
484
|
-
const upload = {
|
|
485
|
-
method: "PUT",
|
|
486
|
-
headers: {
|
|
487
|
-
...presign.headers,
|
|
488
|
-
"Content-Length": String(fileSize),
|
|
489
|
-
},
|
|
490
|
-
body: fileToWebStream(props.file),
|
|
491
|
-
redirect: "error",
|
|
492
|
-
duplex: "half",
|
|
493
|
-
};
|
|
494
|
-
let uploadResponse;
|
|
495
|
-
try {
|
|
496
|
-
uploadResponse = await fetch(presign.url, upload);
|
|
497
|
-
}
|
|
498
|
-
catch (err) {
|
|
499
|
-
// A transport-level failure (DNS, connection reset, redirect when none is
|
|
500
|
-
// allowed). Surface a clean message without leaking the signed URL.
|
|
501
|
-
throw new Error(`Failed to upload "${props.file}" to "${key}" in bucket "${bucket}" on branch ${branchId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
502
|
-
}
|
|
503
|
-
if (!uploadResponse.ok) {
|
|
504
|
-
// The upload targets the S3 data plane, whose error bodies are XML rather
|
|
505
|
-
// than the JSON `{ message }` the console returns, so surface the status
|
|
506
|
-
// rather than the body. Never include the presigned URL, which carries the
|
|
507
|
-
// signature.
|
|
508
|
-
throw new Error(`Failed to upload "${props.file}" to "${key}" in bucket "${bucket}" on branch ${branchId} (HTTP ${uploadResponse.status}): Request failed with status code ${uploadResponse.status}`);
|
|
509
|
-
}
|
|
510
|
-
log.info(`File "${props.file}" uploaded to "${key}" in bucket "${bucket}" on branch ${branchId}`);
|
|
329
|
+
const branchId = await branchIdFromProps(props);
|
|
330
|
+
const { bucket, rest: key } = splitBucketTarget(props.target);
|
|
331
|
+
if (bucket === "" || key === "") throw new Error("Object target must be in the form <bucket>/<key>.");
|
|
332
|
+
let fileSize;
|
|
333
|
+
try {
|
|
334
|
+
const fileStat = await stat(props.file);
|
|
335
|
+
if (!fileStat.isFile()) throw new Error(`"${props.file}" is not a regular file.`);
|
|
336
|
+
fileSize = fileStat.size;
|
|
337
|
+
} catch (err) {
|
|
338
|
+
if (err?.code === "ENOENT") throw new Error(`File "${props.file}" does not exist.`);
|
|
339
|
+
throw err;
|
|
340
|
+
}
|
|
341
|
+
if (fileSize > MAX_OBJECT_BYTES) throw new Error(`File "${props.file}" is ${fileSize} bytes, which exceeds the ${MAX_OBJECT_BYTES}-byte (100 MB) single-upload limit. Larger objects are not supported yet.`);
|
|
342
|
+
let presign;
|
|
343
|
+
try {
|
|
344
|
+
({data: presign} = await presignUpload(props.apiClient, {
|
|
345
|
+
projectId: props.projectId,
|
|
346
|
+
branchId,
|
|
347
|
+
bucketName: bucket,
|
|
348
|
+
objectKey: key,
|
|
349
|
+
contentType: props.contentType
|
|
350
|
+
}));
|
|
351
|
+
} catch (err) {
|
|
352
|
+
if (isNeonApiError(err)) {
|
|
353
|
+
const status = err.status;
|
|
354
|
+
if (status === 404) throw new Error(objectNotFoundMessage(err, key, bucket, branchId));
|
|
355
|
+
const serverMessage = serverErrorMessage(err.data);
|
|
356
|
+
throw new Error(serverMessage ?? `Failed to presign upload for "${key}" in bucket "${bucket}" on branch ${branchId}${status !== void 0 ? ` (HTTP ${status})` : ""}: ${err.message}`);
|
|
357
|
+
}
|
|
358
|
+
throw err;
|
|
359
|
+
}
|
|
360
|
+
const upload = {
|
|
361
|
+
method: "PUT",
|
|
362
|
+
headers: {
|
|
363
|
+
...presign.headers,
|
|
364
|
+
"Content-Length": String(fileSize)
|
|
365
|
+
},
|
|
366
|
+
body: fileToWebStream(props.file),
|
|
367
|
+
redirect: "error",
|
|
368
|
+
duplex: "half"
|
|
369
|
+
};
|
|
370
|
+
let uploadResponse;
|
|
371
|
+
try {
|
|
372
|
+
uploadResponse = await fetch(presign.url, upload);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
throw new Error(`Failed to upload "${props.file}" to "${key}" in bucket "${bucket}" on branch ${branchId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
375
|
+
}
|
|
376
|
+
if (!uploadResponse.ok) throw new Error(`Failed to upload "${props.file}" to "${key}" in bucket "${bucket}" on branch ${branchId} (HTTP ${uploadResponse.status}): Request failed with status code ${uploadResponse.status}`);
|
|
377
|
+
log.info(`File "${props.file}" uploaded to "${key}" in bucket "${bucket}" on branch ${branchId}`);
|
|
511
378
|
};
|
|
512
379
|
const deleteObject = async (props) => {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}));
|
|
541
|
-
}
|
|
542
|
-
catch (err) {
|
|
543
|
-
if (isNeonApiError(err) && err.status === 404) {
|
|
544
|
-
throw new Error(objectNotFoundMessage(err, rest, bucket, branchId));
|
|
545
|
-
}
|
|
546
|
-
throw err;
|
|
547
|
-
}
|
|
548
|
-
log.info(`Object "${rest}" deleted from bucket "${bucket}" on branch ${branchId}`);
|
|
380
|
+
const branchId = await branchIdFromProps(props);
|
|
381
|
+
const { bucket, rest } = splitBucketTarget(props.target);
|
|
382
|
+
if (props.recursive) {
|
|
383
|
+
if (rest === "") throw new Error("Recursive delete requires a non-empty prefix ending in \"/\".");
|
|
384
|
+
if (!rest.endsWith("/")) throw new Error(`Recursive delete requires a prefix ending in "/" (got "${rest}").`);
|
|
385
|
+
const { data } = await retryOnLock(() => deleteProjectBranchBucketObjectsByPrefix(props.apiClient, {
|
|
386
|
+
projectId: props.projectId,
|
|
387
|
+
branchId,
|
|
388
|
+
bucketName: bucket,
|
|
389
|
+
prefix: rest
|
|
390
|
+
}));
|
|
391
|
+
log.info(`Deleted ${data.deleted} object(s) under prefix "${rest}" from bucket "${bucket}" on branch ${branchId}`);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (rest === "") throw new Error("Object target must be in the form <bucket>/<key>.");
|
|
395
|
+
try {
|
|
396
|
+
await retryOnLock(() => deleteProjectBranchBucketObject(props.apiClient, {
|
|
397
|
+
projectId: props.projectId,
|
|
398
|
+
branchId,
|
|
399
|
+
bucketName: bucket,
|
|
400
|
+
objectKey: rest
|
|
401
|
+
}));
|
|
402
|
+
} catch (err) {
|
|
403
|
+
if (isNeonApiError(err) && err.status === 404) throw new Error(objectNotFoundMessage(err, rest, bucket, branchId));
|
|
404
|
+
throw err;
|
|
405
|
+
}
|
|
406
|
+
log.info(`Object "${rest}" deleted from bucket "${bucket}" on branch ${branchId}`);
|
|
549
407
|
};
|
|
408
|
+
//#endregion
|
|
409
|
+
export { aliases, builder, command, describe, handler, resolveListDelimiter, splitBucketTarget, bucket_exports as t };
|