alchemy 0.43.4 → 0.44.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.mjs +430 -346
- package/bin/alchemy.ts +7 -21
- package/bin/commands/create.ts +79 -23
- package/bin/services/package-manager.ts +0 -3
- package/bin/services/template-manager.ts +9 -4
- package/bin/services/vibe-rules.ts +47 -0
- package/bin/types.ts +25 -5
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +8 -11
- package/lib/alchemy.js.map +1 -1
- package/lib/apply.js +2 -0
- package/lib/apply.js.map +1 -1
- package/lib/build-date.d.ts +1 -1
- package/lib/build-date.js +1 -1
- package/lib/cloudflare/assets.d.ts +1 -1
- package/lib/cloudflare/assets.d.ts.map +1 -1
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +9 -6
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker-dev.js +4 -1
- package/lib/cloudflare/bundle/bundle-worker-dev.js.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker.js +4 -1
- package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
- package/lib/cloudflare/bundle/external.d.ts +1 -0
- package/lib/cloudflare/bundle/external.d.ts.map +1 -1
- package/lib/cloudflare/bundle/external.js +1 -1
- package/lib/cloudflare/bundle/external.js.map +1 -1
- package/lib/cloudflare/bundle/nodejs-import-warning-plugin.d.ts +10 -0
- package/lib/cloudflare/bundle/nodejs-import-warning-plugin.d.ts.map +1 -0
- package/lib/cloudflare/bundle/nodejs-import-warning-plugin.js +67 -0
- package/lib/cloudflare/bundle/nodejs-import-warning-plugin.js.map +1 -0
- package/lib/cloudflare/container.d.ts +27 -2
- package/lib/cloudflare/container.d.ts.map +1 -1
- package/lib/cloudflare/container.js +82 -24
- package/lib/cloudflare/container.js.map +1 -1
- package/lib/cloudflare/redirect-rule.js +1 -1
- package/lib/cloudflare/redirect-rule.js.map +1 -1
- package/lib/cloudflare/website.d.ts +17 -0
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +1 -0
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker/miniflare.d.ts +4 -1
- package/lib/cloudflare/worker/miniflare.d.ts.map +1 -1
- package/lib/cloudflare/worker/miniflare.js +22 -2
- package/lib/cloudflare/worker/miniflare.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +12 -0
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +24 -0
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +20 -2
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/context.d.ts +6 -1
- package/lib/context.d.ts.map +1 -1
- package/lib/context.js +2 -1
- package/lib/context.js.map +1 -1
- package/lib/destroy.d.ts.map +1 -1
- package/lib/destroy.js +1 -0
- package/lib/destroy.js.map +1 -1
- package/lib/docker/api.d.ts +11 -0
- package/lib/docker/api.d.ts.map +1 -1
- package/lib/docker/api.js +41 -1
- package/lib/docker/api.js.map +1 -1
- package/lib/docker/image.d.ts +1 -1
- package/lib/docker/image.d.ts.map +1 -1
- package/lib/docker/image.js +11 -5
- package/lib/docker/image.js.map +1 -1
- package/lib/esbuild/bundle.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/llms.cloudflare.txt +379 -0
- package/lib/llms.d.ts +10 -0
- package/lib/llms.d.ts.map +1 -0
- package/lib/llms.js +12 -0
- package/lib/llms.js.map +1 -0
- package/lib/planetscale/index.d.ts +3 -2
- package/lib/planetscale/index.d.ts.map +1 -1
- package/lib/planetscale/index.js +3 -2
- package/lib/planetscale/index.js.map +1 -1
- package/lib/planetscale/password.d.ts +233 -0
- package/lib/planetscale/password.d.ts.map +1 -0
- package/lib/planetscale/password.js +238 -0
- package/lib/planetscale/password.js.map +1 -0
- package/lib/scope.d.ts +6 -5
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +18 -7
- package/lib/scope.js.map +1 -1
- package/lib/test/bun.d.ts.map +1 -1
- package/lib/test/bun.js +1 -0
- package/lib/test/bun.js.map +1 -1
- package/lib/test/vitest.d.ts.map +1 -1
- package/lib/test/vitest.js +1 -0
- package/lib/test/vitest.js.map +1 -1
- package/lib/util/nanoid.d.ts +3 -0
- package/lib/util/nanoid.d.ts.map +1 -0
- package/lib/util/nanoid.js +13 -0
- package/lib/util/nanoid.js.map +1 -0
- package/lib/util/safe-fetch.d.ts.map +1 -1
- package/lib/util/safe-fetch.js +0 -1
- package/lib/util/safe-fetch.js.map +1 -1
- package/lib/util/telemetry/client.d.ts.map +1 -1
- package/lib/util/telemetry/client.js +13 -3
- package/lib/util/telemetry/client.js.map +1 -1
- package/lib/util/telemetry/constants.d.ts +2 -1
- package/lib/util/telemetry/constants.d.ts.map +1 -1
- package/lib/util/telemetry/constants.js +3 -2
- package/lib/util/telemetry/constants.js.map +1 -1
- package/package.json +10 -2
- package/src/alchemy.ts +8 -13
- package/src/apply.ts +2 -0
- package/src/build-date.ts +1 -1
- package/src/cloudflare/bucket.ts +22 -14
- package/src/cloudflare/bundle/bundle-worker-dev.ts +4 -1
- package/src/cloudflare/bundle/bundle-worker.ts +4 -1
- package/src/cloudflare/bundle/external.ts +1 -1
- package/src/cloudflare/bundle/nodejs-import-warning-plugin.ts +94 -0
- package/src/cloudflare/container.ts +113 -28
- package/src/cloudflare/redirect-rule.ts +1 -1
- package/src/cloudflare/website.ts +21 -1
- package/src/cloudflare/worker/miniflare.ts +33 -8
- package/src/cloudflare/worker-metadata.ts +15 -0
- package/src/cloudflare/wrangler.json.ts +61 -5
- package/src/context.ts +7 -0
- package/src/destroy.ts +1 -0
- package/src/docker/api.ts +57 -1
- package/src/docker/image.ts +15 -6
- package/src/index.ts +1 -0
- package/src/llms.cloudflare.txt +379 -0
- package/src/llms.ts +25 -0
- package/src/planetscale/index.ts +3 -2
- package/src/planetscale/password.ts +366 -0
- package/src/scope.ts +25 -11
- package/src/test/bun.ts +1 -0
- package/src/test/vitest.ts +1 -0
- package/src/util/nanoid.ts +13 -0
- package/src/util/safe-fetch.ts +0 -1
- package/src/util/telemetry/client.ts +17 -3
- package/src/util/telemetry/constants.ts +5 -3
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { alchemy } from "../alchemy.ts";
|
|
2
|
+
import type { Context } from "../context.ts";
|
|
3
|
+
import { Resource } from "../resource.ts";
|
|
4
|
+
import type { Secret } from "../secret.ts";
|
|
5
|
+
import { logger } from "../util/logger.ts";
|
|
6
|
+
import { lowercaseId } from "../util/nanoid.ts";
|
|
7
|
+
import { PlanetScaleApi } from "./api.ts";
|
|
8
|
+
import type { Branch } from "./branch.ts";
|
|
9
|
+
import type { Database } from "./database.ts";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Properties for creating or updating a PlanetScale Branch
|
|
13
|
+
*/
|
|
14
|
+
export interface PasswordProps {
|
|
15
|
+
/**
|
|
16
|
+
* The name of the password
|
|
17
|
+
*/
|
|
18
|
+
name: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The organization ID where the password will be created
|
|
22
|
+
* Required when using string database name, optional when using Database resource
|
|
23
|
+
*/
|
|
24
|
+
organizationId?: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The database where the password will be created
|
|
28
|
+
* Can be either a database name (string) or Database resource
|
|
29
|
+
*/
|
|
30
|
+
database: string | Database;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The branch where the password will be created
|
|
34
|
+
* Can be either a branch name (string) or Branch resource
|
|
35
|
+
* Defaults to "main" if not provided
|
|
36
|
+
*/
|
|
37
|
+
branch?: string | Branch;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* PlanetScale API token (overrides environment variable)
|
|
41
|
+
*/
|
|
42
|
+
apiKey?: Secret;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The password
|
|
46
|
+
*/
|
|
47
|
+
role: "reader" | "writer" | "admin" | "readwriter";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Whether the password is for a read replica
|
|
51
|
+
*/
|
|
52
|
+
replica?: boolean;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The TTL of the password in seconds
|
|
56
|
+
*/
|
|
57
|
+
ttl?: number;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The CIDRs of the password
|
|
61
|
+
*/
|
|
62
|
+
cidrs?: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Represents a PlanetScale Branch
|
|
67
|
+
*/
|
|
68
|
+
export interface Password
|
|
69
|
+
extends Resource<"planetscale::Password">,
|
|
70
|
+
PasswordProps {
|
|
71
|
+
/**
|
|
72
|
+
* The unique identifier for the password
|
|
73
|
+
*/
|
|
74
|
+
id: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The timestamp when the password expires (ISO 8601 format)
|
|
78
|
+
*/
|
|
79
|
+
expiresAt: string;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The host URL for database connection
|
|
83
|
+
*/
|
|
84
|
+
host: string;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The username for database authentication
|
|
88
|
+
*/
|
|
89
|
+
username: string;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The encrypted password for database authentication
|
|
93
|
+
*/
|
|
94
|
+
password: Secret<string>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The name slug for the password
|
|
98
|
+
*/
|
|
99
|
+
nameSlug: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Create and manage database passwords for PlanetScale branches. Database passwords provide secure access to your database with specific roles and permissions.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ## Basic Reader Password
|
|
107
|
+
*
|
|
108
|
+
* Create a read-only password for a database branch:
|
|
109
|
+
*
|
|
110
|
+
* ```ts
|
|
111
|
+
* import { Password } from "alchemy/planetscale";
|
|
112
|
+
*
|
|
113
|
+
* const readerPassword = await Password("app-reader", {
|
|
114
|
+
* name: "app-reader",
|
|
115
|
+
* organizationId: "my-org",
|
|
116
|
+
* database: "my-app-db",
|
|
117
|
+
* branch: "main",
|
|
118
|
+
* role: "reader"
|
|
119
|
+
* });
|
|
120
|
+
*
|
|
121
|
+
* // Access connection details
|
|
122
|
+
* console.log(`Host: ${readerPassword.password.host}`);
|
|
123
|
+
* console.log(`Username: ${readerPassword.password.username}`);
|
|
124
|
+
* console.log(`Password: ${readerPassword.password.password.unencrypted}`);
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ## Writer Password with TTL
|
|
129
|
+
*
|
|
130
|
+
* Create a writer password that expires after 24 hours:
|
|
131
|
+
*
|
|
132
|
+
* ```ts
|
|
133
|
+
* import { Password } from "alchemy/planetscale";
|
|
134
|
+
*
|
|
135
|
+
* const writerPassword = await Password("app-writer", {
|
|
136
|
+
* name: "app-writer",
|
|
137
|
+
* organizationId: "my-org",
|
|
138
|
+
* database: "my-app-db",
|
|
139
|
+
* branch: "development",
|
|
140
|
+
* role: "writer",
|
|
141
|
+
* ttl: 86400 // 24 hours in seconds
|
|
142
|
+
* });
|
|
143
|
+
*
|
|
144
|
+
* // Password will expire at the specified time
|
|
145
|
+
* console.log(`Expires at: ${writerPassword.expiresAt}`);
|
|
146
|
+
* ```
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ## Admin Password with IP Restrictions
|
|
150
|
+
*
|
|
151
|
+
* Create an admin password that only allows connections from specific IP addresses:
|
|
152
|
+
*
|
|
153
|
+
* ```ts
|
|
154
|
+
* import { Password } from "alchemy/planetscale";
|
|
155
|
+
*
|
|
156
|
+
* const adminPassword = await Password("admin-access", {
|
|
157
|
+
* name: "admin-access",
|
|
158
|
+
* organizationId: "my-org",
|
|
159
|
+
* database: "my-app-db",
|
|
160
|
+
* branch: "main",
|
|
161
|
+
* role: "admin",
|
|
162
|
+
* cidrs: ["203.0.113.0/24", "198.51.100.0/24"],
|
|
163
|
+
* ttl: 3600 // 1 hour
|
|
164
|
+
* });
|
|
165
|
+
* ```
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ## Database Password with Custom API Key
|
|
169
|
+
*
|
|
170
|
+
* Create a password using a specific API key instead of the default environment variable:
|
|
171
|
+
*
|
|
172
|
+
* ```ts
|
|
173
|
+
* import { Password } from "alchemy/planetscale";
|
|
174
|
+
*
|
|
175
|
+
* const password = await Password("custom-auth", {
|
|
176
|
+
* name: "custom-auth",
|
|
177
|
+
* organizationId: "my-org",
|
|
178
|
+
* database: "my-app-db",
|
|
179
|
+
* branch: "main",
|
|
180
|
+
* role: "readwriter",
|
|
181
|
+
* apiKey: alchemy.secret(process.env.CUSTOM_PLANETSCALE_TOKEN)
|
|
182
|
+
* });
|
|
183
|
+
* ```
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ## Read Replica Password
|
|
187
|
+
*
|
|
188
|
+
* Create a password for accessing a read replica:
|
|
189
|
+
*
|
|
190
|
+
* ```ts
|
|
191
|
+
* import { Password } from "alchemy/planetscale";
|
|
192
|
+
*
|
|
193
|
+
* const replicaPassword = await Password("replica-reader", {
|
|
194
|
+
* name: "replica-reader",
|
|
195
|
+
* organizationId: "my-org",
|
|
196
|
+
* database: "my-app-db",
|
|
197
|
+
* branch: "main",
|
|
198
|
+
* role: "reader",
|
|
199
|
+
* replica: true
|
|
200
|
+
* });
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ## Using Database Resource Instance
|
|
204
|
+
*
|
|
205
|
+
* Create a password using a Database resource instead of string:
|
|
206
|
+
*
|
|
207
|
+
* ```ts
|
|
208
|
+
* import { Database, Password } from "alchemy/planetscale";
|
|
209
|
+
*
|
|
210
|
+
* const database = await Database("my-db", {
|
|
211
|
+
* name: "my-app-db",
|
|
212
|
+
* organizationId: "my-org",
|
|
213
|
+
* clusterSize: "PS_10"
|
|
214
|
+
* });
|
|
215
|
+
*
|
|
216
|
+
* const password = await Password("db-reader", {
|
|
217
|
+
* name: "db-reader",
|
|
218
|
+
* database: database, // Using Database resource
|
|
219
|
+
* role: "reader"
|
|
220
|
+
* });
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ## Using Both Database and Branch Resources
|
|
225
|
+
*
|
|
226
|
+
* Create a password using both Database and Branch resources:
|
|
227
|
+
*
|
|
228
|
+
* ```ts
|
|
229
|
+
* import { Database, Branch, Password } from "alchemy/planetscale";
|
|
230
|
+
*
|
|
231
|
+
* const database = await Database("my-db", {
|
|
232
|
+
* name: "my-app-db",
|
|
233
|
+
* organizationId: "my-org",
|
|
234
|
+
* clusterSize: "PS_10"
|
|
235
|
+
* });
|
|
236
|
+
*
|
|
237
|
+
* const branch = await Branch("feature-branch", {
|
|
238
|
+
* name: "feature-branch",
|
|
239
|
+
* organizationId: "my-org",
|
|
240
|
+
* databaseName: "my-app-db",
|
|
241
|
+
* parentBranch: "main",
|
|
242
|
+
* isProduction: false
|
|
243
|
+
* });
|
|
244
|
+
*
|
|
245
|
+
* const password = await Password("feature-writer", {
|
|
246
|
+
* name: "feature-writer",
|
|
247
|
+
* database: database, // Using Database resource
|
|
248
|
+
* branch: branch, // Using Branch resource
|
|
249
|
+
* role: "writer"
|
|
250
|
+
* });
|
|
251
|
+
* ```
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
export const Password = Resource(
|
|
255
|
+
"planetscale::Password",
|
|
256
|
+
async function (
|
|
257
|
+
this: Context<Password>,
|
|
258
|
+
_id: string,
|
|
259
|
+
props: PasswordProps,
|
|
260
|
+
): Promise<Password> {
|
|
261
|
+
const apiKey =
|
|
262
|
+
props.apiKey?.unencrypted || process.env.PLANETSCALE_API_TOKEN;
|
|
263
|
+
if (!apiKey) {
|
|
264
|
+
throw new Error("PLANETSCALE_API_TOKEN environment variable is required");
|
|
265
|
+
}
|
|
266
|
+
const nameSlug = this.isReplacement
|
|
267
|
+
? lowercaseId()
|
|
268
|
+
: (this.output?.nameSlug ?? lowercaseId());
|
|
269
|
+
const name = `${props.name.toLowerCase()}-${nameSlug}`;
|
|
270
|
+
|
|
271
|
+
const api = new PlanetScaleApi({ apiKey });
|
|
272
|
+
const branchName =
|
|
273
|
+
props.branch == null
|
|
274
|
+
? "main"
|
|
275
|
+
: typeof props.branch === "string"
|
|
276
|
+
? props.branch
|
|
277
|
+
: props.branch.name;
|
|
278
|
+
const databaseName =
|
|
279
|
+
typeof props.database === "string" ? props.database : props.database.name;
|
|
280
|
+
|
|
281
|
+
if (this.phase === "delete") {
|
|
282
|
+
try {
|
|
283
|
+
if (this.output?.name) {
|
|
284
|
+
const response = await api.delete(
|
|
285
|
+
`/organizations/${props.organizationId}/databases/${databaseName}/branches/${branchName}/passwords/${this.output.id}`,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
if (!response.ok && response.status !== 404) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
`Failed to delete branch: ${response.statusText} ${await response.text()}`,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
logger.error("Error deleting password:", error);
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
return this.destroy();
|
|
299
|
+
}
|
|
300
|
+
if (this.phase === "update") {
|
|
301
|
+
if (
|
|
302
|
+
this.output?.name === name &&
|
|
303
|
+
((this.output?.cidrs === undefined && props.cidrs === undefined) ||
|
|
304
|
+
(Array.isArray(this.output?.cidrs) &&
|
|
305
|
+
Array.isArray(props.cidrs) &&
|
|
306
|
+
this.output.cidrs.length === props.cidrs.length &&
|
|
307
|
+
this.output.cidrs.every((cidr, i) => cidr === props.cidrs![i])))
|
|
308
|
+
) {
|
|
309
|
+
return this.replace();
|
|
310
|
+
}
|
|
311
|
+
const updateResponse = await api.patch(
|
|
312
|
+
`/organizations/${props.organizationId}/databases/${databaseName}/branches/${branchName}/passwords/${this.output.id}`,
|
|
313
|
+
{
|
|
314
|
+
name,
|
|
315
|
+
cidrs: props.cidrs,
|
|
316
|
+
},
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
if (!updateResponse.ok) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
`Failed to update password: ${updateResponse.statusText} ${await updateResponse.text()}`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return this({
|
|
326
|
+
...this.output,
|
|
327
|
+
...props,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
const createResponse = await api.post(
|
|
333
|
+
`/organizations/${props.organizationId}/databases/${databaseName}/branches/${branchName}/passwords`,
|
|
334
|
+
{
|
|
335
|
+
name,
|
|
336
|
+
role: props.role,
|
|
337
|
+
replica: props.replica,
|
|
338
|
+
ttl: props.ttl,
|
|
339
|
+
cidrs: props.cidrs,
|
|
340
|
+
},
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
if (!createResponse.ok) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`Failed to create password: ${createResponse.statusText} ${await createResponse.text()}`,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const data = await createResponse.json<any>();
|
|
350
|
+
|
|
351
|
+
return this({
|
|
352
|
+
id: data.id,
|
|
353
|
+
expiresAt: data.expires_at,
|
|
354
|
+
host: data.access_host_url,
|
|
355
|
+
username: data.username,
|
|
356
|
+
password: alchemy.secret(data.plain_text),
|
|
357
|
+
nameSlug,
|
|
358
|
+
...props,
|
|
359
|
+
name: `${props.name}-${nameSlug}`,
|
|
360
|
+
});
|
|
361
|
+
} catch (error) {
|
|
362
|
+
logger.error("Error managing password:", error);
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
);
|
package/src/scope.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import util from "node:util";
|
|
3
3
|
import type { Phase } from "./alchemy.ts";
|
|
4
|
+
import { DOStateStore } from "./cloudflare/do-state-store/index.ts";
|
|
4
5
|
import { destroy, destroyAll } from "./destroy.ts";
|
|
5
6
|
import { FileSystemStateStore } from "./fs/file-system-state-store.ts";
|
|
6
7
|
import {
|
|
@@ -29,10 +30,9 @@ export class RootScopeStateAttemptError extends Error {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export interface ScopeOptions {
|
|
32
|
-
appName?: string;
|
|
33
33
|
stage?: string;
|
|
34
|
-
parent
|
|
35
|
-
scopeName
|
|
34
|
+
parent: Scope | undefined;
|
|
35
|
+
scopeName: string;
|
|
36
36
|
password?: string;
|
|
37
37
|
stateStore?: StateStoreType;
|
|
38
38
|
quiet?: boolean;
|
|
@@ -48,7 +48,7 @@ export type PendingDeletions = Array<{
|
|
|
48
48
|
}>;
|
|
49
49
|
|
|
50
50
|
// TODO: support browser
|
|
51
|
-
const DEFAULT_STAGE =
|
|
51
|
+
export const DEFAULT_STAGE =
|
|
52
52
|
process.env.ALCHEMY_STAGE ??
|
|
53
53
|
process.env.USER ??
|
|
54
54
|
process.env.USERNAME ??
|
|
@@ -97,9 +97,9 @@ export class Scope {
|
|
|
97
97
|
|
|
98
98
|
public readonly resources = new Map<ResourceID, PendingResource>();
|
|
99
99
|
public readonly children: Map<ResourceID, Scope> = new Map();
|
|
100
|
-
public readonly appName: string | undefined;
|
|
101
100
|
public readonly stage: string;
|
|
102
|
-
public readonly
|
|
101
|
+
public readonly name: string;
|
|
102
|
+
public readonly scopeName: string;
|
|
103
103
|
public readonly parent: Scope | undefined;
|
|
104
104
|
public readonly password: string | undefined;
|
|
105
105
|
public readonly state: StateStore;
|
|
@@ -117,15 +117,26 @@ export class Scope {
|
|
|
117
117
|
|
|
118
118
|
private deferred: (() => Promise<any>)[] = [];
|
|
119
119
|
|
|
120
|
+
public get appName(): string {
|
|
121
|
+
if (this.parent) {
|
|
122
|
+
return this.parent.appName;
|
|
123
|
+
}
|
|
124
|
+
return this.scopeName;
|
|
125
|
+
}
|
|
126
|
+
|
|
120
127
|
constructor(options: ScopeOptions) {
|
|
121
|
-
this.
|
|
122
|
-
this.
|
|
123
|
-
|
|
128
|
+
this.scopeName = options.scopeName;
|
|
129
|
+
this.name = this.scopeName;
|
|
130
|
+
this.parent = options.parent ?? Scope.getScope();
|
|
131
|
+
|
|
132
|
+
const isChild = this.parent !== undefined;
|
|
133
|
+
if (this.scopeName?.includes(":") && isChild) {
|
|
134
|
+
// TODO(sam): relax this constraint once we move to SQLite3 store
|
|
124
135
|
throw new Error(
|
|
125
136
|
`Scope name "${this.scopeName}" cannot contain double colons`,
|
|
126
137
|
);
|
|
127
138
|
}
|
|
128
|
-
|
|
139
|
+
|
|
129
140
|
this.stage = options?.stage ?? this.parent?.stage ?? DEFAULT_STAGE;
|
|
130
141
|
this.parent?.children.set(this.scopeName!, this);
|
|
131
142
|
this.quiet = options.quiet ?? this.parent?.quiet ?? false;
|
|
@@ -161,7 +172,10 @@ export class Scope {
|
|
|
161
172
|
this.stateStore =
|
|
162
173
|
options.stateStore ??
|
|
163
174
|
this.parent?.stateStore ??
|
|
164
|
-
((scope) =>
|
|
175
|
+
((scope) =>
|
|
176
|
+
process.env.ALCHEMY_STATE_STORE === "cloudflare"
|
|
177
|
+
? new DOStateStore(scope)
|
|
178
|
+
: new FileSystemStateStore(scope));
|
|
165
179
|
this.state = this.stateStore(this);
|
|
166
180
|
if (!options.telemetryClient && !this.parent?.telemetryClient) {
|
|
167
181
|
throw new Error("Telemetry client is required");
|
package/src/test/bun.ts
CHANGED
|
@@ -133,6 +133,7 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
|
|
|
133
133
|
|
|
134
134
|
// Create local test scope based on filename
|
|
135
135
|
const scope = new Scope({
|
|
136
|
+
parent: undefined,
|
|
136
137
|
scopeName: `${defaultOptions.prefix ? `${defaultOptions.prefix}-` : ""}${path.basename(meta.filename)}`,
|
|
137
138
|
// parent: globalTestScope,
|
|
138
139
|
stateStore: defaultOptions?.stateStore,
|
package/src/test/vitest.ts
CHANGED
|
@@ -139,6 +139,7 @@ export function test(
|
|
|
139
139
|
};
|
|
140
140
|
|
|
141
141
|
const scope = new Scope({
|
|
142
|
+
parent: undefined,
|
|
142
143
|
scopeName: `${defaultOptions.prefix ? `${defaultOptions.prefix}-` : ""}${path.basename(meta.filename)}`,
|
|
143
144
|
stateStore: defaultOptions?.stateStore,
|
|
144
145
|
phase: "up",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const lowercaseId = idGenerator("abcdefghijklmnopqrstuvwxyz", 10);
|
|
2
|
+
|
|
3
|
+
export function idGenerator(alphabet: string, defaultLength: number) {
|
|
4
|
+
return function generateId(length: number = defaultLength): string {
|
|
5
|
+
let id = "";
|
|
6
|
+
const alphabetLength = alphabet.length;
|
|
7
|
+
for (let i = 0; i < length; i++) {
|
|
8
|
+
const randomIndex = Math.floor(Math.random() * alphabetLength);
|
|
9
|
+
id += alphabet[randomIndex];
|
|
10
|
+
}
|
|
11
|
+
return id;
|
|
12
|
+
};
|
|
13
|
+
}
|
package/src/util/safe-fetch.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import type { Phase } from "../../alchemy.ts";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
POSTHOG_CLIENT_API_HOST,
|
|
5
|
+
POSTHOG_PROJECT_ID,
|
|
6
|
+
TELEMETRY_DISABLED,
|
|
7
|
+
} from "./constants.ts";
|
|
4
8
|
import { context } from "./context.ts";
|
|
5
9
|
import type { Telemetry } from "./types.ts";
|
|
6
10
|
|
|
@@ -92,12 +96,22 @@ export class TelemetryClient implements ITelemetryClient {
|
|
|
92
96
|
if (events.length === 0) {
|
|
93
97
|
return;
|
|
94
98
|
}
|
|
95
|
-
const response = await fetch(
|
|
99
|
+
const response = await fetch(`${POSTHOG_CLIENT_API_HOST}/batch`, {
|
|
96
100
|
method: "POST",
|
|
97
101
|
headers: {
|
|
98
102
|
"Content-Type": "application/json",
|
|
99
103
|
},
|
|
100
|
-
body: JSON.stringify(
|
|
104
|
+
body: JSON.stringify({
|
|
105
|
+
api_key: POSTHOG_PROJECT_ID,
|
|
106
|
+
historical_migration: false,
|
|
107
|
+
batch: events.map((e) => ({
|
|
108
|
+
event: e.event,
|
|
109
|
+
properties: {
|
|
110
|
+
distinct_id: this.context?.sessionId,
|
|
111
|
+
},
|
|
112
|
+
timestamp: new Date(e.timestamp).toISOString(),
|
|
113
|
+
})),
|
|
114
|
+
}),
|
|
101
115
|
});
|
|
102
116
|
if (!response.ok) {
|
|
103
117
|
throw new Error(
|
|
@@ -8,6 +8,8 @@ export const TELEMETRY_DISABLED =
|
|
|
8
8
|
!!process.env.ALCHEMY_TELEMETRY_DISABLED || !!process.env.DO_NOT_TRACK;
|
|
9
9
|
|
|
10
10
|
// TODO(sam): replace with permanent URL
|
|
11
|
-
export const
|
|
12
|
-
process.env.
|
|
13
|
-
|
|
11
|
+
export const POSTHOG_CLIENT_API_HOST =
|
|
12
|
+
process.env.POSTHOG_CLIENT_API_HOST ?? "https://ph.alchemy.run";
|
|
13
|
+
export const POSTHOG_PROJECT_ID =
|
|
14
|
+
process.env.POSTHOG_PROJECT_ID ??
|
|
15
|
+
"phc_A51Mi7Q63TvnNrvRMgvBxE1il0DAL66rVg4LdWPRsfK";
|