@roj-ai/platform-cli 0.1.6 → 0.1.7
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/resource.d.ts.map +1 -1
- package/dist/resource.js +55 -19
- package/dist/resource.js.map +1 -1
- package/dist/upload.d.ts.map +1 -1
- package/dist/upload.js +49 -12
- package/dist/upload.js.map +1 -1
- package/package.json +2 -2
- package/src/resource.ts +82 -20
- package/src/upload.ts +77 -12
package/dist/resource.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAKA,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE;IAChE,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;CACd,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAKA,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE;IAChE,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;CACd,GAAG,OAAO,CAAC,IAAI,CAAC,CAsHhB"}
|
package/dist/resource.js
CHANGED
|
@@ -25,24 +25,30 @@ export async function uploadResource(pathOrDir, options) {
|
|
|
25
25
|
mimeType = guessMimeType(filename);
|
|
26
26
|
}
|
|
27
27
|
try {
|
|
28
|
-
// 1.
|
|
29
|
-
console.log(`Uploading file: ${filename}`);
|
|
28
|
+
// 1. Hash file content for dedup
|
|
30
29
|
const file = Bun.file(filePath);
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
const buf = await file.arrayBuffer();
|
|
31
|
+
const contentHash = await sha256Hex(buf);
|
|
32
|
+
// 2. Preflight upload — if hash already known, server skips R2 put.
|
|
33
|
+
let uploadResult = await postFile({ url: options.url, apiKey: options.apiKey, contentHash, filename, mimeType });
|
|
34
|
+
if (uploadResult.status === 409 && uploadResult.body?.error === 'file-required') {
|
|
35
|
+
uploadResult = await postFile({
|
|
36
|
+
url: options.url,
|
|
37
|
+
apiKey: options.apiKey,
|
|
38
|
+
contentHash,
|
|
39
|
+
filename,
|
|
40
|
+
mimeType,
|
|
41
|
+
body: { buf, filename, mimeType },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (uploadResult.status >= 400 || !uploadResult.body?.ok || !uploadResult.body.fileId) {
|
|
45
|
+
console.error('File upload failed:', uploadResult.body?.error ?? `HTTP ${uploadResult.status}`);
|
|
41
46
|
process.exit(1);
|
|
42
47
|
}
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
const fileId = uploadResult.body.fileId;
|
|
49
|
+
const dedupNote = uploadResult.body.deduped ? ' (deduped, reused existing R2 object)' : '';
|
|
50
|
+
console.log(`File uploaded: ${fileId}${dedupNote}`);
|
|
51
|
+
// 3. Check if resource exists
|
|
46
52
|
const getResponse = await fetch(`${options.url}/api/v1/rpc`, {
|
|
47
53
|
method: 'POST',
|
|
48
54
|
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
@@ -59,17 +65,22 @@ export async function uploadResource(pathOrDir, options) {
|
|
|
59
65
|
method: 'resources.addRevision',
|
|
60
66
|
input: {
|
|
61
67
|
resourceSlug: options.slug,
|
|
62
|
-
fileId
|
|
68
|
+
fileId,
|
|
63
69
|
label: options.label,
|
|
64
70
|
},
|
|
65
71
|
}),
|
|
66
72
|
});
|
|
67
73
|
const revResult = await revResponse.json();
|
|
68
|
-
if (!revResult.ok) {
|
|
74
|
+
if (!revResult.ok || !revResult.value) {
|
|
69
75
|
console.error('Failed to add revision:', revResult);
|
|
70
76
|
process.exit(1);
|
|
71
77
|
}
|
|
72
|
-
|
|
78
|
+
if (revResult.value.noop) {
|
|
79
|
+
console.log(`Unchanged: latest revision already points at this file (revisionId=${revResult.value.revisionId})`);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.log(`Revision added: ${revResult.value.revisionId}`);
|
|
83
|
+
}
|
|
73
84
|
}
|
|
74
85
|
else {
|
|
75
86
|
// 3b. Resource doesn't exist → create
|
|
@@ -83,7 +94,7 @@ export async function uploadResource(pathOrDir, options) {
|
|
|
83
94
|
slug: options.slug,
|
|
84
95
|
name: options.name,
|
|
85
96
|
description: options.description,
|
|
86
|
-
fileId
|
|
97
|
+
fileId,
|
|
87
98
|
label: options.label,
|
|
88
99
|
},
|
|
89
100
|
}),
|
|
@@ -103,6 +114,31 @@ export async function uploadResource(pathOrDir, options) {
|
|
|
103
114
|
}
|
|
104
115
|
}
|
|
105
116
|
}
|
|
117
|
+
async function postFile(args) {
|
|
118
|
+
const formData = new FormData();
|
|
119
|
+
formData.append('contentHash', args.contentHash);
|
|
120
|
+
formData.append('filename', args.filename);
|
|
121
|
+
formData.append('mimeType', args.mimeType);
|
|
122
|
+
if (args.body) {
|
|
123
|
+
formData.append('file', new Blob([args.body.buf], { type: args.body.mimeType }), args.body.filename);
|
|
124
|
+
}
|
|
125
|
+
const response = await fetch(`${args.url}/api/v1/files/upload`, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: { Authorization: `Bearer ${args.apiKey}` },
|
|
128
|
+
body: formData,
|
|
129
|
+
});
|
|
130
|
+
const body = await response.json().catch(() => null);
|
|
131
|
+
return { status: response.status, body };
|
|
132
|
+
}
|
|
133
|
+
async function sha256Hex(buf) {
|
|
134
|
+
const digest = await crypto.subtle.digest('SHA-256', buf);
|
|
135
|
+
const bytes = new Uint8Array(digest);
|
|
136
|
+
let hex = '';
|
|
137
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
138
|
+
hex += bytes[i].toString(16).padStart(2, '0');
|
|
139
|
+
}
|
|
140
|
+
return hex;
|
|
141
|
+
}
|
|
106
142
|
function guessMimeType(filename) {
|
|
107
143
|
const ext = filename.split('.').pop()?.toLowerCase();
|
|
108
144
|
switch (ext) {
|
package/dist/resource.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAC/C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAEhC,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,SAAiB,EAAE,OAOvD;IACA,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IACnC,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAChC,MAAM,OAAO,GAAG,EAAE,aAAa,EAAE,UAAU,OAAO,CAAC,MAAM,EAAE,EAAE,CAAA;IAE7D,IAAI,QAAgB,CAAA;IACpB,IAAI,QAAgB,CAAA;IACpB,IAAI,QAAgB,CAAA;IACpB,IAAI,OAA2B,CAAA;IAE/B,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QACzB,oBAAoB;QACpB,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC,CAAA;QACtD,QAAQ,GAAG,GAAG,OAAO,CAAC,IAAI,MAAM,CAAA;QAChC,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;QAClC,QAAQ,GAAG,iBAAiB,CAAA;QAC5B,OAAO,CAAC,GAAG,CAAC,sBAAsB,QAAQ,EAAE,CAAC,CAAA;QAC7C,QAAQ,CAAC,UAAU,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;IACnF,CAAC;SAAM,CAAC;QACP,QAAQ,GAAG,QAAQ,CAAA;QACnB,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAC7B,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;IACnC,CAAC;IAED,IAAI,CAAC;QACJ,
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
|
package/dist/upload.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AAAA,wBAAsB,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE;IACzD,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AAAA,wBAAsB,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE;IACzD,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsChB"}
|
package/dist/upload.js
CHANGED
|
@@ -4,23 +4,60 @@ export async function upload(bundlePath, options) {
|
|
|
4
4
|
console.error(`Bundle not found: ${bundlePath}`);
|
|
5
5
|
process.exit(1);
|
|
6
6
|
}
|
|
7
|
+
const filename = bundlePath.split('/').pop();
|
|
8
|
+
const buf = await file.arrayBuffer();
|
|
9
|
+
const contentHash = await sha256Hex(buf);
|
|
10
|
+
// 1. Preflight: ask server if it already has this content for the org.
|
|
11
|
+
let result = await postBundle({ url: options.url, apiKey: options.apiKey, name: options.name, version: options.version, contentHash });
|
|
12
|
+
// 2. Server reports the bundle bytes are missing — retry with body.
|
|
13
|
+
if (result.status === 409 && result.body?.error === 'bundle-required') {
|
|
14
|
+
result = await postBundle({
|
|
15
|
+
url: options.url,
|
|
16
|
+
apiKey: options.apiKey,
|
|
17
|
+
name: options.name,
|
|
18
|
+
version: options.version,
|
|
19
|
+
contentHash,
|
|
20
|
+
body: { buf, filename, mimeType: file.type || 'application/javascript' },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
if (result.status >= 400 || !result.body?.ok) {
|
|
24
|
+
console.error('Upload failed:', result.body?.error ?? `HTTP ${result.status}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const status = result.body.noop
|
|
28
|
+
? `unchanged (latest revision already at ${shortHash(contentHash)})`
|
|
29
|
+
: result.body.deduped
|
|
30
|
+
? `new revision pointing at existing bundle (${shortHash(contentHash)})`
|
|
31
|
+
: `uploaded new bundle (${shortHash(contentHash)})`;
|
|
32
|
+
console.log(`${status}: slug=${result.body.bundleSlug} revisionId=${result.body.revisionId}`);
|
|
33
|
+
}
|
|
34
|
+
async function postBundle(args) {
|
|
7
35
|
const formData = new FormData();
|
|
8
|
-
formData.append('
|
|
9
|
-
formData.append('
|
|
10
|
-
if (
|
|
11
|
-
formData.append('version',
|
|
36
|
+
formData.append('name', args.name);
|
|
37
|
+
formData.append('contentHash', args.contentHash);
|
|
38
|
+
if (args.version)
|
|
39
|
+
formData.append('version', args.version);
|
|
40
|
+
if (args.body) {
|
|
41
|
+
formData.append('bundle', new Blob([args.body.buf], { type: args.body.mimeType }), args.body.filename);
|
|
12
42
|
}
|
|
13
|
-
const response = await fetch(`${
|
|
43
|
+
const response = await fetch(`${args.url}/api/v1/bundles/upload`, {
|
|
14
44
|
method: 'POST',
|
|
15
|
-
headers: { Authorization: `Bearer ${
|
|
45
|
+
headers: { Authorization: `Bearer ${args.apiKey}` },
|
|
16
46
|
body: formData,
|
|
17
47
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
48
|
+
const body = await response.json().catch(() => null);
|
|
49
|
+
return { status: response.status, body };
|
|
50
|
+
}
|
|
51
|
+
async function sha256Hex(buf) {
|
|
52
|
+
const digest = await crypto.subtle.digest('SHA-256', buf);
|
|
53
|
+
const bytes = new Uint8Array(digest);
|
|
54
|
+
let hex = '';
|
|
55
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
56
|
+
hex += bytes[i].toString(16).padStart(2, '0');
|
|
22
57
|
}
|
|
23
|
-
|
|
24
|
-
|
|
58
|
+
return hex;
|
|
59
|
+
}
|
|
60
|
+
function shortHash(hex) {
|
|
61
|
+
return hex.slice(0, 12);
|
|
25
62
|
}
|
|
26
63
|
//# sourceMappingURL=upload.js.map
|
package/dist/upload.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,UAAkB,EAAE,OAKhD;IACA,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACjC,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CAAC,qBAAqB,UAAU,EAAE,CAAC,CAAA;QAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAChB,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roj-ai/platform-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"roj": "./dist/main.js"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"url": "https://github.com/contember/roj/issues"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@roj-ai/sandbox-runtime": "^0.1.
|
|
25
|
+
"@roj-ai/sandbox-runtime": "^0.1.7"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/bun": "^1.3.3"
|
package/src/resource.ts
CHANGED
|
@@ -35,28 +35,34 @@ export async function uploadResource(pathOrDir: string, options: {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
try {
|
|
38
|
-
// 1.
|
|
39
|
-
console.log(`Uploading file: ${filename}`)
|
|
38
|
+
// 1. Hash file content for dedup
|
|
40
39
|
const file = Bun.file(filePath)
|
|
41
|
-
const
|
|
42
|
-
|
|
40
|
+
const buf = await file.arrayBuffer()
|
|
41
|
+
const contentHash = await sha256Hex(buf)
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
// 2. Preflight upload — if hash already known, server skips R2 put.
|
|
44
|
+
let uploadResult = await postFile({ url: options.url, apiKey: options.apiKey, contentHash, filename, mimeType })
|
|
45
|
+
if (uploadResult.status === 409 && uploadResult.body?.error === 'file-required') {
|
|
46
|
+
uploadResult = await postFile({
|
|
47
|
+
url: options.url,
|
|
48
|
+
apiKey: options.apiKey,
|
|
49
|
+
contentHash,
|
|
50
|
+
filename,
|
|
51
|
+
mimeType,
|
|
52
|
+
body: { buf, filename, mimeType },
|
|
53
|
+
})
|
|
54
|
+
}
|
|
49
55
|
|
|
50
|
-
if (!
|
|
51
|
-
|
|
52
|
-
console.error('File upload failed:', (error as { error?: string }).error ?? uploadResponse.statusText)
|
|
56
|
+
if (uploadResult.status >= 400 || !uploadResult.body?.ok || !uploadResult.body.fileId) {
|
|
57
|
+
console.error('File upload failed:', uploadResult.body?.error ?? `HTTP ${uploadResult.status}`)
|
|
53
58
|
process.exit(1)
|
|
54
59
|
}
|
|
55
60
|
|
|
56
|
-
const
|
|
57
|
-
|
|
61
|
+
const fileId = uploadResult.body.fileId
|
|
62
|
+
const dedupNote = uploadResult.body.deduped ? ' (deduped, reused existing R2 object)' : ''
|
|
63
|
+
console.log(`File uploaded: ${fileId}${dedupNote}`)
|
|
58
64
|
|
|
59
|
-
//
|
|
65
|
+
// 3. Check if resource exists
|
|
60
66
|
const getResponse = await fetch(`${options.url}/api/v1/rpc`, {
|
|
61
67
|
method: 'POST',
|
|
62
68
|
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
@@ -75,18 +81,22 @@ export async function uploadResource(pathOrDir: string, options: {
|
|
|
75
81
|
method: 'resources.addRevision',
|
|
76
82
|
input: {
|
|
77
83
|
resourceSlug: options.slug,
|
|
78
|
-
fileId
|
|
84
|
+
fileId,
|
|
79
85
|
label: options.label,
|
|
80
86
|
},
|
|
81
87
|
}),
|
|
82
88
|
})
|
|
83
89
|
|
|
84
|
-
const revResult = await revResponse.json() as { ok: boolean; value?: { revisionId: string } }
|
|
85
|
-
if (!revResult.ok) {
|
|
90
|
+
const revResult = await revResponse.json() as { ok: boolean; value?: { revisionId: string; noop?: boolean } }
|
|
91
|
+
if (!revResult.ok || !revResult.value) {
|
|
86
92
|
console.error('Failed to add revision:', revResult)
|
|
87
93
|
process.exit(1)
|
|
88
94
|
}
|
|
89
|
-
|
|
95
|
+
if (revResult.value.noop) {
|
|
96
|
+
console.log(`Unchanged: latest revision already points at this file (revisionId=${revResult.value.revisionId})`)
|
|
97
|
+
} else {
|
|
98
|
+
console.log(`Revision added: ${revResult.value.revisionId}`)
|
|
99
|
+
}
|
|
90
100
|
} else {
|
|
91
101
|
// 3b. Resource doesn't exist → create
|
|
92
102
|
console.log(`Creating resource "${options.slug}"...`)
|
|
@@ -99,7 +109,7 @@ export async function uploadResource(pathOrDir: string, options: {
|
|
|
99
109
|
slug: options.slug,
|
|
100
110
|
name: options.name,
|
|
101
111
|
description: options.description,
|
|
102
|
-
fileId
|
|
112
|
+
fileId,
|
|
103
113
|
label: options.label,
|
|
104
114
|
},
|
|
105
115
|
}),
|
|
@@ -120,6 +130,58 @@ export async function uploadResource(pathOrDir: string, options: {
|
|
|
120
130
|
}
|
|
121
131
|
}
|
|
122
132
|
|
|
133
|
+
interface PostFileArgs {
|
|
134
|
+
url: string
|
|
135
|
+
apiKey: string
|
|
136
|
+
contentHash: string
|
|
137
|
+
filename: string
|
|
138
|
+
mimeType: string
|
|
139
|
+
body?: { buf: ArrayBuffer; filename: string; mimeType: string }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface PostFileResult {
|
|
143
|
+
status: number
|
|
144
|
+
body: {
|
|
145
|
+
ok?: boolean
|
|
146
|
+
error?: string
|
|
147
|
+
fileId?: string
|
|
148
|
+
filename?: string
|
|
149
|
+
mimeType?: string
|
|
150
|
+
size?: number
|
|
151
|
+
r2Key?: string
|
|
152
|
+
deduped?: boolean
|
|
153
|
+
} | null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function postFile(args: PostFileArgs): Promise<PostFileResult> {
|
|
157
|
+
const formData = new FormData()
|
|
158
|
+
formData.append('contentHash', args.contentHash)
|
|
159
|
+
formData.append('filename', args.filename)
|
|
160
|
+
formData.append('mimeType', args.mimeType)
|
|
161
|
+
if (args.body) {
|
|
162
|
+
formData.append('file', new Blob([args.body.buf], { type: args.body.mimeType }), args.body.filename)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const response = await fetch(`${args.url}/api/v1/files/upload`, {
|
|
166
|
+
method: 'POST',
|
|
167
|
+
headers: { Authorization: `Bearer ${args.apiKey}` },
|
|
168
|
+
body: formData,
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const body = await response.json().catch(() => null) as PostFileResult['body']
|
|
172
|
+
return { status: response.status, body }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function sha256Hex(buf: ArrayBuffer): Promise<string> {
|
|
176
|
+
const digest = await crypto.subtle.digest('SHA-256', buf)
|
|
177
|
+
const bytes = new Uint8Array(digest)
|
|
178
|
+
let hex = ''
|
|
179
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
180
|
+
hex += bytes[i].toString(16).padStart(2, '0')
|
|
181
|
+
}
|
|
182
|
+
return hex
|
|
183
|
+
}
|
|
184
|
+
|
|
123
185
|
function guessMimeType(filename: string): string {
|
|
124
186
|
const ext = filename.split('.').pop()?.toLowerCase()
|
|
125
187
|
switch (ext) {
|
package/src/upload.ts
CHANGED
|
@@ -10,25 +10,90 @@ export async function upload(bundlePath: string, options: {
|
|
|
10
10
|
process.exit(1)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
const filename = bundlePath.split('/').pop()!
|
|
14
|
+
const buf = await file.arrayBuffer()
|
|
15
|
+
const contentHash = await sha256Hex(buf)
|
|
16
|
+
|
|
17
|
+
// 1. Preflight: ask server if it already has this content for the org.
|
|
18
|
+
let result = await postBundle({ url: options.url, apiKey: options.apiKey, name: options.name, version: options.version, contentHash })
|
|
19
|
+
|
|
20
|
+
// 2. Server reports the bundle bytes are missing — retry with body.
|
|
21
|
+
if (result.status === 409 && result.body?.error === 'bundle-required') {
|
|
22
|
+
result = await postBundle({
|
|
23
|
+
url: options.url,
|
|
24
|
+
apiKey: options.apiKey,
|
|
25
|
+
name: options.name,
|
|
26
|
+
version: options.version,
|
|
27
|
+
contentHash,
|
|
28
|
+
body: { buf, filename, mimeType: file.type || 'application/javascript' },
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (result.status >= 400 || !result.body?.ok) {
|
|
33
|
+
console.error('Upload failed:', result.body?.error ?? `HTTP ${result.status}`)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const status = result.body.noop
|
|
38
|
+
? `unchanged (latest revision already at ${shortHash(contentHash)})`
|
|
39
|
+
: result.body.deduped
|
|
40
|
+
? `new revision pointing at existing bundle (${shortHash(contentHash)})`
|
|
41
|
+
: `uploaded new bundle (${shortHash(contentHash)})`
|
|
42
|
+
|
|
43
|
+
console.log(`${status}: slug=${result.body.bundleSlug} revisionId=${result.body.revisionId}`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface PostBundleArgs {
|
|
47
|
+
url: string
|
|
48
|
+
apiKey: string
|
|
49
|
+
name: string
|
|
50
|
+
version?: string
|
|
51
|
+
contentHash: string
|
|
52
|
+
body?: { buf: ArrayBuffer; filename: string; mimeType: string }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface PostBundleResult {
|
|
56
|
+
status: number
|
|
57
|
+
body: {
|
|
58
|
+
ok?: boolean
|
|
59
|
+
error?: string
|
|
60
|
+
bundleSlug?: string
|
|
61
|
+
revisionId?: string
|
|
62
|
+
r2Key?: string
|
|
63
|
+
deduped?: boolean
|
|
64
|
+
noop?: boolean
|
|
65
|
+
} | null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function postBundle(args: PostBundleArgs): Promise<PostBundleResult> {
|
|
13
69
|
const formData = new FormData()
|
|
14
|
-
formData.append('
|
|
15
|
-
formData.append('
|
|
16
|
-
if (
|
|
17
|
-
|
|
70
|
+
formData.append('name', args.name)
|
|
71
|
+
formData.append('contentHash', args.contentHash)
|
|
72
|
+
if (args.version) formData.append('version', args.version)
|
|
73
|
+
if (args.body) {
|
|
74
|
+
formData.append('bundle', new Blob([args.body.buf], { type: args.body.mimeType }), args.body.filename)
|
|
18
75
|
}
|
|
19
76
|
|
|
20
|
-
const response = await fetch(`${
|
|
77
|
+
const response = await fetch(`${args.url}/api/v1/bundles/upload`, {
|
|
21
78
|
method: 'POST',
|
|
22
|
-
headers: { Authorization: `Bearer ${
|
|
79
|
+
headers: { Authorization: `Bearer ${args.apiKey}` },
|
|
23
80
|
body: formData,
|
|
24
81
|
})
|
|
25
82
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
83
|
+
const body = await response.json().catch(() => null) as PostBundleResult['body']
|
|
84
|
+
return { status: response.status, body }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function sha256Hex(buf: ArrayBuffer): Promise<string> {
|
|
88
|
+
const digest = await crypto.subtle.digest('SHA-256', buf)
|
|
89
|
+
const bytes = new Uint8Array(digest)
|
|
90
|
+
let hex = ''
|
|
91
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
92
|
+
hex += bytes[i].toString(16).padStart(2, '0')
|
|
30
93
|
}
|
|
94
|
+
return hex
|
|
95
|
+
}
|
|
31
96
|
|
|
32
|
-
|
|
33
|
-
|
|
97
|
+
function shortHash(hex: string): string {
|
|
98
|
+
return hex.slice(0, 12)
|
|
34
99
|
}
|