@hot-updater/cloudflare 0.31.4 → 0.32.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/dist/iac/index.cjs +206 -92
- package/dist/iac/index.mjs +198 -84
- package/dist/index.cjs +214 -51
- package/dist/index.d.cts +46 -6
- package/dist/index.d.mts +46 -6
- package/dist/index.mjs +209 -47
- package/dist/worker/index.d.cts +4 -3
- package/dist/worker/index.d.mts +3 -2
- package/package.json +10 -7
- package/src/r2S3Storage.ts +197 -0
- package/src/r2Storage.spec.ts +316 -2
- package/src/r2Storage.ts +50 -110
- package/src/r2WranglerStorage.ts +193 -0
- package/worker/dist/README.md +1 -1
- package/worker/dist/index.js +85 -24
- package/worker/dist/index.js.map +4 -4
- package/worker/src/index.ts +0 -1
package/src/r2Storage.ts
CHANGED
|
@@ -1,126 +1,66 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
1
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
parseStorageUri,
|
|
2
|
+
createUniversalStoragePlugin,
|
|
3
|
+
type StoragePluginHooks,
|
|
4
|
+
type UniversalStoragePlugin,
|
|
9
5
|
} from "@hot-updater/plugin-core";
|
|
10
|
-
import { ExecaError } from "execa";
|
|
11
6
|
|
|
12
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
createS3RuntimeStorageProfile,
|
|
9
|
+
createS3StorageProfile,
|
|
10
|
+
type R2S3StorageConfig,
|
|
11
|
+
} from "./r2S3Storage";
|
|
12
|
+
import {
|
|
13
|
+
createWranglerRuntimeStorageProfile,
|
|
14
|
+
createWranglerStorageProfile,
|
|
15
|
+
type R2WranglerStorageConfig,
|
|
16
|
+
} from "./r2WranglerStorage";
|
|
13
17
|
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
18
|
+
export type R2StorageConfig = R2S3StorageConfig | R2WranglerStorageConfig;
|
|
19
|
+
|
|
20
|
+
export type { R2S3StorageConfig, R2WranglerStorageConfig };
|
|
21
|
+
|
|
22
|
+
const hasS3Credentials = (
|
|
23
|
+
config: R2StorageConfig,
|
|
24
|
+
): config is R2S3StorageConfig => {
|
|
25
|
+
return Boolean(config.credentials);
|
|
26
|
+
};
|
|
23
27
|
|
|
24
28
|
/**
|
|
25
29
|
* Cloudflare R2 storage plugin for Hot Updater.
|
|
26
30
|
*/
|
|
27
|
-
|
|
31
|
+
interface R2Storage {
|
|
32
|
+
(
|
|
33
|
+
config: R2S3StorageConfig,
|
|
34
|
+
hooks?: StoragePluginHooks,
|
|
35
|
+
): () => UniversalStoragePlugin;
|
|
36
|
+
/**
|
|
37
|
+
* @deprecated `cloudflareApiToken` uses the Wrangler CLI for R2 operations,
|
|
38
|
+
* which is slower than direct S3-compatible API access. Create R2
|
|
39
|
+
* S3-compatible credentials in the Cloudflare dashboard and pass them with
|
|
40
|
+
* `r2Storage({ credentials })` instead.
|
|
41
|
+
*/
|
|
42
|
+
(
|
|
43
|
+
config: R2WranglerStorageConfig,
|
|
44
|
+
hooks?: StoragePluginHooks,
|
|
45
|
+
): () => UniversalStoragePlugin;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const createR2StoragePlugin = createUniversalStoragePlugin<R2StorageConfig>({
|
|
28
49
|
name: "r2Storage",
|
|
29
50
|
supportedProtocol: "r2",
|
|
30
51
|
factory: (config) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const getStorageKey = createStorageKeyBuilder(config.basePath);
|
|
52
|
+
if (hasS3Credentials(config)) {
|
|
53
|
+
return {
|
|
54
|
+
node: createS3StorageProfile(config),
|
|
55
|
+
runtime: createS3RuntimeStorageProfile(config),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
39
58
|
|
|
40
59
|
return {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (bucket !== bucketName) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`,
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
await wrangler(
|
|
51
|
-
"r2",
|
|
52
|
-
"object",
|
|
53
|
-
"delete",
|
|
54
|
-
[bucketName, key].join("/"),
|
|
55
|
-
"--remote",
|
|
56
|
-
);
|
|
57
|
-
} catch {
|
|
58
|
-
throw new Error("Can not delete bundle");
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
async upload(key, filePath) {
|
|
62
|
-
const contentType = getContentType(filePath);
|
|
63
|
-
|
|
64
|
-
const filename = path.basename(filePath);
|
|
65
|
-
|
|
66
|
-
const Key = getStorageKey(key, filename);
|
|
67
|
-
try {
|
|
68
|
-
const { stderr, exitCode } = await wrangler(
|
|
69
|
-
"r2",
|
|
70
|
-
"object",
|
|
71
|
-
"put",
|
|
72
|
-
[bucketName, Key].join("/"),
|
|
73
|
-
"--file",
|
|
74
|
-
filePath,
|
|
75
|
-
"--content-type",
|
|
76
|
-
contentType,
|
|
77
|
-
"--remote",
|
|
78
|
-
);
|
|
79
|
-
if (exitCode !== 0 && stderr) {
|
|
80
|
-
throw new Error(stderr);
|
|
81
|
-
}
|
|
82
|
-
} catch (error) {
|
|
83
|
-
if (error instanceof ExecaError) {
|
|
84
|
-
throw new Error(error.stderr || error.stdout);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
storageUri: `r2://${bucketName}/${Key}`,
|
|
92
|
-
};
|
|
93
|
-
},
|
|
94
|
-
async downloadFile(storageUri, filePath) {
|
|
95
|
-
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
96
|
-
if (bucket !== bucketName) {
|
|
97
|
-
throw new Error(
|
|
98
|
-
`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`,
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
104
|
-
const { stderr, exitCode } = await wrangler(
|
|
105
|
-
"r2",
|
|
106
|
-
"object",
|
|
107
|
-
"get",
|
|
108
|
-
[bucketName, key].join("/"),
|
|
109
|
-
"--file",
|
|
110
|
-
filePath,
|
|
111
|
-
"--remote",
|
|
112
|
-
);
|
|
113
|
-
if (exitCode !== 0 && stderr) {
|
|
114
|
-
throw new Error(stderr);
|
|
115
|
-
}
|
|
116
|
-
} catch (error) {
|
|
117
|
-
if (error instanceof ExecaError) {
|
|
118
|
-
throw new Error(error.stderr || error.stdout);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
throw error;
|
|
122
|
-
}
|
|
123
|
-
},
|
|
60
|
+
node: createWranglerStorageProfile(config),
|
|
61
|
+
runtime: createWranglerRuntimeStorageProfile(),
|
|
124
62
|
};
|
|
125
63
|
},
|
|
126
64
|
});
|
|
65
|
+
|
|
66
|
+
export const r2Storage: R2Storage = createR2StoragePlugin;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createStorageKeyBuilder,
|
|
7
|
+
getContentType,
|
|
8
|
+
type NodeStorageProfile,
|
|
9
|
+
parseStorageUri,
|
|
10
|
+
type RuntimeStorageProfile,
|
|
11
|
+
} from "@hot-updater/plugin-core";
|
|
12
|
+
import { ExecaError } from "execa";
|
|
13
|
+
|
|
14
|
+
import { createWrangler } from "./utils/createWrangler";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated `cloudflareApiToken` uses the Wrangler CLI for R2 operations,
|
|
18
|
+
* which is slower than direct S3-compatible API access. Create R2
|
|
19
|
+
* S3-compatible credentials in the Cloudflare dashboard and pass them with
|
|
20
|
+
* `r2Storage({ credentials })` instead.
|
|
21
|
+
*/
|
|
22
|
+
export interface R2WranglerStorageConfig {
|
|
23
|
+
accountId: string;
|
|
24
|
+
bucketName: string;
|
|
25
|
+
/**
|
|
26
|
+
* @deprecated This token keeps R2 access on the slower Wrangler CLI path.
|
|
27
|
+
* Create R2 S3-compatible credentials in the Cloudflare dashboard and use
|
|
28
|
+
* `credentials` instead.
|
|
29
|
+
*/
|
|
30
|
+
cloudflareApiToken: string;
|
|
31
|
+
/**
|
|
32
|
+
* Base path where bundles will be stored in the bucket
|
|
33
|
+
*/
|
|
34
|
+
basePath?: string;
|
|
35
|
+
credentials?: never;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ensureExpectedR2Bucket = (bucket: string, bucketName: string) => {
|
|
39
|
+
if (bucket !== bucketName) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const isR2ObjectNotFoundError = (error: ExecaError) => {
|
|
47
|
+
const output = [error.stderr, error.stdout, error.shortMessage, error.message]
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.join("\n")
|
|
50
|
+
.toLowerCase();
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
output.includes("not found") ||
|
|
54
|
+
output.includes("no such object") ||
|
|
55
|
+
output.includes("does not exist")
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const createWranglerStorageProfile = (
|
|
60
|
+
config: R2WranglerStorageConfig,
|
|
61
|
+
): NodeStorageProfile => {
|
|
62
|
+
const { bucketName, cloudflareApiToken, accountId } = config;
|
|
63
|
+
const wrangler = createWrangler({
|
|
64
|
+
accountId,
|
|
65
|
+
cloudflareApiToken,
|
|
66
|
+
cwd: process.cwd(),
|
|
67
|
+
});
|
|
68
|
+
const getStorageKey = createStorageKeyBuilder(config.basePath);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
async delete(storageUri) {
|
|
72
|
+
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
73
|
+
ensureExpectedR2Bucket(bucket, bucketName);
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
await wrangler(
|
|
77
|
+
"r2",
|
|
78
|
+
"object",
|
|
79
|
+
"delete",
|
|
80
|
+
[bucketName, key].join("/"),
|
|
81
|
+
"--remote",
|
|
82
|
+
);
|
|
83
|
+
} catch {
|
|
84
|
+
throw new Error("Can not delete bundle");
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
async upload(key, filePath) {
|
|
88
|
+
const contentType = getContentType(filePath);
|
|
89
|
+
|
|
90
|
+
const filename = path.basename(filePath);
|
|
91
|
+
|
|
92
|
+
const Key = getStorageKey(key, filename);
|
|
93
|
+
try {
|
|
94
|
+
const { stderr, exitCode } = await wrangler(
|
|
95
|
+
"r2",
|
|
96
|
+
"object",
|
|
97
|
+
"put",
|
|
98
|
+
[bucketName, Key].join("/"),
|
|
99
|
+
"--file",
|
|
100
|
+
filePath,
|
|
101
|
+
"--content-type",
|
|
102
|
+
contentType,
|
|
103
|
+
"--remote",
|
|
104
|
+
);
|
|
105
|
+
if (exitCode !== 0 && stderr) {
|
|
106
|
+
throw new Error(stderr);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (error instanceof ExecaError) {
|
|
110
|
+
throw new Error(error.stderr || error.stdout);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
storageUri: `r2://${bucketName}/${Key}`,
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
async exists(storageUri: string) {
|
|
121
|
+
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
122
|
+
ensureExpectedR2Bucket(bucket, bucketName);
|
|
123
|
+
|
|
124
|
+
const tempDir = await fs.mkdtemp(
|
|
125
|
+
path.join(os.tmpdir(), "hot-updater-r2-exists-"),
|
|
126
|
+
);
|
|
127
|
+
const tempFilePath = path.join(tempDir, "object");
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await wrangler(
|
|
131
|
+
"r2",
|
|
132
|
+
"object",
|
|
133
|
+
"get",
|
|
134
|
+
[bucketName, key].join("/"),
|
|
135
|
+
"--file",
|
|
136
|
+
tempFilePath,
|
|
137
|
+
"--remote",
|
|
138
|
+
);
|
|
139
|
+
return true;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error instanceof ExecaError && isR2ObjectNotFoundError(error)) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
throw error;
|
|
146
|
+
} finally {
|
|
147
|
+
await fs.rm(tempDir, { force: true, recursive: true });
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
async downloadFile(storageUri, filePath) {
|
|
151
|
+
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
152
|
+
ensureExpectedR2Bucket(bucket, bucketName);
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
156
|
+
const { stderr, exitCode } = await wrangler(
|
|
157
|
+
"r2",
|
|
158
|
+
"object",
|
|
159
|
+
"get",
|
|
160
|
+
[bucketName, key].join("/"),
|
|
161
|
+
"--file",
|
|
162
|
+
filePath,
|
|
163
|
+
"--remote",
|
|
164
|
+
);
|
|
165
|
+
if (exitCode !== 0 && stderr) {
|
|
166
|
+
throw new Error(stderr);
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (error instanceof ExecaError) {
|
|
170
|
+
throw new Error(error.stderr || error.stdout);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const createWranglerRuntimeStorageProfile =
|
|
180
|
+
(): RuntimeStorageProfile => {
|
|
181
|
+
const error = new Error(
|
|
182
|
+
"r2Storage runtime profile requires R2 S3 credentials. Wrangler-based R2 access is only supported by the node profile.",
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
async readText() {
|
|
187
|
+
throw error;
|
|
188
|
+
},
|
|
189
|
+
async getDownloadUrl() {
|
|
190
|
+
throw error;
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
};
|
package/worker/dist/README.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
This folder contains the built output assets for the worker "hot-updater" generated at 2026-05-
|
|
1
|
+
This folder contains the built output assets for the worker "hot-updater" generated at 2026-05-21T13:59:33.325Z.
|
package/worker/dist/index.js
CHANGED
|
@@ -5236,6 +5236,52 @@ function calculatePagination(total, options) {
|
|
|
5236
5236
|
}
|
|
5237
5237
|
__name(calculatePagination, "calculatePagination");
|
|
5238
5238
|
|
|
5239
|
+
// ../plugin-core/dist/contentAddressedAssets.mjs
|
|
5240
|
+
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_process();
|
|
5241
|
+
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_console();
|
|
5242
|
+
init_performance2();
|
|
5243
|
+
var getContentAddressedAssetStoragePath = /* @__PURE__ */ __name(({ assetPath, fileHash }) => {
|
|
5244
|
+
const extension = assetPath.endsWith(".br") ? ".br" : assetPath.includes(".") ? `.${assetPath.split(".").pop()}` : "";
|
|
5245
|
+
return `sha256/${fileHash.slice(0, 2)}/${fileHash}${extension}`;
|
|
5246
|
+
}, "getContentAddressedAssetStoragePath");
|
|
5247
|
+
|
|
5248
|
+
// ../plugin-core/dist/assetStorageLayout.mjs
|
|
5249
|
+
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_process();
|
|
5250
|
+
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_console();
|
|
5251
|
+
init_performance2();
|
|
5252
|
+
|
|
5253
|
+
// ../plugin-core/dist/legacyAssetStorageLayout.mjs
|
|
5254
|
+
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_process();
|
|
5255
|
+
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_console();
|
|
5256
|
+
init_performance2();
|
|
5257
|
+
var getLegacyManifestAssetStoragePath = /* @__PURE__ */ __name(({ assetPath }) => assetPath, "getLegacyManifestAssetStoragePath");
|
|
5258
|
+
|
|
5259
|
+
// ../plugin-core/dist/assetStorageLayout.mjs
|
|
5260
|
+
var createStorageUriWithRelativePath = /* @__PURE__ */ __name(({ baseStorageUri, relativePath }) => {
|
|
5261
|
+
const storageUrl = new URL(baseStorageUri);
|
|
5262
|
+
storageUrl.pathname = `${storageUrl.pathname.replace(/\/+$/, "")}/${relativePath.replace(/\\/g, "/").split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
5263
|
+
return storageUrl.toString();
|
|
5264
|
+
}, "createStorageUriWithRelativePath");
|
|
5265
|
+
var getAssetStorageLayout = /* @__PURE__ */ __name((assetBaseStorageUri) => {
|
|
5266
|
+
const pathname = new URL(assetBaseStorageUri).pathname.replace(/\/+$/, "");
|
|
5267
|
+
return pathname.endsWith("/assets") || pathname === "/assets" ? "content-addressed" : "legacy-files";
|
|
5268
|
+
}, "getAssetStorageLayout");
|
|
5269
|
+
var getManifestAssetStoragePath = /* @__PURE__ */ __name(({ assetBaseStorageUri, assetPath, fileHash }) => {
|
|
5270
|
+
if (getAssetStorageLayout(assetBaseStorageUri) === "content-addressed") return getContentAddressedAssetStoragePath({
|
|
5271
|
+
assetPath,
|
|
5272
|
+
fileHash
|
|
5273
|
+
});
|
|
5274
|
+
return getLegacyManifestAssetStoragePath({ assetPath });
|
|
5275
|
+
}, "getManifestAssetStoragePath");
|
|
5276
|
+
var resolveManifestAssetStorageUri = /* @__PURE__ */ __name(({ assetBaseStorageUri, assetPath, fileHash }) => createStorageUriWithRelativePath({
|
|
5277
|
+
baseStorageUri: assetBaseStorageUri,
|
|
5278
|
+
relativePath: getManifestAssetStoragePath({
|
|
5279
|
+
assetBaseStorageUri,
|
|
5280
|
+
assetPath,
|
|
5281
|
+
fileHash
|
|
5282
|
+
})
|
|
5283
|
+
}), "resolveManifestAssetStorageUri");
|
|
5284
|
+
|
|
5239
5285
|
// ../plugin-core/dist/createDatabasePlugin.mjs
|
|
5240
5286
|
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_process();
|
|
5241
5287
|
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_console();
|
|
@@ -5707,7 +5753,7 @@ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
|
5707
5753
|
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
5708
5754
|
var __getProtoOf2 = Object.getPrototypeOf;
|
|
5709
5755
|
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
5710
|
-
var __commonJSMin = /* @__PURE__ */ __name((cb, mod) => () => (mod ||
|
|
5756
|
+
var __commonJSMin = /* @__PURE__ */ __name((cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), "__commonJSMin");
|
|
5711
5757
|
var __copyProps2 = /* @__PURE__ */ __name((to, from, except, desc) => {
|
|
5712
5758
|
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames2(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
5713
5759
|
key = keys[i];
|
|
@@ -6718,7 +6764,6 @@ var require_min_version2 = /* @__PURE__ */ __commonJSMin((exports, module) => {
|
|
|
6718
6764
|
case "<":
|
|
6719
6765
|
case "<=":
|
|
6720
6766
|
break;
|
|
6721
|
-
/* istanbul ignore next */
|
|
6722
6767
|
default:
|
|
6723
6768
|
throw new Error(`Unexpected operation: ${comparator.operator}`);
|
|
6724
6769
|
}
|
|
@@ -8310,6 +8355,9 @@ var createProfiledStoragePlugin = /* @__PURE__ */ __name(({ createProfiles, name
|
|
|
8310
8355
|
async downloadFile(storageUri, filePath) {
|
|
8311
8356
|
return requireNodeProfile().downloadFile(storageUri, filePath);
|
|
8312
8357
|
},
|
|
8358
|
+
async exists(storageUri) {
|
|
8359
|
+
return requireNodeProfile().exists(storageUri);
|
|
8360
|
+
},
|
|
8313
8361
|
async upload(key, filePath) {
|
|
8314
8362
|
return requireNodeProfile().upload(key, filePath);
|
|
8315
8363
|
}
|
|
@@ -8434,13 +8482,6 @@ var isBundleManifest = /* @__PURE__ */ __name((value) => {
|
|
|
8434
8482
|
}
|
|
8435
8483
|
);
|
|
8436
8484
|
}, "isBundleManifest");
|
|
8437
|
-
var createChildStorageUri = /* @__PURE__ */ __name((baseStorageUri, relativePath) => {
|
|
8438
|
-
const baseUrl = new URL(baseStorageUri);
|
|
8439
|
-
const normalizedBasePath = baseUrl.pathname.replace(/\/+$/, "");
|
|
8440
|
-
const relativeSegments = relativePath.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment));
|
|
8441
|
-
baseUrl.pathname = `${normalizedBasePath}/${relativeSegments.join("/")}`;
|
|
8442
|
-
return baseUrl.toString();
|
|
8443
|
-
}, "createChildStorageUri");
|
|
8444
8485
|
async function fetchBundleManifest(storageUri, readStorageText, resolveFileUrl, context2) {
|
|
8445
8486
|
const [storageText, fileUrl] = await Promise.all([
|
|
8446
8487
|
readStorageText(storageUri, context2),
|
|
@@ -8491,10 +8532,11 @@ async function resolveChangedAssets({
|
|
|
8491
8532
|
}
|
|
8492
8533
|
const usesBrotliAsset = BR_COMPRESSED_ASSET_PATH_RE.test(assetPath);
|
|
8493
8534
|
const downloadPath = usesBrotliAsset ? `${assetPath}.br` : assetPath;
|
|
8494
|
-
const storageUri =
|
|
8535
|
+
const storageUri = resolveManifestAssetStorageUri({
|
|
8495
8536
|
assetBaseStorageUri,
|
|
8496
|
-
downloadPath
|
|
8497
|
-
|
|
8537
|
+
assetPath: downloadPath,
|
|
8538
|
+
fileHash: asset.fileHash
|
|
8539
|
+
});
|
|
8498
8540
|
const patch = patchDescriptor?.assetPath === assetPath ? patchDescriptor.patch : null;
|
|
8499
8541
|
let fileUrl = null;
|
|
8500
8542
|
try {
|
|
@@ -9022,7 +9064,7 @@ init_performance2();
|
|
|
9022
9064
|
// ../../packages/server/package.json
|
|
9023
9065
|
var package_default = {
|
|
9024
9066
|
name: "@hot-updater/server",
|
|
9025
|
-
version: "0.
|
|
9067
|
+
version: "0.32.0",
|
|
9026
9068
|
type: "module",
|
|
9027
9069
|
description: "React Native OTA solution for self-hosted",
|
|
9028
9070
|
sideEffects: false,
|
|
@@ -9120,6 +9162,8 @@ var HandlerBadRequestError = class extends Error {
|
|
|
9120
9162
|
};
|
|
9121
9163
|
var SDK_VERSION_HEADER = "Hot-Updater-SDK-Version";
|
|
9122
9164
|
var EXPLICIT_NO_UPDATE_MIN_SDK_VERSION = "0.31.0";
|
|
9165
|
+
var DEFAULT_BUNDLE_LIST_LIMIT = 50;
|
|
9166
|
+
var MAX_BUNDLE_LIST_LIMIT = 100;
|
|
9123
9167
|
var supportsExplicitNoUpdateResponse = /* @__PURE__ */ __name((request) => {
|
|
9124
9168
|
const sdkVersion = request.headers.get(SDK_VERSION_HEADER)?.trim();
|
|
9125
9169
|
if (!sdkVersion) {
|
|
@@ -9187,6 +9231,19 @@ var parseStringArraySearchParam = /* @__PURE__ */ __name((url, key) => {
|
|
|
9187
9231
|
const values = url.searchParams.getAll(key);
|
|
9188
9232
|
return values.length > 0 ? values : void 0;
|
|
9189
9233
|
}, "parseStringArraySearchParam");
|
|
9234
|
+
var parsePositiveIntegerSearchParam = /* @__PURE__ */ __name((url, key, defaultValue, maxValue) => {
|
|
9235
|
+
const value = url.searchParams.get(key);
|
|
9236
|
+
if (value === null) {
|
|
9237
|
+
return defaultValue;
|
|
9238
|
+
}
|
|
9239
|
+
const parsed = Number(value);
|
|
9240
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > maxValue) {
|
|
9241
|
+
throw new HandlerBadRequestError(
|
|
9242
|
+
`The '${key}' query parameter must be a positive integer between 1 and ${maxValue}.`
|
|
9243
|
+
);
|
|
9244
|
+
}
|
|
9245
|
+
return parsed;
|
|
9246
|
+
}, "parsePositiveIntegerSearchParam");
|
|
9190
9247
|
var requirePlatformParam = /* @__PURE__ */ __name((params) => {
|
|
9191
9248
|
const platform2 = requireRouteParam(params, "platform");
|
|
9192
9249
|
if (!isPlatform(platform2)) {
|
|
@@ -9271,7 +9328,12 @@ var handleGetBundles = /* @__PURE__ */ __name(async (_params, request, api, cont
|
|
|
9271
9328
|
const url = new URL(request.url);
|
|
9272
9329
|
const channel2 = url.searchParams.get("channel") ?? void 0;
|
|
9273
9330
|
const platform2 = url.searchParams.get("platform");
|
|
9274
|
-
const limit =
|
|
9331
|
+
const limit = parsePositiveIntegerSearchParam(
|
|
9332
|
+
url,
|
|
9333
|
+
"limit",
|
|
9334
|
+
DEFAULT_BUNDLE_LIST_LIMIT,
|
|
9335
|
+
MAX_BUNDLE_LIST_LIMIT
|
|
9336
|
+
);
|
|
9275
9337
|
const pageParam = url.searchParams.get("page");
|
|
9276
9338
|
const offset = url.searchParams.get("offset");
|
|
9277
9339
|
const after = url.searchParams.get("after") ?? void 0;
|
|
@@ -9407,14 +9469,13 @@ var routes = {
|
|
|
9407
9469
|
};
|
|
9408
9470
|
function createHandler(api, options = {}) {
|
|
9409
9471
|
const basePath = options.basePath ?? "/api";
|
|
9410
|
-
const
|
|
9411
|
-
|
|
9412
|
-
|
|
9472
|
+
const routeOptions = {
|
|
9473
|
+
updateCheck: options.routes?.updateCheck ?? true,
|
|
9474
|
+
bundles: options.routes?.bundles ?? false
|
|
9475
|
+
};
|
|
9413
9476
|
const router = createRouter();
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
}
|
|
9417
|
-
if (updateCheckEnabled) {
|
|
9477
|
+
addRoute(router, "GET", "/version", "version");
|
|
9478
|
+
if (routeOptions.updateCheck) {
|
|
9418
9479
|
addRoute(
|
|
9419
9480
|
router,
|
|
9420
9481
|
"GET",
|
|
@@ -9440,7 +9501,7 @@ function createHandler(api, options = {}) {
|
|
|
9440
9501
|
"appVersionUpdateWithCohort"
|
|
9441
9502
|
);
|
|
9442
9503
|
}
|
|
9443
|
-
if (
|
|
9504
|
+
if (routeOptions.bundles) {
|
|
9444
9505
|
addRoute(router, "GET", "/api/bundles/channels", "getChannels");
|
|
9445
9506
|
addRoute(router, "GET", "/api/bundles/:id", "getBundle");
|
|
9446
9507
|
addRoute(router, "GET", "/api/bundles", "getBundles");
|
|
@@ -9461,7 +9522,8 @@ function createHandler(api, options = {}) {
|
|
|
9461
9522
|
headers: { "Content-Type": "application/json" }
|
|
9462
9523
|
});
|
|
9463
9524
|
}
|
|
9464
|
-
const
|
|
9525
|
+
const routeName = match2.data;
|
|
9526
|
+
const handler = routes[routeName];
|
|
9465
9527
|
if (!handler) {
|
|
9466
9528
|
return new Response(JSON.stringify({ error: "Handler not found" }), {
|
|
9467
9529
|
status: 500,
|
|
@@ -12318,7 +12380,6 @@ var hotUpdater = createHotUpdater({
|
|
|
12318
12380
|
basePath: HOT_UPDATER_BASE_PATH,
|
|
12319
12381
|
routes: {
|
|
12320
12382
|
updateCheck: true,
|
|
12321
|
-
version: true,
|
|
12322
12383
|
bundles: false
|
|
12323
12384
|
}
|
|
12324
12385
|
});
|