alchemy 0.70.0 → 0.70.1
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 +97 -60
- package/bin/alchemy.ts +2 -0
- package/bin/commands/telemetry.ts +33 -0
- package/bin/services/execute-alchemy.ts +7 -0
- package/lib/alchemy.d.ts +4 -0
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +1 -0
- package/lib/alchemy.js.map +1 -1
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +2 -0
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/bun-spa/bun-spa.d.ts +6 -8
- package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -1
- package/lib/cloudflare/bun-spa/bun-spa.js +3 -3
- package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.js +30 -3
- package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
- package/lib/scope.d.ts +5 -0
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +3 -1
- package/lib/scope.js.map +1 -1
- package/lib/util/idempotent-spawn.js +1 -1
- package/lib/util/idempotent-spawn.js.map +1 -1
- package/lib/util/telemetry.d.ts +3 -2
- package/lib/util/telemetry.d.ts.map +1 -1
- package/lib/util/telemetry.js +34 -10
- package/lib/util/telemetry.js.map +1 -1
- package/package.json +1 -1
- package/src/alchemy.ts +5 -0
- package/src/cloudflare/api.ts +2 -0
- package/src/cloudflare/bun-spa/bun-spa.ts +5 -8
- package/src/cloudflare/miniflare/build-worker-options.ts +32 -4
- package/src/scope.ts +7 -0
- package/src/util/idempotent-spawn.ts +1 -1
- package/src/util/telemetry.ts +41 -10
- package/templates/tanstack-start/package.json +2 -2
- package/workers/tunnel-proxy.js +1 -1
package/src/alchemy.ts
CHANGED
|
@@ -137,6 +137,7 @@ async function _alchemy(
|
|
|
137
137
|
password: process.env.ALCHEMY_PASSWORD,
|
|
138
138
|
adopt: cliArgs.includes("--adopt"),
|
|
139
139
|
rootDir: path.resolve(parseOption("--root-dir", ALCHEMY_ROOT)),
|
|
140
|
+
profile: parseOption("--profile"),
|
|
140
141
|
} satisfies Partial<AlchemyOptions>;
|
|
141
142
|
const mergedOptions = {
|
|
142
143
|
...cliOptions,
|
|
@@ -295,6 +296,10 @@ export interface AlchemyOptions {
|
|
|
295
296
|
* @default process.cwd()
|
|
296
297
|
*/
|
|
297
298
|
rootDir?: string;
|
|
299
|
+
/**
|
|
300
|
+
* The Alchemy profile to use for authoriziing requests.
|
|
301
|
+
*/
|
|
302
|
+
profile?: string;
|
|
298
303
|
/**
|
|
299
304
|
* Whether this is the application that was selected with `--app`
|
|
300
305
|
*
|
package/src/cloudflare/api.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Provider, type Credentials } from "../auth.ts";
|
|
2
|
+
import { Scope } from "../scope.ts";
|
|
2
3
|
import type { Secret } from "../secret.ts";
|
|
3
4
|
import { isBinary } from "../serde.ts";
|
|
4
5
|
import { memoize } from "../util/memoize.ts";
|
|
@@ -99,6 +100,7 @@ export const createCloudflareApi = memoize(
|
|
|
99
100
|
try {
|
|
100
101
|
const profile =
|
|
101
102
|
options.profile ??
|
|
103
|
+
Scope.getScope()?.profile ??
|
|
102
104
|
process.env.CLOUDFLARE_PROFILE ??
|
|
103
105
|
process.env.ALCHEMY_PROFILE ??
|
|
104
106
|
"default";
|
|
@@ -14,21 +14,18 @@ import {
|
|
|
14
14
|
Website,
|
|
15
15
|
type WebsiteProps,
|
|
16
16
|
} from "../website.ts";
|
|
17
|
-
import type { Worker } from "../worker.ts";
|
|
18
17
|
|
|
19
18
|
export interface BunSPAProps<B extends Bindings> extends WebsiteProps<B> {
|
|
20
19
|
frontend: string;
|
|
21
20
|
outDir?: string;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
export type BunSPA<B extends Bindings> = B
|
|
25
|
-
? never
|
|
26
|
-
: Worker<B & { ASSETS: Assets }>;
|
|
23
|
+
export type BunSPA<B extends Bindings> = Website<B> & { apiUrl: string };
|
|
27
24
|
|
|
28
25
|
export async function BunSPA<B extends Bindings>(
|
|
29
26
|
id: string,
|
|
30
27
|
props: BunSPAProps<B>,
|
|
31
|
-
): Promise<BunSPA<B
|
|
28
|
+
): Promise<BunSPA<B> & { apiUrl: string }> {
|
|
32
29
|
const frontendPath = path.resolve(props.frontend);
|
|
33
30
|
if (!(await exists(frontendPath))) {
|
|
34
31
|
throw new Error(`Frontend path ${frontendPath} does not exist`);
|
|
@@ -66,6 +63,7 @@ export async function BunSPA<B extends Bindings>(
|
|
|
66
63
|
),
|
|
67
64
|
});
|
|
68
65
|
|
|
66
|
+
let apiUrl = website.url!;
|
|
69
67
|
// in dev
|
|
70
68
|
if (scope.local) {
|
|
71
69
|
const cwd = props.cwd ?? process.cwd();
|
|
@@ -74,7 +72,6 @@ export async function BunSPA<B extends Bindings>(
|
|
|
74
72
|
props,
|
|
75
73
|
`bun '${path.relative(cwd, frontendPath)}'`,
|
|
76
74
|
);
|
|
77
|
-
console.log("backend url", website.url);
|
|
78
75
|
const secrets = props.wrangler?.secrets ?? !props.wrangler?.path;
|
|
79
76
|
const env = {
|
|
80
77
|
...(process.env ?? {}),
|
|
@@ -101,11 +98,11 @@ export async function BunSPA<B extends Bindings>(
|
|
|
101
98
|
...process.env,
|
|
102
99
|
NODE_ENV: "development",
|
|
103
100
|
ALCHEMY_ROOT: Scope.current.rootDir,
|
|
104
|
-
PUBLIC_BACKEND_URL:
|
|
101
|
+
PUBLIC_BACKEND_URL: apiUrl,
|
|
105
102
|
},
|
|
106
103
|
});
|
|
107
104
|
}
|
|
108
|
-
return website
|
|
105
|
+
return { ...website, apiUrl } as BunSPA<B>;
|
|
109
106
|
}
|
|
110
107
|
|
|
111
108
|
async function validateBunfigToml(cwd: string): Promise<void> {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as miniflare from "miniflare";
|
|
2
2
|
import { assertNever } from "../../util/assert-never.ts";
|
|
3
3
|
import type { HTTPServer } from "../../util/http.ts";
|
|
4
|
+
import { logger } from "../../util/logger.ts";
|
|
4
5
|
import type { CloudflareApi } from "../api.ts";
|
|
5
6
|
import type {
|
|
6
7
|
Binding,
|
|
@@ -79,10 +80,7 @@ export const buildWorkerOptions = async (
|
|
|
79
80
|
],
|
|
80
81
|
containerEngine: {
|
|
81
82
|
localDocker: {
|
|
82
|
-
socketPath:
|
|
83
|
-
process.platform === "win32"
|
|
84
|
-
? "//./pipe/docker_engine"
|
|
85
|
-
: "unix:///var/run/docker.sock",
|
|
83
|
+
socketPath: await getDockerSocketPath(),
|
|
86
84
|
},
|
|
87
85
|
},
|
|
88
86
|
// This exposes the worker as a route that can be accessed by setting the MF-Route-Override header.
|
|
@@ -480,3 +478,33 @@ const isRemoteBinding = (binding: Binding) => {
|
|
|
480
478
|
!!binding.dev.remote
|
|
481
479
|
);
|
|
482
480
|
};
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* DOCKER_HOST env is standardized
|
|
484
|
+
* docker has an option to expose on tcp://localhost:2375; so we check 2375 if the user has it enabled
|
|
485
|
+
* the pipe on windows doesn't work half the time(even though the pipe exists). This seems like a strange error on how miniflare parses the pipe
|
|
486
|
+
* @returns The Docker path
|
|
487
|
+
*/
|
|
488
|
+
async function getDockerSocketPath() {
|
|
489
|
+
if (process.env.DOCKER_HOST) {
|
|
490
|
+
return process.env.DOCKER_HOST;
|
|
491
|
+
}
|
|
492
|
+
// Check if docker is running on tcp://localhost:2375 using fetch
|
|
493
|
+
try {
|
|
494
|
+
const url = "http://localhost:2375/_ping";
|
|
495
|
+
const res = await fetch(url, { method: "GET" });
|
|
496
|
+
if (res.ok) {
|
|
497
|
+
const text = await res.text();
|
|
498
|
+
if (text.trim() === "OK") {
|
|
499
|
+
return "localhost:2375";
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
} catch {}
|
|
503
|
+
if (process.platform === "win32") {
|
|
504
|
+
logger.warn(
|
|
505
|
+
"Using the pipe on Windows is unstable. If you have issues, try setting DOCKER_HOST or enabling 'Expose daemon on tcp://localhost:2375 without TLS' in docker desktop",
|
|
506
|
+
);
|
|
507
|
+
return "//./pipe/docker_engine";
|
|
508
|
+
}
|
|
509
|
+
return "unix:///var/run/docker.sock";
|
|
510
|
+
}
|
package/src/scope.ts
CHANGED
|
@@ -103,6 +103,10 @@ export interface ScopeOptions extends ProviderCredentials {
|
|
|
103
103
|
* @default process.cwd()
|
|
104
104
|
*/
|
|
105
105
|
rootDir?: string;
|
|
106
|
+
/**
|
|
107
|
+
* The Alchemy profile to use for authoriziing requests.
|
|
108
|
+
*/
|
|
109
|
+
profile?: string;
|
|
106
110
|
/**
|
|
107
111
|
* Whether this is the application that was selected with `--app`
|
|
108
112
|
*
|
|
@@ -203,6 +207,7 @@ export class Scope {
|
|
|
203
207
|
public readonly rootDir: string;
|
|
204
208
|
public readonly dotAlchemy: string;
|
|
205
209
|
public readonly isSelected: boolean | undefined;
|
|
210
|
+
public readonly profile: string | undefined;
|
|
206
211
|
|
|
207
212
|
// Provider credentials for scope-level credential overrides
|
|
208
213
|
public readonly providerCredentials: ProviderCredentials;
|
|
@@ -242,6 +247,7 @@ export class Scope {
|
|
|
242
247
|
rootDir,
|
|
243
248
|
isSelected,
|
|
244
249
|
noTrack,
|
|
250
|
+
profile,
|
|
245
251
|
...providerCredentials
|
|
246
252
|
} = options;
|
|
247
253
|
|
|
@@ -268,6 +274,7 @@ export class Scope {
|
|
|
268
274
|
}
|
|
269
275
|
|
|
270
276
|
this.stage = stage ?? this.parent?.stage ?? DEFAULT_STAGE;
|
|
277
|
+
this.profile = profile ?? this.parent?.profile;
|
|
271
278
|
this.parent?.children.set(this.scopeName!, this);
|
|
272
279
|
this.quiet = quiet ?? this.parent?.quiet ?? false;
|
|
273
280
|
if (this.parent && !this.scopeName) {
|
|
@@ -112,7 +112,7 @@ export async function idempotentSpawn({
|
|
|
112
112
|
const child = spawn(cmd, {
|
|
113
113
|
shell: true,
|
|
114
114
|
cwd,
|
|
115
|
-
stdio: ["
|
|
115
|
+
stdio: ["inherit", out.fd, out.fd], // stdout/stderr -> files (OS-level)
|
|
116
116
|
env,
|
|
117
117
|
detached: false,
|
|
118
118
|
});
|
package/src/util/telemetry.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import envPaths from "env-paths";
|
|
2
2
|
import { exec } from "node:child_process";
|
|
3
|
-
import
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "pathe";
|
|
6
6
|
import pkg from "../../package.json" with { type: "json" };
|
|
@@ -9,11 +9,13 @@ import { Scope } from "../scope.ts";
|
|
|
9
9
|
import { logger } from "./logger.ts";
|
|
10
10
|
import { memoize } from "./memoize.ts";
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const ALCHEMY_DIR = path.join(os.homedir(), ".alchemy");
|
|
13
|
+
const ID_PATH = path.join(ALCHEMY_DIR, "id");
|
|
14
|
+
const ID_PATH_LEGACY = path.join(
|
|
14
15
|
envPaths("alchemy", { suffix: "" }).config,
|
|
15
16
|
"id",
|
|
16
17
|
);
|
|
18
|
+
const DISABLED_PATH = path.join(ALCHEMY_DIR, "telemetry-disabled");
|
|
17
19
|
|
|
18
20
|
export const TELEMETRY_DISABLED =
|
|
19
21
|
!!process.env.ALCHEMY_TELEMETRY_DISABLED || !!process.env.DO_NOT_TRACK;
|
|
@@ -23,26 +25,43 @@ export const TELEMETRY_API_URL =
|
|
|
23
25
|
export const SUPPRESS_TELEMETRY_ERRORS =
|
|
24
26
|
!!process.env.ALCHEMY_TELEMETRY_SUPPRESS_ERRORS;
|
|
25
27
|
|
|
28
|
+
export const getGlobalTelemetryDisabled = memoize(async () => {
|
|
29
|
+
const disabled = await fs
|
|
30
|
+
.readFile(DISABLED_PATH, "utf-8")
|
|
31
|
+
.then((data) => data.trim() === "true")
|
|
32
|
+
.catch(() => false);
|
|
33
|
+
return disabled;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export async function setGlobalTelemetryDisabled() {
|
|
37
|
+
await fs.mkdir(ALCHEMY_DIR, { recursive: true });
|
|
38
|
+
await fs.writeFile(DISABLED_PATH, "true");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function setGlobalTelemetryEnabled() {
|
|
42
|
+
await fs.rm(DISABLED_PATH, { force: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
26
45
|
async function getOrCreateUserId() {
|
|
27
46
|
async function readUserId(path: string) {
|
|
28
47
|
try {
|
|
29
|
-
return (await readFile(path, "utf-8")).trim();
|
|
48
|
+
return (await fs.readFile(path, "utf-8")).trim();
|
|
30
49
|
} catch {
|
|
31
50
|
return null;
|
|
32
51
|
}
|
|
33
52
|
}
|
|
34
53
|
|
|
35
|
-
const id = await readUserId(
|
|
54
|
+
const id = await readUserId(ID_PATH);
|
|
36
55
|
if (id) {
|
|
37
56
|
return id;
|
|
38
57
|
}
|
|
39
58
|
|
|
40
|
-
const legacyId = await readUserId(
|
|
59
|
+
const legacyId = await readUserId(ID_PATH_LEGACY);
|
|
41
60
|
|
|
42
61
|
try {
|
|
43
62
|
const id = legacyId ?? crypto.randomUUID();
|
|
44
|
-
await mkdir(
|
|
45
|
-
await writeFile(
|
|
63
|
+
await fs.mkdir(ALCHEMY_DIR, { recursive: true });
|
|
64
|
+
await fs.writeFile(ID_PATH, id);
|
|
46
65
|
if (!legacyId) {
|
|
47
66
|
console.warn(
|
|
48
67
|
[
|
|
@@ -59,7 +78,11 @@ async function getOrCreateUserId() {
|
|
|
59
78
|
|
|
60
79
|
async function getRootCommitHash() {
|
|
61
80
|
return new Promise<string | null>((resolve) => {
|
|
62
|
-
|
|
81
|
+
const command =
|
|
82
|
+
process.platform === "win32"
|
|
83
|
+
? `git rev-list --max-parents=0 HEAD | ForEach-Object { if (-not (git cat-file -p $_ | Select-String "^parent ")) { $_ } }`
|
|
84
|
+
: `git rev-list --max-parents=0 HEAD | xargs -r -I{} sh -c 'git cat-file -p {} | grep -q "^parent " || echo {}'`;
|
|
85
|
+
exec(command, (err, stdout) => {
|
|
63
86
|
if (err) {
|
|
64
87
|
resolve(null);
|
|
65
88
|
return;
|
|
@@ -236,6 +259,14 @@ export type AlchemyTelemetryData = {
|
|
|
236
259
|
duration: number;
|
|
237
260
|
};
|
|
238
261
|
|
|
262
|
+
async function isTelemetryDisabled() {
|
|
263
|
+
return (
|
|
264
|
+
Scope.getScope()?.noTrack ||
|
|
265
|
+
TELEMETRY_DISABLED ||
|
|
266
|
+
(await getGlobalTelemetryDisabled())
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
239
270
|
export async function createAndSendEvent(
|
|
240
271
|
data:
|
|
241
272
|
| CliTelemetryData
|
|
@@ -244,7 +275,7 @@ export async function createAndSendEvent(
|
|
|
244
275
|
| AlchemyTelemetryData,
|
|
245
276
|
error?: Error,
|
|
246
277
|
) {
|
|
247
|
-
if (
|
|
278
|
+
if (await isTelemetryDisabled()) {
|
|
248
279
|
return;
|
|
249
280
|
}
|
|
250
281
|
try {
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"vite-tsconfig-paths": "^5.1.4"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@cloudflare/vite-plugin": "
|
|
28
|
-
"@cloudflare/workers-types": "
|
|
27
|
+
"@cloudflare/vite-plugin": "^1.11.6",
|
|
28
|
+
"@cloudflare/workers-types": "^4.20250805.0",
|
|
29
29
|
"@testing-library/dom": "^10.4.0",
|
|
30
30
|
"@testing-library/react": "^16.2.0",
|
|
31
31
|
"@types/node": "^22.10.2",
|
package/workers/tunnel-proxy.js
CHANGED
|
@@ -34,7 +34,7 @@ var h={async fetch(e,r){let n=new URL(e.url);n.host=r.TUNNEL_HOST;let l=new Head
|
|
|
34
34
|
Alchemy</a>.</p>
|
|
35
35
|
</div>
|
|
36
36
|
<div class="bg-slate-200 px-5 py-3 flex flex-col w-full max-w-lg">
|
|
37
|
-
<p class="text-sm text-slate-500">Alchemy 0.70.
|
|
37
|
+
<p class="text-sm text-slate-500">Alchemy 0.70.1</p>
|
|
38
38
|
</div>
|
|
39
39
|
</div>
|
|
40
40
|
</body>
|