alchemy 0.39.1 → 0.40.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/lib/alchemy.d.ts +6 -0
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +5 -0
- package/lib/alchemy.js.map +1 -1
- package/lib/build-date.d.ts +1 -1
- package/lib/build-date.js +1 -1
- package/lib/cloudflare/bucket.d.ts +10 -0
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +1 -0
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker-dev.d.ts +25 -0
- package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +1 -0
- package/lib/cloudflare/bundle/bundle-worker-dev.js +85 -0
- package/lib/cloudflare/bundle/bundle-worker-dev.js.map +1 -0
- package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker.js +0 -3
- package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
- package/lib/cloudflare/bundle/nodejs-compat-mode.d.ts +1 -2
- package/lib/cloudflare/bundle/nodejs-compat-mode.d.ts.map +1 -1
- package/lib/cloudflare/bundle/nodejs-compat-mode.js +6 -1
- package/lib/cloudflare/bundle/nodejs-compat-mode.js.map +1 -1
- package/lib/cloudflare/d1-database.d.ts +10 -0
- package/lib/cloudflare/d1-database.d.ts.map +1 -1
- package/lib/cloudflare/d1-database.js +1 -0
- package/lib/cloudflare/d1-database.js.map +1 -1
- package/lib/cloudflare/do-state-store/internal.d.ts +1 -0
- package/lib/cloudflare/do-state-store/internal.d.ts.map +1 -1
- package/lib/cloudflare/do-state-store/internal.js +27 -29
- package/lib/cloudflare/do-state-store/internal.js.map +1 -1
- package/lib/cloudflare/do-state-store/store.d.ts.map +1 -1
- package/lib/cloudflare/do-state-store/store.js +7 -20
- package/lib/cloudflare/do-state-store/store.js.map +1 -1
- package/lib/cloudflare/images.d.ts +12 -0
- package/lib/cloudflare/images.d.ts.map +1 -1
- package/lib/cloudflare/images.js +4 -0
- package/lib/cloudflare/images.js.map +1 -1
- package/lib/cloudflare/kv-namespace.d.ts +10 -0
- package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
- package/lib/cloudflare/kv-namespace.js +1 -0
- package/lib/cloudflare/kv-namespace.js.map +1 -1
- package/lib/cloudflare/queue.d.ts +10 -0
- package/lib/cloudflare/queue.d.ts.map +1 -1
- package/lib/cloudflare/queue.js +1 -0
- package/lib/cloudflare/queue.js.map +1 -1
- package/lib/cloudflare/vite.d.ts.map +1 -1
- package/lib/cloudflare/vite.js +12 -0
- package/lib/cloudflare/vite.js.map +1 -1
- package/lib/cloudflare/website.d.ts +7 -0
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +16 -6
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker/get-worker-template.d.ts +2 -0
- package/lib/cloudflare/worker/get-worker-template.d.ts.map +1 -0
- package/lib/cloudflare/worker/get-worker-template.js +12 -0
- package/lib/cloudflare/worker/get-worker-template.js.map +1 -0
- package/lib/cloudflare/worker/http-server.d.ts +14 -0
- package/lib/cloudflare/worker/http-server.d.ts.map +1 -0
- package/lib/cloudflare/worker/http-server.js +67 -0
- package/lib/cloudflare/worker/http-server.js.map +1 -0
- package/lib/cloudflare/worker/miniflare-worker-options.d.ts +13 -0
- package/lib/cloudflare/worker/miniflare-worker-options.d.ts.map +1 -0
- package/lib/cloudflare/worker/miniflare-worker-options.js +408 -0
- package/lib/cloudflare/worker/miniflare-worker-options.js.map +1 -0
- package/lib/cloudflare/worker/miniflare.d.ts +31 -0
- package/lib/cloudflare/worker/miniflare.d.ts.map +1 -0
- package/lib/cloudflare/worker/miniflare.js +150 -0
- package/lib/cloudflare/worker/miniflare.js.map +1 -0
- package/lib/cloudflare/worker/mixed-mode.d.ts +17 -0
- package/lib/cloudflare/worker/mixed-mode.d.ts.map +1 -0
- package/lib/cloudflare/worker/mixed-mode.js +145 -0
- package/lib/cloudflare/worker/mixed-mode.js.map +1 -0
- package/lib/cloudflare/worker/subdomain.d.ts +3 -0
- package/lib/cloudflare/worker/subdomain.d.ts.map +1 -0
- package/lib/cloudflare/worker/subdomain.js +10 -0
- package/lib/cloudflare/worker/subdomain.js.map +1 -0
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +7 -3
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +23 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +229 -8
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/workflow.d.ts +7 -0
- package/lib/cloudflare/workflow.d.ts.map +1 -1
- package/lib/cloudflare/workflow.js.map +1 -1
- package/lib/scope.d.ts +2 -0
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +6 -1
- package/lib/scope.js.map +1 -1
- package/lib/util/assert-never.d.ts +1 -1
- package/lib/util/assert-never.d.ts.map +1 -1
- package/lib/util/assert-never.js +2 -2
- package/lib/util/assert-never.js.map +1 -1
- package/lib/util/cli.d.ts +21 -7
- package/lib/util/cli.d.ts.map +1 -1
- package/lib/util/cli.js +8 -0
- package/lib/util/cli.js.map +1 -1
- package/lib/util/deferred-promise.d.ts +8 -0
- package/lib/util/deferred-promise.d.ts.map +1 -0
- package/lib/util/deferred-promise.js +16 -0
- package/lib/util/deferred-promise.js.map +1 -0
- package/lib/util/detect-package-manager.d.ts +3 -0
- package/lib/util/detect-package-manager.d.ts.map +1 -0
- package/lib/util/detect-package-manager.js +25 -0
- package/lib/util/detect-package-manager.js.map +1 -0
- package/lib/util/logger.d.ts.map +1 -1
- package/lib/util/logger.js +1 -0
- package/lib/util/logger.js.map +1 -1
- package/lib/util/memoize.d.ts +4 -0
- package/lib/util/memoize.d.ts.map +1 -0
- package/lib/util/memoize.js +17 -0
- package/lib/util/memoize.js.map +1 -0
- package/package.json +7 -5
- package/src/alchemy.ts +14 -0
- package/src/build-date.ts +1 -1
- package/src/cloudflare/bucket.ts +12 -0
- package/src/cloudflare/bundle/bundle-worker-dev.ts +126 -0
- package/src/cloudflare/bundle/bundle-worker.ts +0 -5
- package/src/cloudflare/bundle/nodejs-compat-mode.ts +12 -3
- package/src/cloudflare/d1-database.ts +11 -0
- package/src/cloudflare/do-state-store/internal.ts +30 -31
- package/src/cloudflare/do-state-store/store.ts +9 -26
- package/src/cloudflare/images.ts +14 -0
- package/src/cloudflare/kv-namespace.ts +12 -0
- package/src/cloudflare/queue.ts +12 -0
- package/src/cloudflare/vite.ts +12 -0
- package/src/cloudflare/website.ts +25 -6
- package/src/cloudflare/worker/get-worker-template.ts +14 -0
- package/src/cloudflare/worker/http-server.ts +80 -0
- package/src/cloudflare/worker/miniflare-worker-options.ts +531 -0
- package/src/cloudflare/worker/miniflare.ts +198 -0
- package/src/cloudflare/worker/mixed-mode.ts +220 -0
- package/src/cloudflare/worker/subdomain.ts +16 -0
- package/src/cloudflare/worker-metadata.ts +12 -4
- package/src/cloudflare/worker.ts +284 -18
- package/src/cloudflare/workflow.ts +7 -0
- package/src/scope.ts +11 -1
- package/src/util/assert-never.ts +2 -2
- package/src/util/cli.ts +19 -7
- package/src/util/deferred-promise.ts +19 -0
- package/src/util/detect-package-manager.ts +23 -0
- package/src/util/logger.ts +1 -0
- package/src/util/memoize.ts +23 -0
- package/workers/do-state-store.js +826 -0
- package/{src/cloudflare/do-state-store/worker.ts → workers/do-state-store.ts} +55 -23
- package/workers/mixed-mode-proxy-worker.js +39 -0
- package/workers/mixed-mode-proxy-worker.ts +46 -0
- package/lib/cloudflare/do-state-store/worker.d.ts +0 -27
- package/lib/cloudflare/do-state-store/worker.d.ts.map +0 -1
- package/lib/cloudflare/do-state-store/worker.js +0 -179
- package/lib/cloudflare/do-state-store/worker.js.map +0 -1
package/src/cloudflare/worker.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
unlinkSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
1
9
|
import path from "node:path";
|
|
2
10
|
import { BUILD_DATE } from "../build-date.ts";
|
|
3
11
|
import type { Context } from "../context.ts";
|
|
@@ -11,6 +19,7 @@ import { Secret, secret } from "../secret.ts";
|
|
|
11
19
|
import { serializeScope } from "../serde.ts";
|
|
12
20
|
import type { type } from "../type.ts";
|
|
13
21
|
import { getContentType } from "../util/content-type.ts";
|
|
22
|
+
import { DeferredPromise } from "../util/deferred-promise.ts";
|
|
14
23
|
import { logger } from "../util/logger.ts";
|
|
15
24
|
import { withExponentialBackoff } from "../util/retry.ts";
|
|
16
25
|
import { CloudflareApiError, handleApiError } from "./api-error.ts";
|
|
@@ -28,6 +37,7 @@ import {
|
|
|
28
37
|
} from "./bindings.ts";
|
|
29
38
|
import type { Bound } from "./bound.ts";
|
|
30
39
|
import { isBucket } from "./bucket.ts";
|
|
40
|
+
import { createWorkerDevContext } from "./bundle/bundle-worker-dev.ts";
|
|
31
41
|
import {
|
|
32
42
|
type NoBundleResult,
|
|
33
43
|
bundleWorkerScript,
|
|
@@ -61,6 +71,9 @@ import {
|
|
|
61
71
|
prepareWorkerMetadata,
|
|
62
72
|
} from "./worker-metadata.ts";
|
|
63
73
|
import { WorkerStub, isWorkerStub } from "./worker-stub.ts";
|
|
74
|
+
import type { MiniflareWorkerOptions } from "./worker/miniflare-worker-options.ts";
|
|
75
|
+
import { miniflareServer } from "./worker/miniflare.ts";
|
|
76
|
+
import { getAccountSubdomain } from "./worker/subdomain.ts";
|
|
64
77
|
import { Workflow, isWorkflow, upsertWorkflow } from "./workflow.ts";
|
|
65
78
|
|
|
66
79
|
/**
|
|
@@ -300,6 +313,32 @@ export interface BaseWorkerProps<
|
|
|
300
313
|
* @example "pr-123"
|
|
301
314
|
*/
|
|
302
315
|
version?: string;
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Configuration for local development. By default, when Alchemy is running in development mode,
|
|
319
|
+
* the worker will be emulated locally and available at a randomly selected port.
|
|
320
|
+
*/
|
|
321
|
+
dev?:
|
|
322
|
+
| boolean
|
|
323
|
+
| {
|
|
324
|
+
/**
|
|
325
|
+
* Port to use for local development
|
|
326
|
+
*/
|
|
327
|
+
port?: number;
|
|
328
|
+
/**
|
|
329
|
+
* EXPERIMENTAL: Whether to run the worker remotely instead of locally.
|
|
330
|
+
*
|
|
331
|
+
* When this is enabled, hot reloading will not work.
|
|
332
|
+
*
|
|
333
|
+
* @default false
|
|
334
|
+
*/
|
|
335
|
+
remote?: boolean;
|
|
336
|
+
}
|
|
337
|
+
| {
|
|
338
|
+
command: string;
|
|
339
|
+
url: string;
|
|
340
|
+
cwd?: string;
|
|
341
|
+
};
|
|
303
342
|
}
|
|
304
343
|
|
|
305
344
|
export interface InlineWorkerProps<
|
|
@@ -922,9 +961,6 @@ export const _Worker = Resource(
|
|
|
922
961
|
throw new Error("entrypoint must be provided when noBundle is true");
|
|
923
962
|
}
|
|
924
963
|
|
|
925
|
-
// Create Cloudflare API client with automatic account discovery
|
|
926
|
-
const api = await createCloudflareApi(props);
|
|
927
|
-
|
|
928
964
|
// Use the provided name
|
|
929
965
|
const workerName = props.name ?? id;
|
|
930
966
|
|
|
@@ -932,6 +968,200 @@ export const _Worker = Resource(
|
|
|
932
968
|
props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE;
|
|
933
969
|
const compatibilityFlags = props.compatibilityFlags ?? [];
|
|
934
970
|
|
|
971
|
+
if (this.scope.dev && this.phase !== "delete" && props.dev !== false) {
|
|
972
|
+
// Get current timestamp
|
|
973
|
+
const now = Date.now();
|
|
974
|
+
|
|
975
|
+
if (typeof props.dev === "object" && "command" in props.dev) {
|
|
976
|
+
upsertDevCommand({
|
|
977
|
+
id,
|
|
978
|
+
command: props.dev.command,
|
|
979
|
+
cwd: props.dev.cwd ?? process.cwd(),
|
|
980
|
+
env: props.env ?? {},
|
|
981
|
+
});
|
|
982
|
+
return this({
|
|
983
|
+
type: "service",
|
|
984
|
+
id,
|
|
985
|
+
entrypoint: props.entrypoint,
|
|
986
|
+
name: workerName,
|
|
987
|
+
compatibilityDate,
|
|
988
|
+
compatibilityFlags,
|
|
989
|
+
format: props.format || "esm", // Include format in the output
|
|
990
|
+
bindings: props.bindings ?? ({} as B),
|
|
991
|
+
env: props.env,
|
|
992
|
+
observability: props.observability,
|
|
993
|
+
createdAt: now,
|
|
994
|
+
updatedAt: now,
|
|
995
|
+
eventSources: props.eventSources,
|
|
996
|
+
url: props.dev.url,
|
|
997
|
+
dev: props.dev,
|
|
998
|
+
// Include assets configuration in the output
|
|
999
|
+
assets: props.assets,
|
|
1000
|
+
// Include cron triggers in the output
|
|
1001
|
+
crons: props.crons,
|
|
1002
|
+
// phantom property
|
|
1003
|
+
Env: undefined!,
|
|
1004
|
+
} as unknown as Worker<B>);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const sharedOptions: Omit<MiniflareWorkerOptions, "script"> = {
|
|
1008
|
+
name: workerName,
|
|
1009
|
+
compatibilityDate,
|
|
1010
|
+
compatibilityFlags,
|
|
1011
|
+
bindings: props.bindings ?? ({} as B),
|
|
1012
|
+
port: typeof props.dev === "object" ? props.dev.port : undefined,
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
let url: string;
|
|
1016
|
+
|
|
1017
|
+
// If entrypoint is provided, set up hot reloading with esbuild context
|
|
1018
|
+
if (props.entrypoint) {
|
|
1019
|
+
const startPromise = new DeferredPromise<string>();
|
|
1020
|
+
|
|
1021
|
+
await createWorkerDevContext(
|
|
1022
|
+
workerName,
|
|
1023
|
+
{
|
|
1024
|
+
...props,
|
|
1025
|
+
entrypoint: props.entrypoint,
|
|
1026
|
+
compatibilityDate,
|
|
1027
|
+
compatibilityFlags,
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
onBuildStart: () => {
|
|
1031
|
+
logger.task("miniflare-server", {
|
|
1032
|
+
message: `${startPromise.status === "pending" ? "Building" : "Rebuilding"}`,
|
|
1033
|
+
status: "pending",
|
|
1034
|
+
resource: id,
|
|
1035
|
+
prefix: "build",
|
|
1036
|
+
prefixColor: "cyanBright",
|
|
1037
|
+
});
|
|
1038
|
+
},
|
|
1039
|
+
onBuildEnd: async (newScript) => {
|
|
1040
|
+
try {
|
|
1041
|
+
// Hot reload callback - update the miniflare worker
|
|
1042
|
+
const server = await miniflareServer.push({
|
|
1043
|
+
...sharedOptions,
|
|
1044
|
+
script: newScript,
|
|
1045
|
+
});
|
|
1046
|
+
if (startPromise.status === "pending") {
|
|
1047
|
+
logger.task("miniflare-server", {
|
|
1048
|
+
message: `Started Miniflare server at ${server.url}`,
|
|
1049
|
+
status: "success",
|
|
1050
|
+
resource: id,
|
|
1051
|
+
prefix: "ready",
|
|
1052
|
+
prefixColor: "cyanBright",
|
|
1053
|
+
});
|
|
1054
|
+
startPromise.resolve(server.url);
|
|
1055
|
+
} else {
|
|
1056
|
+
logger.task("miniflare-server", {
|
|
1057
|
+
message: `Updated Miniflare server at ${server.url}`,
|
|
1058
|
+
status: "success",
|
|
1059
|
+
resource: id,
|
|
1060
|
+
prefix: "reload",
|
|
1061
|
+
prefixColor: "cyanBright",
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
if (startPromise.status === "pending") {
|
|
1066
|
+
logger.error(error);
|
|
1067
|
+
logger.task("miniflare-server", {
|
|
1068
|
+
message: "Failed to start Miniflare server",
|
|
1069
|
+
status: "failure",
|
|
1070
|
+
resource: id,
|
|
1071
|
+
prefix: "error",
|
|
1072
|
+
prefixColor: "redBright",
|
|
1073
|
+
});
|
|
1074
|
+
startPromise.reject(
|
|
1075
|
+
new Error(
|
|
1076
|
+
`Failed to start Miniflare server for worker "${workerName}"`,
|
|
1077
|
+
{ cause: error },
|
|
1078
|
+
),
|
|
1079
|
+
);
|
|
1080
|
+
} else {
|
|
1081
|
+
logger.task("miniflare-server", {
|
|
1082
|
+
message: "Failed to update Miniflare server",
|
|
1083
|
+
status: "failure",
|
|
1084
|
+
resource: id,
|
|
1085
|
+
prefix: "error",
|
|
1086
|
+
prefixColor: "redBright",
|
|
1087
|
+
});
|
|
1088
|
+
logger.error(error);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
onBuildError: (errors) => {
|
|
1093
|
+
if (startPromise.status === "pending") {
|
|
1094
|
+
logger.task("miniflare-server", {
|
|
1095
|
+
message: "Failed to build worker",
|
|
1096
|
+
status: "failure",
|
|
1097
|
+
resource: id,
|
|
1098
|
+
prefix: "error",
|
|
1099
|
+
prefixColor: "redBright",
|
|
1100
|
+
});
|
|
1101
|
+
startPromise.reject(errors);
|
|
1102
|
+
} else {
|
|
1103
|
+
logger.task("miniflare-server", {
|
|
1104
|
+
message: "Failed to rebuild worker",
|
|
1105
|
+
status: "failure",
|
|
1106
|
+
resource: id,
|
|
1107
|
+
prefix: "error",
|
|
1108
|
+
prefixColor: "redBright",
|
|
1109
|
+
});
|
|
1110
|
+
logger.error(errors);
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
},
|
|
1114
|
+
);
|
|
1115
|
+
|
|
1116
|
+
url = await startPromise.value;
|
|
1117
|
+
} else {
|
|
1118
|
+
// Fallback to one-time bundling for inline scripts
|
|
1119
|
+
const scriptContent =
|
|
1120
|
+
props.script ??
|
|
1121
|
+
(await bundleWorkerScript({
|
|
1122
|
+
name: workerName,
|
|
1123
|
+
...props,
|
|
1124
|
+
compatibilityDate,
|
|
1125
|
+
compatibilityFlags,
|
|
1126
|
+
}));
|
|
1127
|
+
const server = await miniflareServer.push({
|
|
1128
|
+
...sharedOptions,
|
|
1129
|
+
script:
|
|
1130
|
+
typeof scriptContent === "string"
|
|
1131
|
+
? scriptContent
|
|
1132
|
+
: scriptContent[props.entrypoint!].toString(),
|
|
1133
|
+
});
|
|
1134
|
+
url = server.url;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
return this({
|
|
1138
|
+
type: "service",
|
|
1139
|
+
id,
|
|
1140
|
+
entrypoint: props.entrypoint,
|
|
1141
|
+
name: workerName,
|
|
1142
|
+
compatibilityDate,
|
|
1143
|
+
compatibilityFlags,
|
|
1144
|
+
format: props.format || "esm", // Include format in the output
|
|
1145
|
+
bindings: props.bindings ?? ({} as B),
|
|
1146
|
+
env: props.env,
|
|
1147
|
+
observability: props.observability,
|
|
1148
|
+
createdAt: now,
|
|
1149
|
+
updatedAt: now,
|
|
1150
|
+
eventSources: props.eventSources,
|
|
1151
|
+
url,
|
|
1152
|
+
dev: props.dev,
|
|
1153
|
+
// Include assets configuration in the output
|
|
1154
|
+
assets: props.assets,
|
|
1155
|
+
// Include cron triggers in the output
|
|
1156
|
+
crons: props.crons,
|
|
1157
|
+
// phantom property
|
|
1158
|
+
Env: undefined!,
|
|
1159
|
+
} as unknown as Worker<B>);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Create Cloudflare API client with automatic account discovery
|
|
1163
|
+
const api = await createCloudflareApi(props);
|
|
1164
|
+
|
|
935
1165
|
const uploadWorkerScript = async (props: WorkerProps<B>) => {
|
|
936
1166
|
// Get the script content - either from props.script, or by bundling
|
|
937
1167
|
const scriptBundle =
|
|
@@ -1229,6 +1459,7 @@ export const _Worker = Resource(
|
|
|
1229
1459
|
updatedAt: now,
|
|
1230
1460
|
eventSources: props.eventSources,
|
|
1231
1461
|
url: workerUrl,
|
|
1462
|
+
dev: props.dev,
|
|
1232
1463
|
// Include assets configuration in the output
|
|
1233
1464
|
assets: props.assets,
|
|
1234
1465
|
// Include cron triggers in the output
|
|
@@ -1288,6 +1519,55 @@ export async function deleteWorker(
|
|
|
1288
1519
|
return;
|
|
1289
1520
|
}
|
|
1290
1521
|
|
|
1522
|
+
function upsertDevCommand(props: {
|
|
1523
|
+
id: string;
|
|
1524
|
+
command: string;
|
|
1525
|
+
cwd: string;
|
|
1526
|
+
env: Record<string, string>;
|
|
1527
|
+
}) {
|
|
1528
|
+
const persistFile = path.join(process.cwd(), ".alchemy", `${props.id}.pid`);
|
|
1529
|
+
if (existsSync(persistFile)) {
|
|
1530
|
+
const pid = Number.parseInt(readFileSync(persistFile, "utf8"));
|
|
1531
|
+
try {
|
|
1532
|
+
// Actually kill the process if it's alive
|
|
1533
|
+
process.kill(pid, "SIGTERM");
|
|
1534
|
+
} catch {
|
|
1535
|
+
// ignore
|
|
1536
|
+
}
|
|
1537
|
+
try {
|
|
1538
|
+
unlinkSync(persistFile);
|
|
1539
|
+
} catch {
|
|
1540
|
+
// ignore
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
const command = props.command.split(" ");
|
|
1544
|
+
const proc = spawn(command[0], command.slice(1), {
|
|
1545
|
+
env: {
|
|
1546
|
+
...process.env,
|
|
1547
|
+
...props.env,
|
|
1548
|
+
ALCHEMY_CLOUDFLARE_PERSIST_PATH: path.join(
|
|
1549
|
+
process.cwd(),
|
|
1550
|
+
".alchemy",
|
|
1551
|
+
"miniflare",
|
|
1552
|
+
),
|
|
1553
|
+
},
|
|
1554
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
1555
|
+
});
|
|
1556
|
+
process.on("SIGINT", () => {
|
|
1557
|
+
try {
|
|
1558
|
+
unlinkSync(persistFile);
|
|
1559
|
+
} catch {
|
|
1560
|
+
// ignore
|
|
1561
|
+
}
|
|
1562
|
+
proc.kill("SIGINT");
|
|
1563
|
+
process.exit(0);
|
|
1564
|
+
});
|
|
1565
|
+
if (proc.pid) {
|
|
1566
|
+
mkdirSync(path.dirname(persistFile), { recursive: true });
|
|
1567
|
+
writeFileSync(persistFile, proc.pid.toString());
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1291
1571
|
type PutWorkerOptions = WorkerProps & {
|
|
1292
1572
|
dispatchNamespace?: string;
|
|
1293
1573
|
migrationTag?: string;
|
|
@@ -1564,21 +1844,7 @@ export async function configureURL<B extends Bindings>(
|
|
|
1564
1844
|
);
|
|
1565
1845
|
|
|
1566
1846
|
// Get the account's workers.dev subdomain
|
|
1567
|
-
const
|
|
1568
|
-
`/accounts/${api.accountId}/workers/subdomain`,
|
|
1569
|
-
);
|
|
1570
|
-
|
|
1571
|
-
if (!subdomainResponse.ok) {
|
|
1572
|
-
throw new Error(
|
|
1573
|
-
`Could not fetch workers.dev subdomain: ${subdomainResponse.status} ${subdomainResponse.statusText}`,
|
|
1574
|
-
);
|
|
1575
|
-
}
|
|
1576
|
-
const subdomainData: {
|
|
1577
|
-
result: {
|
|
1578
|
-
subdomain: string;
|
|
1579
|
-
};
|
|
1580
|
-
} = await subdomainResponse.json();
|
|
1581
|
-
const subdomain = subdomainData.result?.subdomain;
|
|
1847
|
+
const subdomain = await getAccountSubdomain(api);
|
|
1582
1848
|
|
|
1583
1849
|
if (subdomain) {
|
|
1584
1850
|
workerUrl = `https://${workerName}.${subdomain}.workers.dev`;
|
|
@@ -25,6 +25,13 @@ export interface WorkflowProps {
|
|
|
25
25
|
* @default - bound worker script
|
|
26
26
|
*/
|
|
27
27
|
scriptName?: string;
|
|
28
|
+
dev?: {
|
|
29
|
+
/**
|
|
30
|
+
* Whether to run the workflow remotely instead of locally
|
|
31
|
+
* @default false
|
|
32
|
+
*/
|
|
33
|
+
remote?: boolean;
|
|
34
|
+
};
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
export function isWorkflow(binding: Binding): binding is Workflow {
|
package/src/scope.ts
CHANGED
|
@@ -36,6 +36,7 @@ export interface ScopeOptions {
|
|
|
36
36
|
stateStore?: StateStoreType;
|
|
37
37
|
quiet?: boolean;
|
|
38
38
|
phase?: Phase;
|
|
39
|
+
dev?: boolean;
|
|
39
40
|
telemetryClient?: ITelemetryClient;
|
|
40
41
|
logger?: LoggerApi;
|
|
41
42
|
}
|
|
@@ -100,6 +101,7 @@ export class Scope {
|
|
|
100
101
|
public readonly stateStore: StateStoreType;
|
|
101
102
|
public readonly quiet: boolean;
|
|
102
103
|
public readonly phase: Phase;
|
|
104
|
+
public readonly dev?: boolean;
|
|
103
105
|
public readonly logger: LoggerApi;
|
|
104
106
|
public readonly telemetryClient: ITelemetryClient;
|
|
105
107
|
public readonly dataMutex: AsyncMutex;
|
|
@@ -111,7 +113,7 @@ export class Scope {
|
|
|
111
113
|
private deferred: (() => Promise<any>)[] = [];
|
|
112
114
|
|
|
113
115
|
constructor(options: ScopeOptions) {
|
|
114
|
-
this.appName = options.appName;
|
|
116
|
+
this.appName = options.appName ?? options.parent?.appName;
|
|
115
117
|
this.scopeName = options.scopeName ?? null;
|
|
116
118
|
if (this.scopeName?.includes(":")) {
|
|
117
119
|
throw new Error(
|
|
@@ -143,6 +145,14 @@ export class Scope {
|
|
|
143
145
|
options.logger,
|
|
144
146
|
);
|
|
145
147
|
|
|
148
|
+
this.dev = options.dev ?? this.parent?.dev ?? false;
|
|
149
|
+
|
|
150
|
+
if (this.dev) {
|
|
151
|
+
this.logger.warnOnce(
|
|
152
|
+
"Local development mode is in beta. Please report any issues to https://github.com/sam-goodwin/alchemy/issues.",
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
146
156
|
this.stateStore =
|
|
147
157
|
options.stateStore ??
|
|
148
158
|
this.parent?.stateStore ??
|
package/src/util/assert-never.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export function assertNever(value: never): never {
|
|
2
|
-
throw new Error(`Unexpected value: ${value}`);
|
|
1
|
+
export function assertNever(value: never, message?: string): never {
|
|
2
|
+
throw new Error(message ?? `Unexpected value: ${value}`);
|
|
3
3
|
}
|
package/src/util/cli.ts
CHANGED
|
@@ -2,6 +2,10 @@ import packageJson from "../../package.json" with { type: "json" };
|
|
|
2
2
|
import type { Phase } from "../alchemy.ts";
|
|
3
3
|
import { dedent } from "./dedent.ts";
|
|
4
4
|
|
|
5
|
+
declare global {
|
|
6
|
+
var _ALCHEMY_WARNINGS: Set<string> | undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
// ANSI color codes
|
|
6
10
|
const colors = {
|
|
7
11
|
reset: "\x1b[0m",
|
|
@@ -28,27 +32,28 @@ const colorize = (text: string, color: ColorName): string => {
|
|
|
28
32
|
return `${colors[color]}${text}${colors.reset}`;
|
|
29
33
|
};
|
|
30
34
|
|
|
31
|
-
export
|
|
35
|
+
export interface Task {
|
|
32
36
|
prefix?: string;
|
|
33
|
-
prefixColor?:
|
|
37
|
+
prefixColor?: ColorName;
|
|
34
38
|
resource?: string;
|
|
35
39
|
message: string;
|
|
36
40
|
status?: "pending" | "success" | "failure";
|
|
37
|
-
}
|
|
41
|
+
}
|
|
38
42
|
|
|
39
|
-
export
|
|
43
|
+
export interface LoggerApi {
|
|
40
44
|
log: (...args: unknown[]) => void;
|
|
41
45
|
warn: (...args: unknown[]) => void;
|
|
46
|
+
warnOnce: (message: string) => void;
|
|
42
47
|
error: (...args: unknown[]) => void;
|
|
43
48
|
task: (id: string, data: Task) => void;
|
|
44
49
|
exit: () => void;
|
|
45
|
-
}
|
|
50
|
+
}
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
interface AlchemyInfo {
|
|
48
53
|
phase: Phase;
|
|
49
54
|
stage: string;
|
|
50
55
|
appName: string;
|
|
51
|
-
}
|
|
56
|
+
}
|
|
52
57
|
|
|
53
58
|
let loggerApi: LoggerApi | null = null;
|
|
54
59
|
export const createLoggerInstance = (
|
|
@@ -67,6 +72,7 @@ export const createDummyLogger = (): LoggerApi => {
|
|
|
67
72
|
log: () => {},
|
|
68
73
|
error: () => {},
|
|
69
74
|
warn: () => {},
|
|
75
|
+
warnOnce: () => {},
|
|
70
76
|
task: () => {},
|
|
71
77
|
exit: () => {},
|
|
72
78
|
};
|
|
@@ -87,6 +93,12 @@ export const createFallbackLogger = (alchemyInfo: AlchemyInfo): LoggerApi => {
|
|
|
87
93
|
console.error(colorize("ERROR", "redBright"), ...args),
|
|
88
94
|
warn: (...args: unknown[]) =>
|
|
89
95
|
console.warn(colorize("WARN", "yellowBright"), ...args),
|
|
96
|
+
warnOnce: (message: string) => {
|
|
97
|
+
globalThis._ALCHEMY_WARNINGS ??= new Set();
|
|
98
|
+
if (globalThis._ALCHEMY_WARNINGS.has(message)) return;
|
|
99
|
+
globalThis._ALCHEMY_WARNINGS.add(message);
|
|
100
|
+
console.warn(colorize("WARN", "yellowBright"), message);
|
|
101
|
+
},
|
|
90
102
|
task: (_id: string, data: Task) => {
|
|
91
103
|
const prefix = data.prefix ? `[${data.prefix}]` : "";
|
|
92
104
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export class DeferredPromise<T> {
|
|
2
|
+
private promise = Promise.withResolvers<T>();
|
|
3
|
+
|
|
4
|
+
status: "pending" | "fulfilled" | "rejected" = "pending";
|
|
5
|
+
|
|
6
|
+
get value() {
|
|
7
|
+
return this.promise.promise;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
resolve(value: T) {
|
|
11
|
+
this.status = "fulfilled";
|
|
12
|
+
this.promise.resolve(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
reject(reason?: any) {
|
|
16
|
+
this.status = "rejected";
|
|
17
|
+
this.promise.reject(reason);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
|
|
3
|
+
export type PackageManager = "bun" | "pnpm" | "yarn" | "npm";
|
|
4
|
+
|
|
5
|
+
export function detectPackageManager(): PackageManager {
|
|
6
|
+
if (fs.existsSync("bun.lockb")) return "bun";
|
|
7
|
+
if (fs.existsSync("pnpm-lock.yaml")) return "pnpm";
|
|
8
|
+
if (fs.existsSync("yarn.lock")) return "yarn";
|
|
9
|
+
|
|
10
|
+
if (process.env.npm_execpath?.includes("bun")) {
|
|
11
|
+
return "bun";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
15
|
+
if (userAgent) {
|
|
16
|
+
if (userAgent.startsWith("bun")) return "bun";
|
|
17
|
+
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
18
|
+
if (userAgent.startsWith("yarn")) return "yarn";
|
|
19
|
+
if (userAgent.startsWith("npm")) return "npm";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return "npm";
|
|
23
|
+
}
|
package/src/util/logger.ts
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
type AsyncReturnType<T> = T extends (...args: any[]) => Promise<infer R>
|
|
2
|
+
? R
|
|
3
|
+
: T;
|
|
4
|
+
|
|
5
|
+
export function memoize<F extends (...args: any[]) => Promise<any>>(
|
|
6
|
+
fn: F,
|
|
7
|
+
keyFn: (...args: Parameters<F>) => string,
|
|
8
|
+
) {
|
|
9
|
+
const cache = new Map<string, Promise<AsyncReturnType<F>>>();
|
|
10
|
+
return async (...args: Parameters<F>): Promise<AsyncReturnType<F>> => {
|
|
11
|
+
const key = keyFn(...args);
|
|
12
|
+
const cached = cache.get(key);
|
|
13
|
+
if (cached) {
|
|
14
|
+
return await cached;
|
|
15
|
+
}
|
|
16
|
+
const promise = fn(...args).catch((error) => {
|
|
17
|
+
cache.delete(key);
|
|
18
|
+
throw error;
|
|
19
|
+
});
|
|
20
|
+
cache.set(key, promise);
|
|
21
|
+
return await promise;
|
|
22
|
+
};
|
|
23
|
+
}
|