@clickraft/cli 0.8.6 → 0.8.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/CHANGELOG.md +1 -1
- package/dist/cli.js +144 -144
- package/dist/cli.js.map +1 -1
- package/dist/index.js +144 -144
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,6 +14,8 @@ import { request as undiciRequest } from "undici";
|
|
|
14
14
|
import { z as z6 } from "zod";
|
|
15
15
|
import { createHash, randomUUID } from "crypto";
|
|
16
16
|
import { z as z5 } from "zod";
|
|
17
|
+
import { createHash as createHash2 } from "crypto";
|
|
18
|
+
import { request as undiciRequest2 } from "undici";
|
|
17
19
|
var TOKEN_FIELD_PATTERN = /"(access_token|refresh_token|device_code|user_code|code_verifier|client_secret)"\s*:\s*"[^"]*"/gi;
|
|
18
20
|
var BARE_TOKEN_PATTERN = /\bck_(?:live|dev|test)_[A-Za-z0-9_-]+/gi;
|
|
19
21
|
function redactTokenLikeFields(input) {
|
|
@@ -677,6 +679,140 @@ async function patch(params) {
|
|
|
677
679
|
workflow: data.workflow
|
|
678
680
|
};
|
|
679
681
|
}
|
|
682
|
+
async function uploadAsset(params) {
|
|
683
|
+
const contentType = detectContentType(params.filename, params.content);
|
|
684
|
+
const sha = sha256Hex(params.content);
|
|
685
|
+
const contentHash = `sha256:${sha}`;
|
|
686
|
+
const idempotencyKey = generateUploadIdempotencyKey(params.content, params.scope);
|
|
687
|
+
const response = await params.client.request({
|
|
688
|
+
method: "POST",
|
|
689
|
+
path: "/assets",
|
|
690
|
+
body: {
|
|
691
|
+
filename: params.filename,
|
|
692
|
+
contentType,
|
|
693
|
+
sizeBytes: params.content.length,
|
|
694
|
+
contentHash
|
|
695
|
+
},
|
|
696
|
+
idempotencyKey,
|
|
697
|
+
responseSchema: params.schema,
|
|
698
|
+
signal: params.signal
|
|
699
|
+
});
|
|
700
|
+
if (!response.deduplicated) {
|
|
701
|
+
const first = await tryPut({
|
|
702
|
+
uploadUrl: response.uploadUrl,
|
|
703
|
+
content: params.content,
|
|
704
|
+
contentType,
|
|
705
|
+
signal: params.signal,
|
|
706
|
+
dispatcher: params.putDispatcher
|
|
707
|
+
});
|
|
708
|
+
if (!first.ok) {
|
|
709
|
+
if (first.kind === "fatal") {
|
|
710
|
+
throw new ApiError(
|
|
711
|
+
"STORAGE_UPLOAD_FAILED",
|
|
712
|
+
`Upload to signed URL failed for ${params.filename} (HTTP ${first.status ?? "unknown"}).`,
|
|
713
|
+
{ retryable: false }
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
const retry = await tryPut({
|
|
717
|
+
uploadUrl: response.uploadUrl,
|
|
718
|
+
content: params.content,
|
|
719
|
+
contentType,
|
|
720
|
+
signal: params.signal,
|
|
721
|
+
dispatcher: params.putDispatcher
|
|
722
|
+
});
|
|
723
|
+
if (!retry.ok) {
|
|
724
|
+
throw new ApiError(
|
|
725
|
+
"STORAGE_UPLOAD_FAILED",
|
|
726
|
+
`Upload to signed URL failed for ${params.filename} after one retry.`,
|
|
727
|
+
{ retryable: true }
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return {
|
|
733
|
+
assetId: response.assetId,
|
|
734
|
+
publicUrl: response.publicUrl,
|
|
735
|
+
deduplicated: response.deduplicated,
|
|
736
|
+
bytes: params.content.length,
|
|
737
|
+
contentHash
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
async function tryPut(args) {
|
|
741
|
+
try {
|
|
742
|
+
const response = await undiciRequest2(args.uploadUrl, {
|
|
743
|
+
method: "PUT",
|
|
744
|
+
body: args.content,
|
|
745
|
+
headers: {
|
|
746
|
+
"content-type": args.contentType,
|
|
747
|
+
"content-length": String(args.content.length)
|
|
748
|
+
},
|
|
749
|
+
signal: args.signal,
|
|
750
|
+
dispatcher: args.dispatcher
|
|
751
|
+
});
|
|
752
|
+
await response.body.text();
|
|
753
|
+
const status = response.statusCode;
|
|
754
|
+
if (status >= 200 && status < 300) return { ok: true };
|
|
755
|
+
if (status >= 400 && status < 500) return { ok: false, kind: "fatal", status };
|
|
756
|
+
return { ok: false, kind: "retryable", status };
|
|
757
|
+
} catch {
|
|
758
|
+
return { ok: false, kind: "retryable" };
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function sha256Hex(content) {
|
|
762
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
763
|
+
}
|
|
764
|
+
var MAGIC_NUMBERS = [
|
|
765
|
+
{ contentType: "image/png", prefix: [137, 80, 78, 71, 13, 10, 26, 10] },
|
|
766
|
+
{ contentType: "image/jpeg", prefix: [255, 216, 255] },
|
|
767
|
+
{ contentType: "image/webp", prefix: [82, 73, 70, 70] }
|
|
768
|
+
// also matches WAV — disambiguated below
|
|
769
|
+
];
|
|
770
|
+
var EXTENSION_FALLBACK = {
|
|
771
|
+
png: "image/png",
|
|
772
|
+
jpg: "image/jpeg",
|
|
773
|
+
jpeg: "image/jpeg",
|
|
774
|
+
gif: "image/gif",
|
|
775
|
+
webp: "image/webp",
|
|
776
|
+
mp4: "video/mp4",
|
|
777
|
+
mov: "video/quicktime",
|
|
778
|
+
mp3: "audio/mpeg",
|
|
779
|
+
wav: "audio/wav",
|
|
780
|
+
pdf: "application/pdf",
|
|
781
|
+
txt: "text/plain",
|
|
782
|
+
json: "application/json"
|
|
783
|
+
};
|
|
784
|
+
function detectContentType(filename, content) {
|
|
785
|
+
if (content.length >= 6) {
|
|
786
|
+
const head = content.subarray(0, 6).toString("ascii");
|
|
787
|
+
if (head === "GIF87a" || head === "GIF89a") return "image/gif";
|
|
788
|
+
}
|
|
789
|
+
if (content.length >= 12 && content.subarray(4, 8).toString("ascii") === "ftyp") {
|
|
790
|
+
return "video/mp4";
|
|
791
|
+
}
|
|
792
|
+
for (const { contentType, prefix } of MAGIC_NUMBERS) {
|
|
793
|
+
if (matchesPrefix(content, prefix)) {
|
|
794
|
+
if (contentType === "image/webp") {
|
|
795
|
+
const formatId = content.subarray(8, 12).toString("ascii");
|
|
796
|
+
if (formatId === "WEBP") return "image/webp";
|
|
797
|
+
if (formatId === "WAVE") return "audio/wav";
|
|
798
|
+
break;
|
|
799
|
+
}
|
|
800
|
+
return contentType;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
const ext = filename.toLowerCase().split(".").pop();
|
|
804
|
+
if (ext && EXTENSION_FALLBACK[ext]) {
|
|
805
|
+
return EXTENSION_FALLBACK[ext];
|
|
806
|
+
}
|
|
807
|
+
return "application/octet-stream";
|
|
808
|
+
}
|
|
809
|
+
function matchesPrefix(content, prefix) {
|
|
810
|
+
if (content.length < prefix.length) return false;
|
|
811
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
812
|
+
if (content[i] !== prefix[i]) return false;
|
|
813
|
+
}
|
|
814
|
+
return true;
|
|
815
|
+
}
|
|
680
816
|
|
|
681
817
|
// src/errors/codes.ts
|
|
682
818
|
var LOGIN_HINT = "Run `clickraft login` to authenticate.";
|
|
@@ -1635,10 +1771,9 @@ var GenerationResultSchema = z9.object({
|
|
|
1635
1771
|
}).strict();
|
|
1636
1772
|
|
|
1637
1773
|
// src/commands/upload.ts
|
|
1638
|
-
import { createHash as createHash2 } from "crypto";
|
|
1639
1774
|
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
1640
1775
|
import { basename } from "path";
|
|
1641
|
-
import
|
|
1776
|
+
import "undici";
|
|
1642
1777
|
|
|
1643
1778
|
// src/schemas/assets.ts
|
|
1644
1779
|
import { z as z10 } from "zod";
|
|
@@ -1663,60 +1798,16 @@ async function uploadFile(opts) {
|
|
|
1663
1798
|
const size = await safeStatSize(opts.filePath);
|
|
1664
1799
|
validateSize(size);
|
|
1665
1800
|
const content = await safeReadFile(opts.filePath);
|
|
1666
|
-
const contentType = detectContentType(opts.filePath, content);
|
|
1667
|
-
const sha = sha256Hex(content);
|
|
1668
|
-
const contentHash = `sha256:${sha}`;
|
|
1669
|
-
const filename = basename(opts.filePath);
|
|
1670
|
-
const idempotencyKey = generateUploadIdempotencyKey(content, scope);
|
|
1671
1801
|
const client = opts.client ?? await buildClient(opts);
|
|
1672
|
-
|
|
1802
|
+
return uploadAsset({
|
|
1673
1803
|
client,
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1804
|
+
content,
|
|
1805
|
+
filename: basename(opts.filePath),
|
|
1806
|
+
scope,
|
|
1807
|
+
signal: opts.signal,
|
|
1808
|
+
putDispatcher: opts.putDispatcher,
|
|
1809
|
+
schema: UploadResponseSchema
|
|
1680
1810
|
});
|
|
1681
|
-
if (!response.deduplicated) {
|
|
1682
|
-
const first = await tryPut({
|
|
1683
|
-
uploadUrl: response.uploadUrl,
|
|
1684
|
-
content,
|
|
1685
|
-
contentType,
|
|
1686
|
-
signal: opts.signal,
|
|
1687
|
-
dispatcher: opts.putDispatcher
|
|
1688
|
-
});
|
|
1689
|
-
if (!first.ok) {
|
|
1690
|
-
if (first.kind === "fatal") {
|
|
1691
|
-
throw new ApiError(
|
|
1692
|
-
"STORAGE_UPLOAD_FAILED",
|
|
1693
|
-
`Upload to signed URL failed for ${filename} (HTTP ${first.status ?? "unknown"}).`,
|
|
1694
|
-
{ retryable: false }
|
|
1695
|
-
);
|
|
1696
|
-
}
|
|
1697
|
-
const retry = await tryPut({
|
|
1698
|
-
uploadUrl: response.uploadUrl,
|
|
1699
|
-
content,
|
|
1700
|
-
contentType,
|
|
1701
|
-
signal: opts.signal,
|
|
1702
|
-
dispatcher: opts.putDispatcher
|
|
1703
|
-
});
|
|
1704
|
-
if (!retry.ok) {
|
|
1705
|
-
throw new ApiError(
|
|
1706
|
-
"STORAGE_UPLOAD_FAILED",
|
|
1707
|
-
`Upload to signed URL failed for ${filename} after one retry.`,
|
|
1708
|
-
{ retryable: true }
|
|
1709
|
-
);
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
return {
|
|
1714
|
-
assetId: response.assetId,
|
|
1715
|
-
publicUrl: response.publicUrl,
|
|
1716
|
-
deduplicated: response.deduplicated,
|
|
1717
|
-
bytes: content.length,
|
|
1718
|
-
contentHash
|
|
1719
|
-
};
|
|
1720
1811
|
}
|
|
1721
1812
|
async function buildClient(opts) {
|
|
1722
1813
|
const cfg = await loadConfig({
|
|
@@ -1731,42 +1822,6 @@ async function buildClient(opts) {
|
|
|
1731
1822
|
cloudflareAccess: cfg.cloudflareAccess
|
|
1732
1823
|
});
|
|
1733
1824
|
}
|
|
1734
|
-
async function postAssetMetadata(args) {
|
|
1735
|
-
return args.client.request({
|
|
1736
|
-
method: "POST",
|
|
1737
|
-
path: "/assets",
|
|
1738
|
-
body: {
|
|
1739
|
-
filename: args.filename,
|
|
1740
|
-
contentType: args.contentType,
|
|
1741
|
-
sizeBytes: args.sizeBytes,
|
|
1742
|
-
contentHash: args.contentHash
|
|
1743
|
-
},
|
|
1744
|
-
idempotencyKey: args.idempotencyKey,
|
|
1745
|
-
responseSchema: UploadResponseSchema,
|
|
1746
|
-
signal: args.signal
|
|
1747
|
-
});
|
|
1748
|
-
}
|
|
1749
|
-
async function tryPut(args) {
|
|
1750
|
-
try {
|
|
1751
|
-
const response = await undiciRequest2(args.uploadUrl, {
|
|
1752
|
-
method: "PUT",
|
|
1753
|
-
body: args.content,
|
|
1754
|
-
headers: {
|
|
1755
|
-
"content-type": args.contentType,
|
|
1756
|
-
"content-length": String(args.content.length)
|
|
1757
|
-
},
|
|
1758
|
-
signal: args.signal,
|
|
1759
|
-
dispatcher: args.dispatcher
|
|
1760
|
-
});
|
|
1761
|
-
await response.body.text();
|
|
1762
|
-
const status = response.statusCode;
|
|
1763
|
-
if (status >= 200 && status < 300) return { ok: true };
|
|
1764
|
-
if (status >= 400 && status < 500) return { ok: false, kind: "fatal", status };
|
|
1765
|
-
return { ok: false, kind: "retryable", status };
|
|
1766
|
-
} catch {
|
|
1767
|
-
return { ok: false, kind: "retryable" };
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
1825
|
async function safeStatSize(filePath) {
|
|
1771
1826
|
let stats;
|
|
1772
1827
|
try {
|
|
@@ -1828,61 +1883,6 @@ function validateSize(bytes) {
|
|
|
1828
1883
|
);
|
|
1829
1884
|
}
|
|
1830
1885
|
}
|
|
1831
|
-
function sha256Hex(content) {
|
|
1832
|
-
return createHash2("sha256").update(content).digest("hex");
|
|
1833
|
-
}
|
|
1834
|
-
var MAGIC_NUMBERS = [
|
|
1835
|
-
{ contentType: "image/png", prefix: [137, 80, 78, 71, 13, 10, 26, 10] },
|
|
1836
|
-
{ contentType: "image/jpeg", prefix: [255, 216, 255] },
|
|
1837
|
-
{ contentType: "image/webp", prefix: [82, 73, 70, 70] }
|
|
1838
|
-
// also matches WAV — disambiguated below
|
|
1839
|
-
];
|
|
1840
|
-
var EXTENSION_FALLBACK = {
|
|
1841
|
-
png: "image/png",
|
|
1842
|
-
jpg: "image/jpeg",
|
|
1843
|
-
jpeg: "image/jpeg",
|
|
1844
|
-
gif: "image/gif",
|
|
1845
|
-
webp: "image/webp",
|
|
1846
|
-
mp4: "video/mp4",
|
|
1847
|
-
mov: "video/quicktime",
|
|
1848
|
-
mp3: "audio/mpeg",
|
|
1849
|
-
wav: "audio/wav",
|
|
1850
|
-
pdf: "application/pdf",
|
|
1851
|
-
txt: "text/plain",
|
|
1852
|
-
json: "application/json"
|
|
1853
|
-
};
|
|
1854
|
-
function detectContentType(filePath, content) {
|
|
1855
|
-
if (content.length >= 6) {
|
|
1856
|
-
const head = content.subarray(0, 6).toString("ascii");
|
|
1857
|
-
if (head === "GIF87a" || head === "GIF89a") return "image/gif";
|
|
1858
|
-
}
|
|
1859
|
-
if (content.length >= 12 && content.subarray(4, 8).toString("ascii") === "ftyp") {
|
|
1860
|
-
return "video/mp4";
|
|
1861
|
-
}
|
|
1862
|
-
for (const { contentType, prefix } of MAGIC_NUMBERS) {
|
|
1863
|
-
if (matchesPrefix(content, prefix)) {
|
|
1864
|
-
if (contentType === "image/webp") {
|
|
1865
|
-
const formatId = content.subarray(8, 12).toString("ascii");
|
|
1866
|
-
if (formatId === "WEBP") return "image/webp";
|
|
1867
|
-
if (formatId === "WAVE") return "audio/wav";
|
|
1868
|
-
break;
|
|
1869
|
-
}
|
|
1870
|
-
return contentType;
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
const ext = filePath.toLowerCase().split(".").pop();
|
|
1874
|
-
if (ext && EXTENSION_FALLBACK[ext]) {
|
|
1875
|
-
return EXTENSION_FALLBACK[ext];
|
|
1876
|
-
}
|
|
1877
|
-
return "application/octet-stream";
|
|
1878
|
-
}
|
|
1879
|
-
function matchesPrefix(content, prefix) {
|
|
1880
|
-
if (content.length < prefix.length) return false;
|
|
1881
|
-
for (let i = 0; i < prefix.length; i++) {
|
|
1882
|
-
if (content[i] !== prefix[i]) return false;
|
|
1883
|
-
}
|
|
1884
|
-
return true;
|
|
1885
|
-
}
|
|
1886
1886
|
|
|
1887
1887
|
// src/commands/generate/internal.ts
|
|
1888
1888
|
async function buildGenerateClient(opts) {
|