@opennextjs/cloudflare 1.19.10 → 1.20.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/api/cloudflare-context.d.ts +2 -0
- package/dist/api/durable-objects/bucket-cache-purge.js +3 -3
- package/dist/cli/commands/deploy.d.ts +1 -0
- package/dist/cli/commands/deploy.js +1 -0
- package/dist/cli/commands/populate-cache.d.ts +12 -1
- package/dist/cli/commands/populate-cache.js +133 -6
- package/dist/cli/commands/preview.d.ts +1 -0
- package/dist/cli/commands/preview.js +1 -0
- package/dist/cli/commands/skew-protection.js +9 -0
- package/dist/cli/commands/upload.d.ts +1 -0
- package/dist/cli/commands/upload.js +1 -0
- package/dist/cli/utils/ensure-r2-bucket.js +9 -2
- package/package.json +9 -1
|
@@ -32,6 +32,8 @@ declare global {
|
|
|
32
32
|
CF_PREVIEW_DOMAIN?: string;
|
|
33
33
|
CF_WORKERS_SCRIPTS_API_TOKEN?: string;
|
|
34
34
|
CF_ACCOUNT_ID?: string;
|
|
35
|
+
R2_ACCESS_KEY_ID?: string;
|
|
36
|
+
R2_SECRET_ACCESS_KEY?: string;
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
export type CloudflareContext<CfProperties extends Record<string, unknown> = IncomingRequestCfProperties, Context = ExecutionContext> = {
|
|
@@ -25,7 +25,7 @@ export class BucketCachePurge extends DurableObject {
|
|
|
25
25
|
// Insert the tag into the sql table
|
|
26
26
|
this.ctx.storage.sql.exec(`
|
|
27
27
|
INSERT OR REPLACE INTO cache_purge (tag)
|
|
28
|
-
VALUES (?)`,
|
|
28
|
+
VALUES (?)`, tag);
|
|
29
29
|
}
|
|
30
30
|
const nextAlarm = await this.ctx.storage.getAlarm();
|
|
31
31
|
if (!nextAlarm) {
|
|
@@ -57,7 +57,7 @@ export class BucketCachePurge extends DurableObject {
|
|
|
57
57
|
this.ctx.storage.sql.exec(`
|
|
58
58
|
DELETE FROM cache_purge
|
|
59
59
|
WHERE tag IN (${tags.map(() => "?").join(",")})
|
|
60
|
-
`, tags.map((row) => row.tag));
|
|
60
|
+
`, ...tags.map((row) => row.tag));
|
|
61
61
|
if (tags.length < MAX_NUMBER_OF_TAGS_PER_PURGE) {
|
|
62
62
|
// If we have less than MAX_NUMBER_OF_TAGS_PER_PURGE tags, we can stop
|
|
63
63
|
tags = [];
|
|
@@ -70,6 +70,6 @@ export class BucketCachePurge extends DurableObject {
|
|
|
70
70
|
`)
|
|
71
71
|
.toArray();
|
|
72
72
|
}
|
|
73
|
-
} while (tags.length
|
|
73
|
+
} while (tags.length > 0);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -24,6 +24,7 @@ export async function deployCommand(args) {
|
|
|
24
24
|
environment: args.env,
|
|
25
25
|
wranglerConfigPath: args.wranglerConfigPath,
|
|
26
26
|
cacheChunkSize: args.cacheChunkSize,
|
|
27
|
+
useRclone: args.rclone,
|
|
27
28
|
shouldUsePreviewId: false,
|
|
28
29
|
}, envVars);
|
|
29
30
|
const deploymentMapping = await getDeploymentMapping(buildOpts, config, envVars);
|
|
@@ -32,16 +32,25 @@ export type PopulateCacheOptions = {
|
|
|
32
32
|
/**
|
|
33
33
|
* Number of concurrent requests when populating the cache.
|
|
34
34
|
* For KV this is the chunk size passed to `wrangler kv bulk put`.
|
|
35
|
-
* For R2 this is the number of concurrent requests to the local worker.
|
|
35
|
+
* For R2 this is the number of concurrent requests to the local worker or rclone transfers.
|
|
36
36
|
*
|
|
37
37
|
* @default 25
|
|
38
38
|
*/
|
|
39
39
|
cacheChunkSize?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Whether to use `rclone` instead of the worker-based R2 cache population path.
|
|
42
|
+
*/
|
|
43
|
+
useRclone?: boolean;
|
|
40
44
|
/**
|
|
41
45
|
* Instructs Wrangler to use the preview namespace or ID defined in the Wrangler config for the remote target.
|
|
42
46
|
*/
|
|
43
47
|
shouldUsePreviewId: boolean;
|
|
44
48
|
};
|
|
49
|
+
export declare function loadRclone(importRclone?: () => Promise<{
|
|
50
|
+
default: typeof import("rclone.js");
|
|
51
|
+
}>): Promise<((...args: Array<string | object>) => import("child_process").ChildProcess) & Record<"test" | "checksum" | "link" | "version" | "config" | "copy" | "delete" | "about" | "authorize" | "backend" | "cat" | "check" | "cleanup" | "config create" | "config delete" | "config disconnect" | "config dump" | "config edit" | "config file" | "config password" | "config providers" | "config reconnect" | "config show" | "config update" | "config userinfo" | "copyto" | "copyurl" | "cryptcheck" | "cryptdecode" | "dedupe" | "deletefile" | "genautocomplete" | "genautocomplete bash" | "genautocomplete fish" | "genautocomplete zsh" | "gendocs" | "hashsum" | "help" | "listremotes" | "ls" | "lsd" | "lsf" | "lsjson" | "lsl" | "md5sum" | "mkdir" | "mount" | "move" | "moveto" | "ncdu" | "obscure" | "purge" | "rc" | "rcat" | "rcd" | "rmdir" | "rmdirs" | "selfupdate" | "serve" | "serve dlna" | "serve ftp" | "serve http" | "serve restic" | "serve sftp" | "serve webdav" | "settier" | "sha1sum" | "size" | "sync" | "touch" | "tree", (...args: Array<string | object>) => import("child_process").ChildProcess> & {
|
|
52
|
+
promises: ((...args: Array<string | object>) => Promise<Buffer<ArrayBufferLike>>) & Record<"test" | "checksum" | "link" | "version" | "config" | "copy" | "delete" | "about" | "authorize" | "backend" | "cat" | "check" | "cleanup" | "config create" | "config delete" | "config disconnect" | "config dump" | "config edit" | "config file" | "config password" | "config providers" | "config reconnect" | "config show" | "config update" | "config userinfo" | "copyto" | "copyurl" | "cryptcheck" | "cryptdecode" | "dedupe" | "deletefile" | "genautocomplete" | "genautocomplete bash" | "genautocomplete fish" | "genautocomplete zsh" | "gendocs" | "hashsum" | "help" | "listremotes" | "ls" | "lsd" | "lsf" | "lsjson" | "lsl" | "md5sum" | "mkdir" | "mount" | "move" | "moveto" | "ncdu" | "obscure" | "purge" | "rc" | "rcat" | "rcd" | "rmdir" | "rmdirs" | "selfupdate" | "serve" | "serve dlna" | "serve ftp" | "serve http" | "serve restic" | "serve sftp" | "serve webdav" | "settier" | "sha1sum" | "size" | "sync" | "touch" | "tree", (...args: Array<string | object>) => Promise<Buffer<ArrayBufferLike>>>;
|
|
53
|
+
}>;
|
|
45
54
|
/**
|
|
46
55
|
* Add the `populateCache` command to yargs configuration, with nested commands for `local` and `remote`.
|
|
47
56
|
*
|
|
@@ -56,4 +65,6 @@ export declare function withPopulateCacheOptions<T extends yargs.Argv>(args: T):
|
|
|
56
65
|
env: string | undefined;
|
|
57
66
|
} & {
|
|
58
67
|
cacheChunkSize: number | undefined;
|
|
68
|
+
} & {
|
|
69
|
+
rclone: boolean;
|
|
59
70
|
}>;
|
|
@@ -46,6 +46,7 @@ async function populateCacheCommand(target, args) {
|
|
|
46
46
|
environment: args.env,
|
|
47
47
|
wranglerConfigPath: args.wranglerConfigPath,
|
|
48
48
|
cacheChunkSize: args.cacheChunkSize,
|
|
49
|
+
useRclone: args.rclone,
|
|
49
50
|
shouldUsePreviewId: false,
|
|
50
51
|
}, envVars);
|
|
51
52
|
}
|
|
@@ -150,6 +151,13 @@ async function populateR2IncrementalCache(buildOpts, config, populateCacheOption
|
|
|
150
151
|
logger.info("No cache assets to populate");
|
|
151
152
|
return;
|
|
152
153
|
}
|
|
154
|
+
if (populateCacheOptions.useRclone) {
|
|
155
|
+
if (populateCacheOptions.target !== "remote") {
|
|
156
|
+
throw new Error("The `--rclone` option can only be used when populating a remote R2 cache.");
|
|
157
|
+
}
|
|
158
|
+
await populateR2IncrementalCacheWithRclone(bucketName, prefix, assets, envVars, populateCacheOptions.cacheChunkSize);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
153
161
|
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
154
162
|
const handlerPath = path.join(currentDir, "../workers/r2-cache.js");
|
|
155
163
|
const isRemote = populateCacheOptions.target === "remote";
|
|
@@ -204,6 +212,113 @@ async function populateR2IncrementalCache(buildOpts, config, populateCacheOption
|
|
|
204
212
|
}
|
|
205
213
|
logger.info(`Successfully populated cache with ${assets.length} entries`);
|
|
206
214
|
}
|
|
215
|
+
async function populateR2IncrementalCacheWithRclone(bucketName, prefix, assets, envVars, cacheChunkSize) {
|
|
216
|
+
const accessKey = envVars.R2_ACCESS_KEY_ID;
|
|
217
|
+
const secretKey = envVars.R2_SECRET_ACCESS_KEY;
|
|
218
|
+
const accountId = envVars.CF_ACCOUNT_ID;
|
|
219
|
+
if (!accessKey || !secretKey || !accountId) {
|
|
220
|
+
throw new Error("R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, and CF_ACCOUNT_ID must be provided to use `rclone`");
|
|
221
|
+
}
|
|
222
|
+
const rclone = await loadRclone();
|
|
223
|
+
logger.info("\nPopulating remote R2 incremental cache using `rclone`...");
|
|
224
|
+
const configDir = await fsp.mkdtemp(path.join(os.tmpdir(), "rclone-config-"));
|
|
225
|
+
const configPath = path.join(configDir, "rclone.conf");
|
|
226
|
+
const stagingDir = await fsp.mkdtemp(path.join(os.tmpdir(), "r2-staging-"));
|
|
227
|
+
const transfers = Math.max(1, cacheChunkSize ?? 25);
|
|
228
|
+
const checkers = Math.max(1, Math.floor(transfers / 2));
|
|
229
|
+
try {
|
|
230
|
+
await fsp.writeFile(configPath, `[r2]\ntype = s3\nprovider = Cloudflare\naccess_key_id = ${accessKey}\nsecret_access_key = ${secretKey}\nendpoint = https://${accountId}.r2.cloudflarestorage.com\nacl = private\n`, { mode: 0o600 });
|
|
231
|
+
const rcloneEnv = {
|
|
232
|
+
...process.env,
|
|
233
|
+
RCLONE_CONFIG: configPath,
|
|
234
|
+
};
|
|
235
|
+
await ensureRcloneExecutable(rclone, rcloneEnv);
|
|
236
|
+
await stageCacheAssets(assets, stagingDir, prefix, transfers);
|
|
237
|
+
await runRcloneCopy(rclone, stagingDir, `r2:${bucketName}`, {
|
|
238
|
+
progress: true,
|
|
239
|
+
transfers,
|
|
240
|
+
checkers,
|
|
241
|
+
env: rcloneEnv,
|
|
242
|
+
stdio: "inherit",
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
await Promise.allSettled([
|
|
247
|
+
fsp.rm(stagingDir, { recursive: true, force: true }),
|
|
248
|
+
fsp.rm(configDir, { recursive: true, force: true }),
|
|
249
|
+
]);
|
|
250
|
+
}
|
|
251
|
+
logger.info(`Successfully populated cache with ${assets.length} entries`);
|
|
252
|
+
}
|
|
253
|
+
async function runRcloneCopy(rclone, source, destination, options) {
|
|
254
|
+
await new Promise((resolve, reject) => {
|
|
255
|
+
const child = rclone.copy(source, destination, options);
|
|
256
|
+
child.once("error", reject);
|
|
257
|
+
child.once("close", (code, signal) => {
|
|
258
|
+
if (code === 0) {
|
|
259
|
+
resolve();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
reject(new Error(`rclone exited ${signal ? `after receiving signal ${signal}` : `with code ${code ?? "unknown"}`}`));
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
async function stageCacheAssets(assets, stagingDir, prefix, maxConcurrency) {
|
|
267
|
+
const pending = new Set();
|
|
268
|
+
for (const asset of assets) {
|
|
269
|
+
const task = stageCacheAsset(asset, stagingDir, prefix).finally(() => pending.delete(task));
|
|
270
|
+
pending.add(task);
|
|
271
|
+
if (pending.size >= maxConcurrency) {
|
|
272
|
+
await Promise.race(pending);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
await Promise.all(pending);
|
|
276
|
+
}
|
|
277
|
+
async function stageCacheAsset(asset, stagingDir, prefix) {
|
|
278
|
+
const cacheKey = computeCacheKey(asset.key, {
|
|
279
|
+
prefix,
|
|
280
|
+
buildId: asset.buildId,
|
|
281
|
+
cacheType: asset.isFetch ? "fetch" : "cache",
|
|
282
|
+
});
|
|
283
|
+
const destination = path.resolve(stagingDir, cacheKey);
|
|
284
|
+
const relativeDestination = path.relative(stagingDir, destination);
|
|
285
|
+
if (relativeDestination.startsWith(`..${path.sep}`) || path.isAbsolute(relativeDestination)) {
|
|
286
|
+
throw new Error(`Cannot stage R2 cache key outside the temporary directory: ${JSON.stringify(cacheKey)}`);
|
|
287
|
+
}
|
|
288
|
+
await fsp.mkdir(path.dirname(destination), { recursive: true });
|
|
289
|
+
try {
|
|
290
|
+
await fsp.link(asset.fullPath, destination);
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "EXDEV") {
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
await fsp.copyFile(asset.fullPath, destination);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function ensureRcloneExecutable(rclone, env) {
|
|
300
|
+
try {
|
|
301
|
+
await rclone.promises.version({ env });
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
305
|
+
throw new Error("The `rclone.js` executable is unavailable. pnpm users must allow its install script with `pnpm approve-builds`, select `rclone.js`, then run `pnpm rebuild rclone.js`.");
|
|
306
|
+
}
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
export async function loadRclone(importRclone = () => import("rclone.js")) {
|
|
311
|
+
try {
|
|
312
|
+
return (await importRclone()).default;
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
if (error instanceof Error &&
|
|
316
|
+
("code" in error ? error.code === "ERR_MODULE_NOT_FOUND" : error.message.includes("rclone.js"))) {
|
|
317
|
+
throw new Error("The `--rclone` option requires the optional `rclone.js` peer dependency. Install it in your project before using this option.");
|
|
318
|
+
}
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
207
322
|
/**
|
|
208
323
|
* Sends cache entries to the R2 worker, one entry per request.
|
|
209
324
|
*
|
|
@@ -262,11 +377,17 @@ async function sendEntryToR2Worker(options) {
|
|
|
262
377
|
"x-opennext-cache-key": key,
|
|
263
378
|
"content-length": fs.statSync(filename).size.toString(),
|
|
264
379
|
// Include Access Client ID and Secret if they are set in the environment,
|
|
265
|
-
//
|
|
380
|
+
// so the helper worker can be reached through Cloudflare Access.
|
|
266
381
|
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
382
|
+
// If the workers.dev subdomain (or a parent route) is behind Cloudflare Access,
|
|
383
|
+
// attach a "Service Auth" policy to the *existing* Access application that already
|
|
384
|
+
// covers "open-next-cache-populate.<account>.workers.dev" — typically the
|
|
385
|
+
// "*.<account>.workers.dev" wildcard application. Creating a separate application
|
|
386
|
+
// scoped to this hostname has been observed to block the upload, even alongside
|
|
387
|
+
// the wildcard app. The policy should have:
|
|
388
|
+
// - Action set to "Service Auth"
|
|
389
|
+
// - An Include rule for "Any Access Service Token" or a specific Service Token
|
|
390
|
+
// See: https://opennext.js.org/cloudflare/cli#populating-remote-bindings-when-workers-are-protected-by-cloudflare-access
|
|
270
391
|
...(process.env.CLOUDFLARE_ACCESS_CLIENT_ID && process.env.CLOUDFLARE_ACCESS_CLIENT_SECRET
|
|
271
392
|
? {
|
|
272
393
|
"CF-Access-Client-Id": process.env.CLOUDFLARE_ACCESS_CLIENT_ID,
|
|
@@ -425,8 +546,14 @@ export function addPopulateCacheCommand(y) {
|
|
|
425
546
|
.demandCommand(1, 1));
|
|
426
547
|
}
|
|
427
548
|
export function withPopulateCacheOptions(args) {
|
|
428
|
-
return withWranglerOptions(args)
|
|
549
|
+
return withWranglerOptions(args)
|
|
550
|
+
.options("cacheChunkSize", {
|
|
429
551
|
type: "number",
|
|
430
|
-
desc: "Number of
|
|
552
|
+
desc: "Number of concurrent cache entries to process",
|
|
553
|
+
})
|
|
554
|
+
.options("rclone", {
|
|
555
|
+
type: "boolean",
|
|
556
|
+
default: false,
|
|
557
|
+
desc: "Use rclone to populate a remote R2 incremental cache",
|
|
431
558
|
});
|
|
432
559
|
}
|
|
@@ -8,6 +8,7 @@ import type { WithWranglerArgs } from "./utils/utils.js";
|
|
|
8
8
|
export declare function previewCommand(args: WithWranglerArgs<{
|
|
9
9
|
cacheChunkSize?: number;
|
|
10
10
|
remote: boolean;
|
|
11
|
+
rclone: boolean;
|
|
11
12
|
}>): Promise<void>;
|
|
12
13
|
/**
|
|
13
14
|
* Add the `preview` command to yargs configuration.
|
|
@@ -22,6 +22,7 @@ export async function previewCommand(args) {
|
|
|
22
22
|
environment: args.env,
|
|
23
23
|
wranglerConfigPath: args.wranglerConfigPath,
|
|
24
24
|
cacheChunkSize: args.cacheChunkSize,
|
|
25
|
+
useRclone: args.rclone,
|
|
25
26
|
shouldUsePreviewId: args.remote,
|
|
26
27
|
}, envVars);
|
|
27
28
|
const result = runWrangler(buildOpts, ["dev", ...args.wranglerArgs], { logging: "all" });
|
|
@@ -31,6 +31,8 @@ const MAX_NUMBER_OF_VERSIONS = 20;
|
|
|
31
31
|
/** Maximum age of versions to list */
|
|
32
32
|
const MAX_VERSION_AGE_DAYS = 7;
|
|
33
33
|
const MS_PER_DAY = 24 * 3600 * 1000;
|
|
34
|
+
/** Worker-version trigger types that produce a full upload (assets + code). */
|
|
35
|
+
const UPLOAD_TRIGGER_TYPES = new Set(["upload", "version_upload"]);
|
|
34
36
|
/**
|
|
35
37
|
* Compute the deployment mapping for a deployment.
|
|
36
38
|
*
|
|
@@ -174,11 +176,18 @@ export async function listWorkerVersions(scriptName, options) {
|
|
|
174
176
|
})) {
|
|
175
177
|
const id = version.id;
|
|
176
178
|
const createdOn = version.metadata?.created_on;
|
|
179
|
+
const triggeredBy = version.metadata?.annotations?.["workers/triggered_by"];
|
|
177
180
|
if (id && createdOn) {
|
|
178
181
|
const createdOnMs = new Date(createdOn).getTime();
|
|
179
182
|
if (createdOnMs < afterTimeMs) {
|
|
180
183
|
break;
|
|
181
184
|
}
|
|
185
|
+
// Skip metadata-only versions (e.g. secret/service_token triggers)
|
|
186
|
+
// that lack the static assets bundle. Versions with no annotation
|
|
187
|
+
// are kept for backward compatibility.
|
|
188
|
+
if (triggeredBy !== undefined && !UPLOAD_TRIGGER_TYPES.has(triggeredBy)) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
182
191
|
versions.push({ id, createdOnMs });
|
|
183
192
|
if (versions.length >= maxNumberOfVersions) {
|
|
184
193
|
break;
|
|
@@ -25,6 +25,7 @@ export async function uploadCommand(args) {
|
|
|
25
25
|
environment: args.env,
|
|
26
26
|
wranglerConfigPath: args.wranglerConfigPath,
|
|
27
27
|
cacheChunkSize: args.cacheChunkSize,
|
|
28
|
+
useRclone: args.rclone,
|
|
28
29
|
shouldUsePreviewId: false,
|
|
29
30
|
}, envVars);
|
|
30
31
|
const result = runWrangler(buildOpts, [
|
|
@@ -106,8 +106,15 @@ export async function ensureR2Bucket(projectDir, bucketName, jurisdiction) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
const client = authCredentials.type === "api_key"
|
|
109
|
-
? new Cloudflare({
|
|
110
|
-
|
|
109
|
+
? new Cloudflare({
|
|
110
|
+
apiKey: authCredentials.apiKey,
|
|
111
|
+
apiEmail: authCredentials.apiEmail,
|
|
112
|
+
defaultHeaders: { "Accept-Encoding": "identity" },
|
|
113
|
+
})
|
|
114
|
+
: new Cloudflare({
|
|
115
|
+
apiToken: authCredentials.token,
|
|
116
|
+
defaultHeaders: { "Accept-Encoding": "identity" },
|
|
117
|
+
});
|
|
111
118
|
const accountId = await getAccountId(client);
|
|
112
119
|
if (!accountId) {
|
|
113
120
|
return { success: false, error: "Could not determine Cloudflare account ID" };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opennextjs/cloudflare",
|
|
3
3
|
"description": "Cloudflare builder for next apps",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.20.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"opennextjs-cloudflare": "dist/cli/index.js"
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"@types/mock-fs": "^4.13.4",
|
|
61
61
|
"@types/node": "^22.12.0",
|
|
62
62
|
"@types/picomatch": "^4.0.0",
|
|
63
|
+
"@types/rclone.js": "^0.6.1",
|
|
63
64
|
"@types/yargs": "^17.0.33",
|
|
64
65
|
"diff": "^8.0.2",
|
|
65
66
|
"esbuild": "^0.27.0",
|
|
@@ -71,6 +72,7 @@
|
|
|
71
72
|
"mock-fs": "^5.4.1",
|
|
72
73
|
"next": "^15.5.18",
|
|
73
74
|
"picomatch": "^4.0.2",
|
|
75
|
+
"rclone.js": "^0.6.6",
|
|
74
76
|
"rimraf": "^6.0.1",
|
|
75
77
|
"typescript": "^5.9.3",
|
|
76
78
|
"typescript-eslint": "^8.48.0",
|
|
@@ -78,8 +80,14 @@
|
|
|
78
80
|
},
|
|
79
81
|
"peerDependencies": {
|
|
80
82
|
"next": ">=15.5.18 <16 || >=16.2.6",
|
|
83
|
+
"rclone.js": "^0.6.6",
|
|
81
84
|
"wrangler": "^4.86.0"
|
|
82
85
|
},
|
|
86
|
+
"peerDependenciesMeta": {
|
|
87
|
+
"rclone.js": {
|
|
88
|
+
"optional": true
|
|
89
|
+
}
|
|
90
|
+
},
|
|
83
91
|
"scripts": {
|
|
84
92
|
"clean": "rimraf dist",
|
|
85
93
|
"build": "pnpm clean && tsc",
|