alchemy 0.43.0 → 0.43.2

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.
Files changed (69) hide show
  1. package/README.md +1 -1
  2. package/bin/alchemy.mjs +508 -316
  3. package/lib/alchemy.d.ts.map +1 -1
  4. package/lib/alchemy.js +8 -2
  5. package/lib/alchemy.js.map +1 -1
  6. package/lib/build-date.d.ts +1 -1
  7. package/lib/build-date.js +1 -1
  8. package/lib/cloudflare/astro.d.ts +1 -1
  9. package/lib/cloudflare/astro.d.ts.map +1 -1
  10. package/lib/cloudflare/astro.js +1 -1
  11. package/lib/cloudflare/astro.js.map +1 -1
  12. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts +1 -0
  13. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +1 -1
  14. package/lib/cloudflare/bundle/bundle-worker-dev.js +2 -3
  15. package/lib/cloudflare/bundle/bundle-worker-dev.js.map +1 -1
  16. package/lib/cloudflare/bundle/bundle-worker.d.ts +1 -0
  17. package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -1
  18. package/lib/cloudflare/bundle/bundle-worker.js +2 -3
  19. package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
  20. package/lib/cloudflare/website.d.ts.map +1 -1
  21. package/lib/cloudflare/website.js +10 -22
  22. package/lib/cloudflare/website.js.map +1 -1
  23. package/lib/cloudflare/worker/http-server.d.ts.map +1 -1
  24. package/lib/cloudflare/worker/http-server.js +2 -1
  25. package/lib/cloudflare/worker/http-server.js.map +1 -1
  26. package/lib/cloudflare/worker/miniflare.d.ts +1 -0
  27. package/lib/cloudflare/worker/miniflare.d.ts.map +1 -1
  28. package/lib/cloudflare/worker/miniflare.js +2 -1
  29. package/lib/cloudflare/worker/miniflare.js.map +1 -1
  30. package/lib/cloudflare/worker.d.ts +11 -1
  31. package/lib/cloudflare/worker.d.ts.map +1 -1
  32. package/lib/cloudflare/worker.js +30 -15
  33. package/lib/cloudflare/worker.js.map +1 -1
  34. package/lib/cloudflare/wrangler.json.d.ts +1 -1
  35. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  36. package/lib/cloudflare/wrangler.json.js +28 -10
  37. package/lib/cloudflare/wrangler.json.js.map +1 -1
  38. package/lib/internal/docs/providers.js +2 -0
  39. package/lib/internal/docs/providers.js.map +1 -1
  40. package/lib/os/exec.d.ts +15 -1
  41. package/lib/os/exec.d.ts.map +1 -1
  42. package/lib/os/exec.js +30 -3
  43. package/lib/os/exec.js.map +1 -1
  44. package/lib/scope.d.ts.map +1 -1
  45. package/lib/scope.js +4 -1
  46. package/lib/scope.js.map +1 -1
  47. package/lib/util/deferred-promise.d.ts.map +1 -1
  48. package/lib/util/deferred-promise.js +2 -1
  49. package/lib/util/deferred-promise.js.map +1 -1
  50. package/lib/util/promise-with-resolvers.d.ts +14 -0
  51. package/lib/util/promise-with-resolvers.d.ts.map +1 -0
  52. package/lib/util/promise-with-resolvers.js +19 -0
  53. package/lib/util/promise-with-resolvers.js.map +1 -0
  54. package/package.json +4 -3
  55. package/src/alchemy.ts +8 -2
  56. package/src/build-date.ts +1 -1
  57. package/src/cloudflare/astro.ts +1 -1
  58. package/src/cloudflare/bundle/bundle-worker-dev.ts +3 -4
  59. package/src/cloudflare/bundle/bundle-worker.ts +3 -4
  60. package/src/cloudflare/website.ts +13 -30
  61. package/src/cloudflare/worker/http-server.ts +2 -1
  62. package/src/cloudflare/worker/miniflare.ts +5 -1
  63. package/src/cloudflare/worker.ts +56 -27
  64. package/src/cloudflare/wrangler.json.ts +40 -11
  65. package/src/internal/docs/providers.ts +2 -0
  66. package/src/os/exec.ts +32 -4
  67. package/src/scope.ts +5 -1
  68. package/src/util/deferred-promise.ts +3 -1
  69. package/src/util/promise-with-resolvers.ts +30 -0
@@ -27,7 +27,7 @@ export interface WranglerJsonProps {
27
27
  /**
28
28
  * Path to write the wrangler.json file to
29
29
  *
30
- * @default cwd/wrangler.json
30
+ * @default worker.cwd/wrangler.json
31
31
  */
32
32
  path?: string;
33
33
 
@@ -89,14 +89,27 @@ export const WranglerJson = Resource(
89
89
  _id: string,
90
90
  props: WranglerJsonProps,
91
91
  ): Promise<WranglerJson> {
92
- // Default path is wrangler.json in current directory
93
- const filePath = props.path || "wrangler.jsonc";
94
-
95
92
  if (this.phase === "delete") {
96
93
  return this.destroy();
97
94
  }
98
95
 
99
- if (props.worker.entrypoint === undefined) {
96
+ const cwd = props.worker.cwd
97
+ ? path.resolve(props.worker.cwd)
98
+ : process.cwd();
99
+
100
+ const toAbsolute = <T extends string | undefined>(input: T): T => {
101
+ return (input ? path.resolve(cwd, input) : undefined) as T;
102
+ };
103
+
104
+ const main = toAbsolute(props.main ?? props.worker.entrypoint);
105
+ let filePath = toAbsolute(props.path ?? cwd);
106
+ if (!path.basename(filePath).match(".json")) {
107
+ filePath = path.join(filePath, props.name ?? "wrangler.jsonc");
108
+ }
109
+
110
+ const dirname = path.dirname(filePath);
111
+
112
+ if (!main) {
100
113
  throw new Error(
101
114
  "Worker must have an entrypoint to generate a wrangler.json",
102
115
  );
@@ -107,16 +120,27 @@ export const WranglerJson = Resource(
107
120
  const spec: WranglerJsonSpec = {
108
121
  name: worker.name,
109
122
  // Use entrypoint as main if it exists
110
- main: props.main ?? worker.entrypoint,
123
+ main: path.relative(dirname, main),
111
124
  // see: https://developers.cloudflare.com/workers/configuration/compatibility-dates/
112
125
  compatibility_date: worker.compatibilityDate,
113
126
  compatibility_flags: props.worker.compatibilityFlags,
114
- assets: props.assets,
127
+ assets: props.assets
128
+ ? {
129
+ directory: toAbsolute(props.assets.directory),
130
+ binding: props.assets.binding,
131
+ }
132
+ : undefined,
115
133
  };
116
134
 
117
135
  // Process bindings if they exist
118
136
  if (worker.bindings) {
119
- processBindings(spec, worker.bindings, worker.eventSources, worker.name);
137
+ processBindings(
138
+ spec,
139
+ worker.bindings,
140
+ worker.eventSources,
141
+ worker.name,
142
+ cwd,
143
+ );
120
144
  }
121
145
 
122
146
  // Add environment variables as vars
@@ -128,13 +152,17 @@ export const WranglerJson = Resource(
128
152
  spec.triggers = { crons: worker.crons };
129
153
  }
130
154
 
131
- await fs.mkdir(path.dirname(filePath), { recursive: true });
155
+ if (spec.assets) {
156
+ spec.assets.directory = path.relative(dirname, spec.assets.directory);
157
+ }
158
+
159
+ await fs.mkdir(dirname, { recursive: true });
132
160
  await fs.writeFile(filePath, await formatJson(spec));
133
161
 
134
162
  // Return the resource
135
163
  return this({
136
164
  ...props,
137
- path: filePath,
165
+ path: path.relative(cwd, filePath),
138
166
  spec,
139
167
  createdAt: Date.now(),
140
168
  updatedAt: Date.now(),
@@ -381,6 +409,7 @@ function processBindings(
381
409
  bindings: Bindings,
382
410
  eventSources: EventSource[] | undefined,
383
411
  workerName: string,
412
+ workerCwd: string,
384
413
  ): void {
385
414
  // Arrays to collect different binding types
386
415
  const kvNamespaces: { binding: string; id: string; preview_id: string }[] =
@@ -527,7 +556,7 @@ function processBindings(
527
556
  secrets.push(bindingName);
528
557
  } else if (binding.type === "assets") {
529
558
  spec.assets = {
530
- directory: binding.path,
559
+ directory: path.resolve(workerCwd, binding.path),
531
560
  binding: bindingName,
532
561
  };
533
562
  } else if (binding.type === "workflow") {
@@ -199,6 +199,8 @@ async function generateProviderDocs({
199
199
  See ${alchemy.file("./README.md")} to understand the overview of Alchemy.
200
200
  See ${alchemy.file("./.cursorrules")} to better understand the structure and convention of an Alchemy Resource.
201
201
 
202
+ Documentation should be written in the mdx format for an Astro Starlight documentation site.
203
+
202
204
  Relevant files for the ${providerName} Service:
203
205
  ${alchemy.files(files)}
204
206
 
package/src/os/exec.ts CHANGED
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
3
3
  import { join } from "node:path";
4
4
  import type { Context } from "../context.ts";
5
5
  import { Resource } from "../resource.ts";
6
+ import type { Secret } from "../secret.ts";
6
7
 
7
8
  /**
8
9
  * Properties for executing a shell command
@@ -47,7 +48,7 @@ export interface ExecProps {
47
48
  /**
48
49
  * Environment variables to set
49
50
  */
50
- env?: Record<string, string>;
51
+ env?: Record<string, string | Secret<string>>;
51
52
 
52
53
  /**
53
54
  * Whether to inherit stdio from parent process
@@ -123,6 +124,19 @@ export interface Exec extends Resource<"os::Exec">, ExecProps {
123
124
  * });
124
125
  *
125
126
  * @example
127
+ * // Run a command with secrets in environment variables
128
+ * import alchemy from "alchemy";
129
+ *
130
+ * const deploy = await Exec("deploy-app", {
131
+ * command: "npm run deploy",
132
+ * env: {
133
+ * NODE_ENV: "production",
134
+ * API_KEY: alchemy.secret(process.env.API_KEY),
135
+ * DATABASE_URL: alchemy.secret(process.env.DATABASE_URL)
136
+ * }
137
+ * });
138
+ *
139
+ * @example
126
140
  * // Run a memoized command that only re-executes when the command changes
127
141
  * const memoizedCmd = await Exec("status-check", {
128
142
  * command: "git status",
@@ -198,10 +212,23 @@ export const Exec = Resource(
198
212
  // Parse the command into command and args
199
213
  const [cmd, ...args] = props.command.split(/\s+/);
200
214
 
215
+ // Process environment variables, extracting values from secrets
216
+ const processedEnv: Record<string, string> = {};
217
+ if (props.env) {
218
+ for (const [key, value] of Object.entries(props.env)) {
219
+ if (typeof value === "string") {
220
+ processedEnv[key] = value;
221
+ } else {
222
+ // Handle Secret objects
223
+ processedEnv[key] = value.unencrypted;
224
+ }
225
+ }
226
+ }
227
+
201
228
  // Use spawn for better stdio control
202
229
  const childProcess = spawn(cmd, args, {
203
230
  cwd: props.cwd || process.cwd(),
204
- env: { ...process.env, ...props.env },
231
+ env: { ...process.env, ...processedEnv },
205
232
  shell: true, // Use shell to handle complex commands
206
233
  stdio: inheritStdio ? "inherit" : "pipe", // Inherit stdio when requested
207
234
  });
@@ -342,13 +369,14 @@ export async function exec(
342
369
  }
343
370
 
344
371
  async function hashInputs(cwd: string, patterns: string[]) {
345
- const { glob, readFile } = await import("node:fs/promises");
372
+ const { glob } = await import("glob");
373
+ const { readFile } = await import("node:fs/promises");
346
374
 
347
375
  const hashes = new Map<string, string>();
348
376
 
349
377
  await Promise.all(
350
378
  patterns.flatMap(async (pattern) => {
351
- const files = await Array.fromAsync(glob(pattern, { cwd }));
379
+ const files = await glob(pattern, { cwd });
352
380
  return Promise.all(
353
381
  files.map(async (file: string) => {
354
382
  const path = join(cwd, file);
package/src/scope.ts CHANGED
@@ -48,7 +48,11 @@ export type PendingDeletions = Array<{
48
48
  }>;
49
49
 
50
50
  // TODO: support browser
51
- const DEFAULT_STAGE = process.env.ALCHEMY_STAGE ?? process.env.USER ?? "dev";
51
+ const DEFAULT_STAGE =
52
+ process.env.ALCHEMY_STAGE ??
53
+ process.env.USER ??
54
+ process.env.USERNAME ??
55
+ "dev";
52
56
 
53
57
  declare global {
54
58
  var __ALCHEMY_STORAGE__: AsyncLocalStorage<Scope>;
@@ -1,5 +1,7 @@
1
+ import { promiseWithResolvers } from "./promise-with-resolvers.ts";
2
+
1
3
  export class DeferredPromise<T> {
2
- private promise = Promise.withResolvers<T>();
4
+ private promise = promiseWithResolvers<T>();
3
5
 
4
6
  status: "pending" | "fulfilled" | "rejected" = "pending";
5
7
 
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Interface for the return type of promiseWithResolvers
3
+ */
4
+ export interface PromiseWithResolvers<T> {
5
+ promise: Promise<T>;
6
+ resolve: (value: T | PromiseLike<T>) => void;
7
+ reject: (reason?: any) => void;
8
+ }
9
+
10
+ /**
11
+ * Node 20+ compatible implementation of Promise.withResolvers()
12
+ * Falls back to native Promise.withResolvers when available (Node 22+)
13
+ */
14
+ export function promiseWithResolvers<T>(): PromiseWithResolvers<T> {
15
+ // Use native implementation if available
16
+ if (typeof Promise.withResolvers === "function") {
17
+ return Promise.withResolvers<T>();
18
+ }
19
+
20
+ // Fallback implementation for Node 20+
21
+ let resolve!: (value: T | PromiseLike<T>) => void;
22
+ let reject!: (reason?: any) => void;
23
+
24
+ const promise = new Promise<T>((res, rej) => {
25
+ resolve = res;
26
+ reject = rej;
27
+ });
28
+
29
+ return { promise, resolve, reject };
30
+ }