@fluid-app/fluid-cli-assets 0.1.627 → 0.1.629
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +14 -29
- package/jest.mocks/fluid-cli.ts +2 -0
- package/package.json +2 -2
- package/src/commands/upload.ts +24 -64
package/dist/index.mjs
CHANGED
|
@@ -1,42 +1,27 @@
|
|
|
1
|
-
import { createDomainCommand, declareInputs, findProjectConfig, getAuthToken } from "@fluid-app/fluid-cli";
|
|
1
|
+
import { createDomainCommand, declareInputs, findProjectConfig, getAuthToken, uploadToDam } from "@fluid-app/fluid-cli";
|
|
2
2
|
import "commander";
|
|
3
3
|
import { basename } from "node:path";
|
|
4
4
|
//#region src/commands/upload.ts
|
|
5
|
-
const UPLOAD_BASE = (process.env["FLUID_UPLOAD_BASE"] ?? "https://upload.fluid.app").replace(/\/$/, "");
|
|
6
5
|
function registerUpload(parent, ctx) {
|
|
7
6
|
declareInputs(parent.command("upload [file]").description("Upload a file or URL to the Fluid DAM (via upload.fluid.app)").option("--url <url>", "Fetch from an external URL instead of attaching a local file").option("--name <name>", "Display name for the DAM asset").option("--description <description>", "Asset description").option("--tags <tags>", "Comma-separated tags").option("--folder <folder>", "ImageKit folder override").option("--no-unique-filename", "Disable ImageKit's useUniqueFileName (default: enabled)").option("--create-media", "Also create a Fluid Media resource (active, share, publish-now)").action(async function(filePath, opts) {
|
|
8
7
|
const token = resolveToken(this.parent?.opts() ?? {});
|
|
9
8
|
if (!token) throw new Error("No API token found. Run `fluid login` or provide --token <token>.");
|
|
10
9
|
if (!filePath && !opts.url) throw new Error("Provide a file path or --url <url>. Run `fluid dam upload --help` for usage.");
|
|
11
10
|
if (filePath && opts.url) throw new Error("Provide either a file path OR --url, not both.");
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (opts.createMedia) form.append("create_media", "true");
|
|
26
|
-
if (ctx.verbose) process.stderr.write(`POST ${UPLOAD_BASE}/upload\n`);
|
|
27
|
-
const resp = await fetch(`${UPLOAD_BASE}/upload`, {
|
|
28
|
-
method: "POST",
|
|
29
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
30
|
-
body: form
|
|
11
|
+
const body = await uploadToDam({
|
|
12
|
+
token,
|
|
13
|
+
...filePath ? { file: {
|
|
14
|
+
bytes: ctx.inputs.readFile(filePath),
|
|
15
|
+
name: basename(filePath)
|
|
16
|
+
} } : { url: opts.url },
|
|
17
|
+
name: opts.name,
|
|
18
|
+
description: opts.description,
|
|
19
|
+
tags: opts.tags,
|
|
20
|
+
folder: opts.folder,
|
|
21
|
+
uniqueFilename: opts.uniqueFilename,
|
|
22
|
+
createMedia: opts.createMedia,
|
|
23
|
+
onRequest: ctx.verbose ? (line) => process.stderr.write(`${line}\n`) : void 0
|
|
31
24
|
});
|
|
32
|
-
if (!resp.ok) {
|
|
33
|
-
let detail = "";
|
|
34
|
-
try {
|
|
35
|
-
detail = await resp.text();
|
|
36
|
-
} catch {}
|
|
37
|
-
throw new Error(`Upload failed (HTTP ${resp.status})${detail ? `: ${detail.slice(0, 400)}` : ""}`);
|
|
38
|
-
}
|
|
39
|
-
const body = await resp.json();
|
|
40
25
|
ctx.output(body);
|
|
41
26
|
}), (_opts, args) => args[0] ? [{
|
|
42
27
|
kind: "file",
|
package/jest.mocks/fluid-cli.ts
CHANGED
|
@@ -12,3 +12,5 @@ export function findProjectConfig(): null {
|
|
|
12
12
|
|
|
13
13
|
export * from "../../core/src/inputs/index";
|
|
14
14
|
export { createInputsReader } from "../../core/src/inputs/reader";
|
|
15
|
+
// Dependency-free, so the real request is what the tests drive.
|
|
16
|
+
export * from "../../core/src/dam/upload";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluid-app/fluid-cli-assets",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.629",
|
|
4
4
|
"description": "Fluid CLI commands for digital asset management (DAM)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@fluid-app/fluid-cli": "0.1.
|
|
20
|
+
"@fluid-app/fluid-cli": "0.1.653",
|
|
21
21
|
"commander": "^12.0.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
package/src/commands/upload.ts
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* - Positional `<file>` path → multipart upload from disk.
|
|
10
10
|
* - `--url <url>` → service-side fetch + upload.
|
|
11
11
|
*
|
|
12
|
+
* The request itself is `uploadToDam` from the core CLI, which
|
|
13
|
+
* `fluid sites media sync` sends its files through too.
|
|
14
|
+
*
|
|
12
15
|
* Auth comes from the same --token / --profile / env / active-profile
|
|
13
16
|
* resolution path the rest of the CLI uses; we re-implement the
|
|
14
17
|
* resolve here instead of going through `ctx.getClient()` because
|
|
@@ -21,19 +24,10 @@ import {
|
|
|
21
24
|
declareInputs,
|
|
22
25
|
findProjectConfig,
|
|
23
26
|
getAuthToken,
|
|
27
|
+
uploadToDam,
|
|
24
28
|
} from "@fluid-app/fluid-cli";
|
|
25
29
|
import type { CommandContext } from "@fluid-app/fluid-cli";
|
|
26
30
|
|
|
27
|
-
// Hard-coded — the bearer token we forward is the user's Fluid API
|
|
28
|
-
// token, so the destination MUST be a Fluid-controlled host or any
|
|
29
|
-
// `--upload-base evil.example` would exfiltrate it. If you're running the
|
|
30
|
-
// upload service locally, point at it via the
|
|
31
|
-
// FLUID_UPLOAD_BASE env var instead (read at process start, not
|
|
32
|
-
// from an end-user-typed argv string).
|
|
33
|
-
const UPLOAD_BASE = (
|
|
34
|
-
process.env["FLUID_UPLOAD_BASE"] ?? "https://upload.fluid.app"
|
|
35
|
-
).replace(/\/$/, "");
|
|
36
|
-
|
|
37
31
|
interface UploadOptions {
|
|
38
32
|
url?: string;
|
|
39
33
|
name?: string;
|
|
@@ -98,61 +92,27 @@ export function registerUpload(parent: Command, ctx: CommandContext): void {
|
|
|
98
92
|
throw new Error("Provide either a file path OR --url, not both.");
|
|
99
93
|
}
|
|
100
94
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// emits a proper Content-Disposition with filename. Node 20+
|
|
122
|
-
// global FormData accepts (Blob, filename) pairs.
|
|
123
|
-
form.append("file", new Blob([new Uint8Array(bytes)]), name);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (opts.name) form.append("name", opts.name);
|
|
127
|
-
if (opts.description) form.append("description", opts.description);
|
|
128
|
-
if (opts.tags) form.append("tags", opts.tags);
|
|
129
|
-
if (opts.createMedia) form.append("create_media", "true");
|
|
130
|
-
|
|
131
|
-
if (ctx.verbose) {
|
|
132
|
-
process.stderr.write(`POST ${UPLOAD_BASE}/upload\n`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const resp = await fetch(`${UPLOAD_BASE}/upload`, {
|
|
136
|
-
method: "POST",
|
|
137
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
138
|
-
body: form,
|
|
95
|
+
const body = await uploadToDam({
|
|
96
|
+
token,
|
|
97
|
+
// Exactly one of the two was given, checked above.
|
|
98
|
+
...(filePath
|
|
99
|
+
? {
|
|
100
|
+
file: {
|
|
101
|
+
bytes: ctx.inputs.readFile(filePath),
|
|
102
|
+
name: basename(filePath),
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
: { url: opts.url }),
|
|
106
|
+
name: opts.name,
|
|
107
|
+
description: opts.description,
|
|
108
|
+
tags: opts.tags,
|
|
109
|
+
folder: opts.folder,
|
|
110
|
+
uniqueFilename: opts.uniqueFilename,
|
|
111
|
+
createMedia: opts.createMedia,
|
|
112
|
+
onRequest: ctx.verbose
|
|
113
|
+
? (line) => process.stderr.write(`${line}\n`)
|
|
114
|
+
: undefined,
|
|
139
115
|
});
|
|
140
|
-
|
|
141
|
-
if (!resp.ok) {
|
|
142
|
-
let detail = "";
|
|
143
|
-
try {
|
|
144
|
-
detail = await resp.text();
|
|
145
|
-
} catch {
|
|
146
|
-
// No body or unreadable — error message stays generic.
|
|
147
|
-
}
|
|
148
|
-
throw new Error(
|
|
149
|
-
`Upload failed (HTTP ${resp.status})${
|
|
150
|
-
detail ? `: ${detail.slice(0, 400)}` : ""
|
|
151
|
-
}`,
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const body: unknown = await resp.json();
|
|
156
116
|
ctx.output(body);
|
|
157
117
|
});
|
|
158
118
|
declareInputs(upload, (_opts, args) =>
|