alchemy 0.63.1 → 0.65.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/bin/alchemy.js +7664 -5857
- package/bin/commands/init.ts +130 -9
- package/bin/constants.ts +4 -0
- package/bin/services/cdp-manager/cdp-proxy.ts +69 -0
- package/bin/services/cdp-manager/server.ts +173 -0
- package/bin/services/execute-alchemy.ts +83 -4
- package/bin/types.ts +1 -0
- package/lib/alchemy.d.ts +4 -0
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +10 -4
- package/lib/alchemy.js.map +1 -1
- package/lib/apply.d.ts +3 -0
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +11 -1
- package/lib/apply.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +3 -1
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bucket.d.ts +2 -1
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/bundle/cloudflare-internal.d.ts +6 -0
- package/lib/cloudflare/bundle/cloudflare-internal.d.ts.map +1 -0
- package/lib/cloudflare/bundle/cloudflare-internal.js +39 -0
- package/lib/cloudflare/bundle/cloudflare-internal.js.map +1 -0
- package/lib/cloudflare/bundle/nodejs-builtin-modules.d.ts +2 -0
- package/lib/cloudflare/bundle/nodejs-builtin-modules.d.ts.map +1 -0
- package/lib/cloudflare/bundle/nodejs-builtin-modules.js +9 -0
- package/lib/cloudflare/bundle/nodejs-builtin-modules.js.map +1 -0
- package/lib/cloudflare/bundle/plugin-als-external.d.ts +6 -0
- package/lib/cloudflare/bundle/plugin-als-external.d.ts.map +1 -0
- package/lib/cloudflare/bundle/plugin-als-external.js +12 -0
- package/lib/cloudflare/bundle/plugin-als-external.js.map +1 -0
- package/lib/cloudflare/bundle/plugin-hybrid-node-compat.d.ts +4 -1
- package/lib/cloudflare/bundle/plugin-hybrid-node-compat.d.ts.map +1 -1
- package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js +23 -23
- package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js.map +1 -1
- package/lib/cloudflare/bundle/plugin-node-compat.d.ts +11 -0
- package/lib/cloudflare/bundle/plugin-node-compat.d.ts.map +1 -0
- package/lib/cloudflare/bundle/plugin-node-compat.js +93 -0
- package/lib/cloudflare/bundle/plugin-node-compat.js.map +1 -0
- package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -1
- package/lib/cloudflare/bundle/plugin-wasm.js +20 -11
- package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -1
- package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
- package/lib/cloudflare/compatibility-date.gen.js +1 -1
- package/lib/cloudflare/d1-local-migrations.js.map +1 -1
- package/lib/cloudflare/durable-object-namespace.d.ts +1 -0
- package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -1
- package/lib/cloudflare/durable-object-namespace.js.map +1 -1
- package/lib/cloudflare/hyperdrive.d.ts +7 -2
- package/lib/cloudflare/hyperdrive.d.ts.map +1 -1
- package/lib/cloudflare/hyperdrive.js +39 -57
- package/lib/cloudflare/hyperdrive.js.map +1 -1
- package/lib/cloudflare/index.d.ts +1 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +1 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/next.d.ts +13 -0
- package/lib/cloudflare/next.d.ts.map +1 -0
- package/lib/cloudflare/next.js +91 -0
- package/lib/cloudflare/next.js.map +1 -0
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +80 -82
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker-assets.d.ts +3 -3
- package/lib/cloudflare/worker-assets.d.ts.map +1 -1
- package/lib/cloudflare/worker-assets.js +22 -47
- package/lib/cloudflare/worker-assets.js.map +1 -1
- package/lib/cloudflare/worker-bundle.d.ts +6 -0
- package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
- package/lib/cloudflare/worker-bundle.js +34 -10
- package/lib/cloudflare/worker-bundle.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +1 -0
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +24 -6
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +4 -6
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +20 -15
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/context.d.ts +3 -1
- package/lib/context.d.ts.map +1 -1
- package/lib/context.js +2 -2
- package/lib/context.js.map +1 -1
- package/lib/destroy.d.ts +8 -0
- package/lib/destroy.d.ts.map +1 -1
- package/lib/destroy.js +20 -4
- package/lib/destroy.js.map +1 -1
- package/lib/planetscale/api/client.gen.d.ts +1750 -0
- package/lib/planetscale/api/client.gen.d.ts.map +1 -0
- package/lib/planetscale/api/client.gen.js +2401 -0
- package/lib/planetscale/api/client.gen.js.map +1 -0
- package/lib/planetscale/api/index.d.ts +3 -0
- package/lib/planetscale/api/index.d.ts.map +1 -0
- package/lib/planetscale/api/index.js +4 -0
- package/lib/planetscale/api/index.js.map +1 -0
- package/lib/planetscale/api/types.gen.d.ts +12156 -0
- package/lib/planetscale/api/types.gen.d.ts.map +1 -0
- package/lib/planetscale/api/types.gen.js +2 -0
- package/lib/planetscale/api/types.gen.js.map +1 -0
- package/lib/planetscale/branch.d.ts +5 -8
- package/lib/planetscale/branch.d.ts.map +1 -1
- package/lib/planetscale/branch.js +122 -97
- package/lib/planetscale/branch.js.map +1 -1
- package/lib/planetscale/database.d.ts +24 -11
- package/lib/planetscale/database.d.ts.map +1 -1
- package/lib/planetscale/database.js +163 -118
- package/lib/planetscale/database.js.map +1 -1
- package/lib/planetscale/index.d.ts +2 -1
- package/lib/planetscale/index.d.ts.map +1 -1
- package/lib/planetscale/index.js +2 -1
- package/lib/planetscale/index.js.map +1 -1
- package/lib/planetscale/password.d.ts +7 -8
- package/lib/planetscale/password.d.ts.map +1 -1
- package/lib/planetscale/password.js +74 -57
- package/lib/planetscale/password.js.map +1 -1
- package/lib/planetscale/role.d.ts +126 -0
- package/lib/planetscale/role.d.ts.map +1 -0
- package/lib/planetscale/role.js +144 -0
- package/lib/planetscale/role.js.map +1 -0
- package/lib/planetscale/utils.d.ts +23 -7
- package/lib/planetscale/utils.d.ts.map +1 -1
- package/lib/planetscale/utils.js +169 -75
- package/lib/planetscale/utils.js.map +1 -1
- package/lib/scope.d.ts +5 -1
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +11 -3
- package/lib/scope.js.map +1 -1
- package/lib/secret.d.ts +0 -3
- package/lib/secret.d.ts.map +1 -1
- package/lib/secret.js +0 -2
- package/lib/secret.js.map +1 -1
- package/lib/state/cloudflare-state-store.js +3 -3
- package/lib/state/cloudflare-state-store.js.map +1 -1
- package/lib/test/bun.d.ts +2 -2
- package/lib/test/bun.d.ts.map +1 -1
- package/lib/test/vitest.d.ts +2 -2
- package/lib/test/vitest.d.ts.map +1 -1
- package/lib/test/vitest.js +4 -4
- package/lib/test/vitest.js.map +1 -1
- package/lib/util/content-type.js +2 -2
- package/lib/util/content-type.js.map +1 -1
- package/lib/util/dedent.d.ts +3 -14
- package/lib/util/dedent.d.ts.map +1 -1
- package/lib/util/dedent.js +33 -45
- package/lib/util/dedent.js.map +1 -1
- package/lib/util/memoize.d.ts.map +1 -1
- package/lib/util/memoize.js +11 -1
- package/lib/util/memoize.js.map +1 -1
- package/package.json +8 -3
- package/src/alchemy.ts +14 -6
- package/src/apply.ts +13 -1
- package/src/cloudflare/bindings.ts +3 -1
- package/src/cloudflare/bucket.ts +3 -1
- package/src/cloudflare/bundle/cloudflare-internal.ts +42 -0
- package/src/cloudflare/bundle/nodejs-builtin-modules.ts +14 -0
- package/src/cloudflare/bundle/plugin-als-external.ts +13 -0
- package/src/cloudflare/bundle/plugin-hybrid-node-compat.ts +29 -28
- package/src/cloudflare/bundle/plugin-node-compat.ts +114 -0
- package/src/cloudflare/bundle/plugin-wasm.ts +34 -14
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/d1-local-migrations.ts +2 -2
- package/src/cloudflare/durable-object-namespace.ts +1 -0
- package/src/cloudflare/hyperdrive.ts +136 -95
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/next.ts +123 -0
- package/src/cloudflare/website.ts +88 -89
- package/src/cloudflare/worker-assets.ts +43 -105
- package/src/cloudflare/worker-bundle.ts +46 -9
- package/src/cloudflare/worker-metadata.ts +2 -0
- package/src/cloudflare/worker.ts +35 -7
- package/src/cloudflare/wrangler.json.ts +114 -113
- package/src/context.ts +5 -3
- package/src/destroy.ts +26 -8
- package/src/planetscale/api/client.gen.ts +2882 -0
- package/src/planetscale/api/index.ts +4 -0
- package/src/planetscale/api/types.gen.ts +13041 -0
- package/src/planetscale/branch.ts +170 -162
- package/src/planetscale/database.ts +263 -243
- package/src/planetscale/index.ts +2 -1
- package/src/planetscale/password.ts +89 -86
- package/src/planetscale/role.ts +278 -0
- package/src/planetscale/utils.ts +289 -128
- package/src/scope.ts +12 -3
- package/src/secret.ts +0 -4
- package/src/state/cloudflare-state-store.ts +4 -4
- package/src/test/bun.ts +2 -2
- package/src/test/vitest.ts +6 -6
- package/src/util/content-type.ts +2 -2
- package/src/util/dedent.ts +34 -50
- package/src/util/memoize.ts +13 -1
- package/templates/astro/alchemy.run.ts +1 -3
- package/templates/hono/alchemy.run.ts +0 -1
- package/templates/nextjs/README.md +85 -0
- package/templates/nextjs/_env +1 -0
- package/templates/nextjs/_env.example +1 -0
- package/templates/nextjs/_gitignore +46 -0
- package/templates/nextjs/alchemy.run.ts +16 -0
- package/templates/nextjs/eslint.config.mjs +16 -0
- package/templates/nextjs/next-env.d.ts +5 -0
- package/templates/nextjs/next.config.ts +10 -0
- package/templates/nextjs/open-next.config.ts +5 -0
- package/templates/nextjs/package.json +31 -0
- package/templates/nextjs/src/app/api/hello/route.ts +19 -0
- package/templates/nextjs/src/app/favicon.ico +0 -0
- package/templates/nextjs/src/app/globals.css +72 -0
- package/templates/nextjs/src/app/layout.tsx +19 -0
- package/templates/nextjs/src/app/page.tsx +55 -0
- package/templates/nextjs/tsconfig.json +33 -0
- package/templates/nextjs/types/env.d.ts +14 -0
- package/templates/nuxt/alchemy.run.ts +1 -3
- package/templates/react-router/alchemy.run.ts +1 -3
- package/templates/rwsdk/alchemy.run.ts +0 -2
- package/templates/sveltekit/alchemy.run.ts +1 -3
- package/templates/tanstack-start/alchemy.run.ts +1 -3
- package/templates/typescript/alchemy.run.ts +0 -1
- package/templates/vite/alchemy.run.ts +0 -1
- package/workers/cloudflare-state-store.js +58 -58
- package/lib/cloudflare/bundle/externals.d.ts +0 -3
- package/lib/cloudflare/bundle/externals.d.ts.map +0 -1
- package/lib/cloudflare/bundle/externals.js +0 -35
- package/lib/cloudflare/bundle/externals.js.map +0 -1
- package/lib/cloudflare/bundle/plugin-compat-warning.d.ts +0 -10
- package/lib/cloudflare/bundle/plugin-compat-warning.d.ts.map +0 -1
- package/lib/cloudflare/bundle/plugin-compat-warning.js +0 -67
- package/lib/cloudflare/bundle/plugin-compat-warning.js.map +0 -1
- package/lib/planetscale/api.d.ts +0 -53
- package/lib/planetscale/api.d.ts.map +0 -1
- package/lib/planetscale/api.js +0 -84
- package/lib/planetscale/api.js.map +0 -1
- package/src/cloudflare/bundle/externals.ts +0 -36
- package/src/cloudflare/bundle/plugin-compat-warning.ts +0 -94
- package/src/planetscale/api.ts +0 -115
|
@@ -4,8 +4,16 @@ import { Resource } from "../resource.ts";
|
|
|
4
4
|
import { Scope } from "../scope.ts";
|
|
5
5
|
import { Secret } from "../secret.ts";
|
|
6
6
|
import { logger } from "../util/logger.ts";
|
|
7
|
-
import { handleApiError } from "./api-error.ts";
|
|
8
|
-
import {
|
|
7
|
+
import { CloudflareApiError, handleApiError } from "./api-error.ts";
|
|
8
|
+
import {
|
|
9
|
+
extractCloudflareResult,
|
|
10
|
+
type CloudflareApiErrorPayload,
|
|
11
|
+
} from "./api-response.ts";
|
|
12
|
+
import {
|
|
13
|
+
createCloudflareApi,
|
|
14
|
+
type CloudflareApi,
|
|
15
|
+
type CloudflareApiOptions,
|
|
16
|
+
} from "./api.ts";
|
|
9
17
|
|
|
10
18
|
/**
|
|
11
19
|
* Origin configuration for a PostgreSQL or MySQL database connection
|
|
@@ -163,6 +171,12 @@ export interface HyperdriveProps extends CloudflareApiOptions {
|
|
|
163
171
|
*/
|
|
164
172
|
hyperdriveId?: string;
|
|
165
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Whether to adopt an existing hyperdrive config
|
|
176
|
+
* @default false
|
|
177
|
+
*/
|
|
178
|
+
adopt?: boolean;
|
|
179
|
+
|
|
166
180
|
dev?: {
|
|
167
181
|
/**
|
|
168
182
|
* The database connection origin configuration for local development
|
|
@@ -176,46 +190,45 @@ export interface HyperdriveProps extends CloudflareApiOptions {
|
|
|
176
190
|
* Output returned after Cloudflare Hyperdrive creation/update.
|
|
177
191
|
* IMPORTANT: The interface name MUST match the exported resource name.
|
|
178
192
|
*/
|
|
179
|
-
export
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
id: string;
|
|
193
|
+
export type Hyperdrive = Resource<"cloudflare::Hyperdrive"> &
|
|
194
|
+
Omit<HyperdriveProps, "origin" | "dev"> & {
|
|
195
|
+
/**
|
|
196
|
+
* The ID of the resource
|
|
197
|
+
*/
|
|
198
|
+
id: string;
|
|
186
199
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
200
|
+
/**
|
|
201
|
+
* Name of the Hyperdrive configuration
|
|
202
|
+
*/
|
|
203
|
+
name: string;
|
|
191
204
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
205
|
+
/**
|
|
206
|
+
* The Cloudflare-generated UUID of the hyperdrive
|
|
207
|
+
*/
|
|
208
|
+
hyperdriveId: string;
|
|
196
209
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Database connection origin configuration
|
|
212
|
+
*/
|
|
213
|
+
origin: HyperdrivePublicOrigin | HyperdriveOriginWithAccess;
|
|
201
214
|
|
|
202
|
-
/**
|
|
203
|
-
* Local development configuration
|
|
204
|
-
* @internal
|
|
205
|
-
*/
|
|
206
|
-
dev: {
|
|
207
215
|
/**
|
|
208
|
-
*
|
|
216
|
+
* Local development configuration
|
|
217
|
+
* @internal
|
|
209
218
|
*/
|
|
210
|
-
|
|
211
|
-
|
|
219
|
+
dev: {
|
|
220
|
+
/**
|
|
221
|
+
* The connection string to use for local development
|
|
222
|
+
*/
|
|
223
|
+
origin: Secret;
|
|
224
|
+
};
|
|
212
225
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
226
|
+
/**
|
|
227
|
+
* Resource type identifier for binding.
|
|
228
|
+
* @internal
|
|
229
|
+
*/
|
|
230
|
+
type: "hyperdrive";
|
|
231
|
+
};
|
|
219
232
|
|
|
220
233
|
/**
|
|
221
234
|
* Represents a Cloudflare Hyperdrive configuration.
|
|
@@ -327,6 +340,7 @@ interface InternalHyperdriveProps extends CloudflareApiOptions {
|
|
|
327
340
|
origin: Secret;
|
|
328
341
|
force?: boolean;
|
|
329
342
|
};
|
|
343
|
+
adopt?: boolean;
|
|
330
344
|
}
|
|
331
345
|
|
|
332
346
|
const _Hyperdrive = Resource(
|
|
@@ -337,7 +351,7 @@ const _Hyperdrive = Resource(
|
|
|
337
351
|
props: InternalHyperdriveProps,
|
|
338
352
|
): Promise<Hyperdrive> {
|
|
339
353
|
const hyperdriveId = props.hyperdriveId || this.output?.hyperdriveId;
|
|
340
|
-
|
|
354
|
+
const adopt = props.adopt || this.scope.adopt;
|
|
341
355
|
const name =
|
|
342
356
|
props.name ?? this.output?.name ?? this.scope.createPhysicalName(id);
|
|
343
357
|
|
|
@@ -354,13 +368,6 @@ const _Hyperdrive = Resource(
|
|
|
354
368
|
});
|
|
355
369
|
}
|
|
356
370
|
const api = await createCloudflareApi(props);
|
|
357
|
-
const configsPath = `/accounts/${api.accountId}/hyperdrive/configs`;
|
|
358
|
-
|
|
359
|
-
// For create operations, we don't have a hyperdriveId yet
|
|
360
|
-
// For update/delete operations, we need to use the hyperdriveId from props or output
|
|
361
|
-
const configPath = hyperdriveId
|
|
362
|
-
? `${configsPath}/${hyperdriveId}`
|
|
363
|
-
: `${configsPath}`;
|
|
364
371
|
|
|
365
372
|
if (this.phase === "delete") {
|
|
366
373
|
if (!hyperdriveId) {
|
|
@@ -369,7 +376,9 @@ const _Hyperdrive = Resource(
|
|
|
369
376
|
}
|
|
370
377
|
|
|
371
378
|
try {
|
|
372
|
-
const deleteResponse = await api.delete(
|
|
379
|
+
const deleteResponse = await api.delete(
|
|
380
|
+
`/accounts/${api.accountId}/hyperdrive/configs/${hyperdriveId}`,
|
|
381
|
+
);
|
|
373
382
|
// Only swallow 404 Not Found errors, all other errors should be handled
|
|
374
383
|
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
375
384
|
await handleApiError(deleteResponse, "delete", "hyperdrive", id);
|
|
@@ -381,75 +390,107 @@ const _Hyperdrive = Resource(
|
|
|
381
390
|
return this.destroy();
|
|
382
391
|
}
|
|
383
392
|
|
|
384
|
-
let response: Response | undefined;
|
|
385
|
-
let apiResource: any;
|
|
386
|
-
|
|
387
393
|
// Prepare request body with unwrapped secrets
|
|
388
|
-
const requestBody = prepareRequestBody(props);
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
394
|
+
const requestBody = prepareRequestBody({ ...props, name });
|
|
395
|
+
let result: HyperdriveResponse;
|
|
396
|
+
|
|
397
|
+
if (hyperdriveId) {
|
|
398
|
+
result = await extractCloudflareResult<HyperdriveResponse>(
|
|
399
|
+
`update hyperdrive config "${hyperdriveId}"`,
|
|
400
|
+
api.put(
|
|
401
|
+
`/accounts/${api.accountId}/hyperdrive/configs/${hyperdriveId}`,
|
|
402
|
+
requestBody,
|
|
403
|
+
),
|
|
404
|
+
);
|
|
405
|
+
} else {
|
|
406
|
+
try {
|
|
407
|
+
result = await extractCloudflareResult<HyperdriveResponse>(
|
|
408
|
+
`create hyperdrive config "${name}"`,
|
|
409
|
+
api.post(
|
|
410
|
+
`/accounts/${api.accountId}/hyperdrive/configs`,
|
|
411
|
+
requestBody,
|
|
412
|
+
),
|
|
413
|
+
);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
if (
|
|
416
|
+
error instanceof CloudflareApiError &&
|
|
417
|
+
(error.errorData as CloudflareApiErrorPayload[]).some(
|
|
418
|
+
(e) => e.code === 2017, // 2017 is the error code for name conflict
|
|
419
|
+
)
|
|
420
|
+
) {
|
|
421
|
+
if (!adopt) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`Hyperdrive config "${name}" already exists. Use adopt: true to adopt it.`,
|
|
424
|
+
{ cause: error },
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
const existing = await findHyperdriveConfigByName(api, name);
|
|
428
|
+
if (!existing) {
|
|
429
|
+
throw new Error(
|
|
430
|
+
`Hyperdrive config "${name}" failed to create due to name conflict and could not be found for adoption.`,
|
|
431
|
+
{ cause: error },
|
|
404
432
|
);
|
|
405
|
-
response = await api.put(configPath, requestBody);
|
|
406
|
-
} else if (getResponse.status === 404) {
|
|
407
|
-
// Hyperdrive doesn't exist, create new
|
|
408
|
-
response = await api.post(configsPath, {
|
|
409
|
-
...requestBody,
|
|
410
|
-
// Ensure name is set correctly if not already set
|
|
411
|
-
name: props.name || id,
|
|
412
|
-
});
|
|
413
|
-
} else {
|
|
414
|
-
// Unexpected error during GET check
|
|
415
|
-
await handleApiError(getResponse, "get", "hyperdrive", id);
|
|
416
433
|
}
|
|
434
|
+
result = await extractCloudflareResult<HyperdriveResponse>(
|
|
435
|
+
`adopt hyperdrive config "${name}"`,
|
|
436
|
+
api.put(
|
|
437
|
+
`/accounts/${api.accountId}/hyperdrive/configs/${existing.id}`,
|
|
438
|
+
requestBody,
|
|
439
|
+
),
|
|
440
|
+
);
|
|
417
441
|
} else {
|
|
418
|
-
|
|
419
|
-
response = await api.post(configsPath, {
|
|
420
|
-
...requestBody,
|
|
421
|
-
// Ensure name is set correctly if not already set
|
|
422
|
-
name: props.name || id,
|
|
423
|
-
});
|
|
442
|
+
throw error;
|
|
424
443
|
}
|
|
425
444
|
}
|
|
426
|
-
|
|
427
|
-
if (!response?.ok) {
|
|
428
|
-
const action = this.phase === "update" ? "update" : "create";
|
|
429
|
-
await handleApiError(response!, action, "hyperdrive", id);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const data: { result: Record<string, any> } = await response!.json();
|
|
433
|
-
apiResource = data.result;
|
|
434
|
-
} catch (error) {
|
|
435
|
-
logger.error(`Error ${this.phase} Hyperdrive '${id}':`, error);
|
|
436
|
-
throw error;
|
|
437
445
|
}
|
|
438
446
|
|
|
439
447
|
// Construct the output object from API response and props
|
|
440
448
|
return this({
|
|
441
449
|
id,
|
|
442
|
-
hyperdriveId:
|
|
443
|
-
name:
|
|
450
|
+
hyperdriveId: result.id, // Store the Cloudflare-assigned UUID
|
|
451
|
+
name: result.name,
|
|
444
452
|
origin: props.origin,
|
|
445
|
-
caching:
|
|
446
|
-
mtls:
|
|
453
|
+
caching: result.caching,
|
|
454
|
+
mtls: result.mtls,
|
|
447
455
|
dev: props.dev,
|
|
448
456
|
type: "hyperdrive",
|
|
449
457
|
});
|
|
450
458
|
},
|
|
451
459
|
);
|
|
452
460
|
|
|
461
|
+
interface HyperdriveResponse {
|
|
462
|
+
id: string;
|
|
463
|
+
name: string;
|
|
464
|
+
origin: HyperdriveOrigin;
|
|
465
|
+
caching?: HyperdriveCaching;
|
|
466
|
+
created_on?: string;
|
|
467
|
+
modified_on?: string;
|
|
468
|
+
mtls?: HyperdriveMtls;
|
|
469
|
+
origin_connection_limit?: number;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function findHyperdriveConfigByName(
|
|
473
|
+
api: CloudflareApi,
|
|
474
|
+
name: string,
|
|
475
|
+
page = 1,
|
|
476
|
+
) {
|
|
477
|
+
const response = await api.get(
|
|
478
|
+
`/accounts/${api.accountId}/hyperdrive/configs?page=${page}`,
|
|
479
|
+
);
|
|
480
|
+
const data: {
|
|
481
|
+
result: HyperdriveResponse[];
|
|
482
|
+
result_info?: { total_pages?: number };
|
|
483
|
+
} = await response.json();
|
|
484
|
+
const found = data.result.find((config) => config.name === name);
|
|
485
|
+
if (found) {
|
|
486
|
+
return found;
|
|
487
|
+
}
|
|
488
|
+
if (data.result_info?.total_pages && page < data.result_info.total_pages) {
|
|
489
|
+
return await findHyperdriveConfigByName(api, name, page + 1);
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
|
|
453
494
|
/**
|
|
454
495
|
* Prepare the request body by unwrapping secret values
|
|
455
496
|
*/
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ export * from "./email-rule.ts";
|
|
|
33
33
|
export * from "./hyperdrive.ts";
|
|
34
34
|
export * from "./images.ts";
|
|
35
35
|
export * from "./kv-namespace.ts";
|
|
36
|
+
export * from "./next.ts";
|
|
36
37
|
export * from "./nuxt/nuxt.ts";
|
|
37
38
|
export * from "./orange.ts";
|
|
38
39
|
export * from "./permission-groups.ts";
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { bold, cyan } from "picocolors";
|
|
4
|
+
import { getPackageManagerRunner } from "../util/detect-package-manager.ts";
|
|
5
|
+
import { exists } from "../util/exists.ts";
|
|
6
|
+
import ignore from "../util/ignore-matcher.ts";
|
|
7
|
+
import { logger } from "../util/logger.ts";
|
|
8
|
+
import type { Assets } from "./assets.ts";
|
|
9
|
+
import type { Bindings } from "./bindings.ts";
|
|
10
|
+
import { Website, type WebsiteProps } from "./website.ts";
|
|
11
|
+
import type { Worker } from "./worker.ts";
|
|
12
|
+
|
|
13
|
+
export interface NextjsProps<B extends Bindings> extends WebsiteProps<B> {}
|
|
14
|
+
|
|
15
|
+
export type Nextjs<B extends Bindings> = B extends { ASSETS: any }
|
|
16
|
+
? never
|
|
17
|
+
: Worker<B & { ASSETS: Assets }>;
|
|
18
|
+
|
|
19
|
+
export async function Nextjs<const B extends Bindings>(
|
|
20
|
+
id: string,
|
|
21
|
+
props: NextjsProps<B> = {},
|
|
22
|
+
): Promise<Nextjs<B>> {
|
|
23
|
+
const runner = await getPackageManagerRunner();
|
|
24
|
+
const isIgnored = await isFileIgnored("wrangler.jsonc", props.cwd);
|
|
25
|
+
if (!isIgnored) {
|
|
26
|
+
logger.warn(
|
|
27
|
+
[
|
|
28
|
+
bold(
|
|
29
|
+
`The ${cyan("wrangler.jsonc")} file for Next.js ${cyan(`"${id}"`)} is not ignored.`,
|
|
30
|
+
),
|
|
31
|
+
"This may cause unexpected behavior. Please add the file to your .gitignore.",
|
|
32
|
+
].join(" "),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return await Website(id, {
|
|
36
|
+
...props,
|
|
37
|
+
entrypoint: props.entrypoint ?? ".open-next/worker.js",
|
|
38
|
+
wrangler: {
|
|
39
|
+
path: "wrangler.jsonc",
|
|
40
|
+
secrets: isIgnored,
|
|
41
|
+
...(props.wrangler ?? {}),
|
|
42
|
+
},
|
|
43
|
+
build: normalizeCommand(props.build, {
|
|
44
|
+
command: `${runner} opennextjs-cloudflare build`,
|
|
45
|
+
env: {
|
|
46
|
+
NEXTJS_ENV: "production",
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
dev: normalizeCommand(props.dev, {
|
|
50
|
+
command: `${runner} next dev`,
|
|
51
|
+
env: {
|
|
52
|
+
NEXTJS_ENV: "development",
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
|
|
56
|
+
// OpenNext generates the files, but relies on us to bundle them.
|
|
57
|
+
noBundle: props.noBundle ?? false,
|
|
58
|
+
|
|
59
|
+
spa: false,
|
|
60
|
+
compatibilityFlags: [
|
|
61
|
+
"nodejs_compat",
|
|
62
|
+
"global_fetch_strictly_public",
|
|
63
|
+
...(props.compatibilityFlags ?? []),
|
|
64
|
+
],
|
|
65
|
+
assets: props.assets ?? ".open-next/assets",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if a file is ignored by .gitignore.
|
|
71
|
+
*
|
|
72
|
+
* @param filename - The name of the file to check.
|
|
73
|
+
* @param cwd - The current working directory.
|
|
74
|
+
* @returns True if the file is ignored, false otherwise.
|
|
75
|
+
*/
|
|
76
|
+
const isFileIgnored = async (filename: string, cwd?: string) => {
|
|
77
|
+
const matcher = ignore();
|
|
78
|
+
|
|
79
|
+
// Read .gitignore from current directory and walk up the directory tree
|
|
80
|
+
let currentDir = path.resolve(cwd ?? process.cwd());
|
|
81
|
+
const gitignorePatterns: string[] = [];
|
|
82
|
+
|
|
83
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
84
|
+
const gitignorePath = path.join(currentDir, ".gitignore");
|
|
85
|
+
try {
|
|
86
|
+
const gitignoreContent = await fs.readFile(gitignorePath, "utf8");
|
|
87
|
+
// unshift to prioritize local .gitignore
|
|
88
|
+
gitignorePatterns.unshift(
|
|
89
|
+
...gitignoreContent
|
|
90
|
+
.split("\n")
|
|
91
|
+
.filter((line) => line.trim() && !line.startsWith("#")),
|
|
92
|
+
);
|
|
93
|
+
} catch {
|
|
94
|
+
// .gitignore doesn't exist in this directory, continue up
|
|
95
|
+
}
|
|
96
|
+
if (await exists(path.join(currentDir, ".git"))) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
currentDir = path.dirname(currentDir);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
matcher.add(gitignorePatterns);
|
|
103
|
+
|
|
104
|
+
return matcher.ignores(filename);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const normalizeCommand = (
|
|
108
|
+
input: WebsiteProps<any>["build"],
|
|
109
|
+
defaults: {
|
|
110
|
+
command: string;
|
|
111
|
+
env: Record<string, string>;
|
|
112
|
+
},
|
|
113
|
+
): WebsiteProps<any>["build"] => {
|
|
114
|
+
return {
|
|
115
|
+
command:
|
|
116
|
+
typeof input === "string" ? input : (input?.command ?? defaults.command),
|
|
117
|
+
env: {
|
|
118
|
+
...defaults.env,
|
|
119
|
+
...(typeof input === "object" ? (input?.env ?? {}) : {}),
|
|
120
|
+
},
|
|
121
|
+
memoize: typeof input === "object" ? input?.memoize : undefined,
|
|
122
|
+
};
|
|
123
|
+
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { alchemy } from "../alchemy.ts";
|
|
5
4
|
import { Exec } from "../os/index.ts";
|
|
6
5
|
import { Scope } from "../scope.ts";
|
|
7
6
|
import { isSecret } from "../secret.ts";
|
|
@@ -255,101 +254,101 @@ export async function Website<B extends Bindings>(
|
|
|
255
254
|
entrypoint: path.relative(paths.cwd, paths.entrypoint),
|
|
256
255
|
} as WorkerProps<B> & { name: string };
|
|
257
256
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
257
|
+
if (!workerProps.entrypoint) {
|
|
258
|
+
await fs.mkdir(path.dirname(paths.entrypoint), { recursive: true });
|
|
259
|
+
const content =
|
|
260
|
+
script ??
|
|
261
|
+
dedent`
|
|
262
|
+
export default {
|
|
263
|
+
async fetch(request, env) {
|
|
264
|
+
return new Response("Not Found", { status: 404 });
|
|
265
|
+
},
|
|
266
|
+
};`;
|
|
267
|
+
await fs.writeFile(paths.entrypoint, content);
|
|
268
|
+
}
|
|
271
269
|
|
|
272
|
-
|
|
270
|
+
await writeMiniflareSymlink(paths.cwd);
|
|
273
271
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
272
|
+
await WranglerJson({
|
|
273
|
+
path: path.relative(paths.cwd, paths.wrangler.path),
|
|
274
|
+
worker,
|
|
275
|
+
assets: {
|
|
276
|
+
binding: "ASSETS",
|
|
277
|
+
directory: path.relative(paths.cwd, paths.assets),
|
|
278
|
+
},
|
|
279
|
+
main: path.relative(paths.cwd, paths.wrangler.main),
|
|
280
|
+
secrets,
|
|
281
|
+
transform: {
|
|
282
|
+
wrangler: props.wrangler?.transform,
|
|
283
|
+
},
|
|
284
|
+
});
|
|
287
285
|
|
|
288
|
-
|
|
289
|
-
await Exec("build", {
|
|
290
|
-
cwd: path.relative(process.cwd(), paths.cwd),
|
|
291
|
-
command: build.command,
|
|
292
|
-
env: {
|
|
293
|
-
...env,
|
|
294
|
-
...(typeof build === "object" ? build.env : {}),
|
|
295
|
-
NODE_ENV: "production",
|
|
296
|
-
},
|
|
297
|
-
memoize: typeof build === "object" ? build.memoize : undefined,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
286
|
+
const scope = Scope.current;
|
|
300
287
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return match[0];
|
|
314
|
-
}
|
|
315
|
-
},
|
|
316
|
-
env: {
|
|
317
|
-
...Object.fromEntries(
|
|
318
|
-
Object.entries(env ?? {}).flatMap(([key, value]) => {
|
|
319
|
-
if (isSecret(value)) {
|
|
320
|
-
return [[key, value.unencrypted]];
|
|
321
|
-
}
|
|
322
|
-
if (typeof value === "string") {
|
|
323
|
-
return [[key, value]];
|
|
324
|
-
}
|
|
325
|
-
return [];
|
|
326
|
-
}),
|
|
327
|
-
),
|
|
328
|
-
...(typeof dev === "object" ? dev.env : {}),
|
|
329
|
-
FORCE_COLOR: "1",
|
|
330
|
-
...process.env,
|
|
331
|
-
// NOTE: we must set this to ensure the user does not accidentally set `NODE_ENV=production`
|
|
332
|
-
// which breaks `vite dev` (it won't, for example, re-write `process.env.TSS_APP_BASE` in the `.js` client side bundle)
|
|
333
|
-
NODE_ENV: "development",
|
|
334
|
-
},
|
|
335
|
-
});
|
|
336
|
-
}
|
|
288
|
+
if (build && !scope.local) {
|
|
289
|
+
await Exec(`${id}-build`, {
|
|
290
|
+
cwd: path.relative(process.cwd(), paths.cwd),
|
|
291
|
+
command: build.command,
|
|
292
|
+
env: {
|
|
293
|
+
...env,
|
|
294
|
+
...(typeof build === "object" ? build.env : {}),
|
|
295
|
+
NODE_ENV: "production",
|
|
296
|
+
},
|
|
297
|
+
memoize: typeof build === "object" ? build.memoize : undefined,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
337
300
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
301
|
+
let url: string | undefined;
|
|
302
|
+
if (dev && scope.local) {
|
|
303
|
+
url = await scope.spawn(name, {
|
|
304
|
+
cmd: typeof dev === "string" ? dev : dev.command,
|
|
305
|
+
cwd: paths.cwd,
|
|
306
|
+
extract: (line) => {
|
|
307
|
+
const URL_REGEX =
|
|
308
|
+
/http:\/\/(localhost|0\.0\.0\.0|127\.0\.0\.1|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):\d+\/?/;
|
|
309
|
+
const match = line
|
|
310
|
+
.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "")
|
|
311
|
+
.match(URL_REGEX);
|
|
312
|
+
if (match) {
|
|
313
|
+
return match[0];
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
env: {
|
|
317
|
+
...Object.fromEntries(
|
|
318
|
+
Object.entries(env ?? {}).flatMap(([key, value]) => {
|
|
319
|
+
if (isSecret(value)) {
|
|
320
|
+
return [[key, value.unencrypted]];
|
|
321
|
+
}
|
|
322
|
+
if (typeof value === "string") {
|
|
323
|
+
return [[key, value]];
|
|
347
324
|
}
|
|
348
|
-
|
|
325
|
+
return [];
|
|
326
|
+
}),
|
|
327
|
+
),
|
|
328
|
+
...(typeof dev === "object" ? dev.env : {}),
|
|
329
|
+
FORCE_COLOR: "1",
|
|
330
|
+
...process.env,
|
|
331
|
+
// NOTE: we must set this to ensure the user does not accidentally set `NODE_ENV=production`
|
|
332
|
+
// which breaks `vite dev` (it won't, for example, re-write `process.env.TSS_APP_BASE` in the `.js` client side bundle)
|
|
333
|
+
NODE_ENV: "development",
|
|
349
334
|
},
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return (await Worker(id, {
|
|
339
|
+
...worker,
|
|
340
|
+
bindings: {
|
|
341
|
+
...worker.bindings,
|
|
342
|
+
...(!scope.local
|
|
343
|
+
? {
|
|
344
|
+
ASSETS: await Assets("assets", {
|
|
345
|
+
path: path.relative(process.cwd(), paths.assets),
|
|
346
|
+
}),
|
|
347
|
+
}
|
|
348
|
+
: {}),
|
|
349
|
+
},
|
|
350
|
+
dev: url ? { url } : undefined,
|
|
351
|
+
})) as Website<B>;
|
|
353
352
|
}
|
|
354
353
|
|
|
355
354
|
async function writeMiniflareSymlink(cwd: string) {
|