@devdogsuga/devtools 0.1.25 → 0.1.27
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/README.md +72 -11
- package/bin/devtools.mjs +4 -4
- package/dist/cli-CZKkNHK5.js +6619 -0
- package/dist/failure-log-BBPGejQv.js +256 -0
- package/dist/launch.js +267 -281
- package/dist/local-env-D8HC2JK_.js +351 -0
- package/dist/local-env-DrBCXqD8.js +2 -0
- package/dist/menu-CQBsQRgH.js +1684 -0
- package/dist/peer-redirect-hooks.js +15 -0
- package/dist/peers-RBplwovA.js +229 -0
- package/dist/root-C2OmZDwD.js +94 -0
- package/dist/stack-Bx-55oi_.js +2 -0
- package/dist/stack-BynCgRVS.js +181 -0
- package/dist/supabase-project-0knM0mqd.js +426 -0
- package/dist/telemetry-C4foD8MD.js +249 -0
- package/dist/telemetry.js +2 -148
- package/package.json +10 -29
- package/bin/devtools-ci-bare.mjs +0 -48
- package/bin/devtools-ci.mjs +0 -31
- package/dist/args.d.ts +0 -26
- package/dist/args.js +0 -78
- package/dist/bws/bw.d.ts +0 -18
- package/dist/bws/bw.js +0 -70
- package/dist/bws/client.d.ts +0 -43
- package/dist/bws/client.js +0 -363
- package/dist/bws/environments.d.ts +0 -96
- package/dist/bws/environments.js +0 -111
- package/dist/bws/pace.d.ts +0 -25
- package/dist/bws/pace.js +0 -43
- package/dist/bws/retry.d.ts +0 -12
- package/dist/bws/retry.js +0 -39
- package/dist/bws/store.d.ts +0 -1
- package/dist/bws/store.js +0 -32
- package/dist/bws/token.d.ts +0 -31
- package/dist/bws/token.js +0 -88
- package/dist/bws/vault.d.ts +0 -29
- package/dist/bws/vault.js +0 -200
- package/dist/catalog.d.ts +0 -57
- package/dist/catalog.js +0 -57
- package/dist/cf/build.d.ts +0 -13
- package/dist/cf/build.js +0 -65
- package/dist/cf/commands.d.ts +0 -1
- package/dist/cf/commands.js +0 -172
- package/dist/cf/local-env.d.ts +0 -58
- package/dist/cf/local-env.js +0 -177
- package/dist/ci.d.ts +0 -1
- package/dist/ci.js +0 -328
- package/dist/cli.d.ts +0 -11
- package/dist/cli.js +0 -1094
- package/dist/commands.d.ts +0 -261
- package/dist/commands.js +0 -1318
- package/dist/completions.d.ts +0 -4
- package/dist/completions.js +0 -110
- package/dist/cron/commands.d.ts +0 -47
- package/dist/cron/commands.js +0 -372
- package/dist/cron/discovery.d.ts +0 -55
- package/dist/cron/discovery.js +0 -85
- package/dist/cron/schema.d.ts +0 -45
- package/dist/cron/schema.js +0 -35
- package/dist/db/config-push.d.ts +0 -8
- package/dist/db/config-push.js +0 -11
- package/dist/db/connection.d.ts +0 -79
- package/dist/db/connection.js +0 -114
- package/dist/db/exec.d.ts +0 -1
- package/dist/db/exec.js +0 -11
- package/dist/db/generate-types.d.ts +0 -1
- package/dist/db/generate-types.js +0 -4
- package/dist/db/generated-env.d.ts +0 -69
- package/dist/db/generated-env.js +0 -113
- package/dist/db/introspect.d.ts +0 -1
- package/dist/db/introspect.js +0 -122
- package/dist/db/local-env.d.ts +0 -65
- package/dist/db/local-env.js +0 -23
- package/dist/db/new-migration.d.ts +0 -8
- package/dist/db/new-migration.js +0 -69
- package/dist/db/pooler.d.ts +0 -23
- package/dist/db/pooler.js +0 -55
- package/dist/db/run.d.ts +0 -68
- package/dist/db/run.js +0 -156
- package/dist/db/seed-buckets.d.ts +0 -2
- package/dist/db/seed-buckets.js +0 -4
- package/dist/db/seed-production.d.ts +0 -8
- package/dist/db/seed-production.js +0 -79
- package/dist/db/seed-roles.d.ts +0 -7
- package/dist/db/seed-roles.js +0 -33
- package/dist/db/session-refresh.d.ts +0 -43
- package/dist/db/session-refresh.js +0 -40
- package/dist/deploy/migrations.d.ts +0 -20
- package/dist/deploy/migrations.js +0 -68
- package/dist/deploy/orphans.d.ts +0 -20
- package/dist/deploy/orphans.js +0 -177
- package/dist/deploy/preflight.d.ts +0 -53
- package/dist/deploy/preflight.js +0 -189
- package/dist/deploy/report.d.ts +0 -26
- package/dist/deploy/report.js +0 -70
- package/dist/deploy/require-planner.d.ts +0 -2
- package/dist/deploy/require-planner.js +0 -60
- package/dist/deploy/require-token.d.ts +0 -15
- package/dist/deploy/require-token.js +0 -78
- package/dist/deploy/secrets-file.d.ts +0 -17
- package/dist/deploy/secrets-file.js +0 -166
- package/dist/deploy/write-env.d.ts +0 -40
- package/dist/deploy/write-env.js +0 -395
- package/dist/docs/index-pages.d.ts +0 -38
- package/dist/docs/index-pages.js +0 -186
- package/dist/emails/commands.d.ts +0 -15
- package/dist/emails/commands.js +0 -130
- package/dist/emails/email-types.d.ts +0 -13
- package/dist/emails/email-types.js +0 -1
- package/dist/emails/fixtures.d.ts +0 -20
- package/dist/emails/fixtures.js +0 -20
- package/dist/env/audit.d.ts +0 -206
- package/dist/env/audit.js +0 -436
- package/dist/env/cloudflare.d.ts +0 -21
- package/dist/env/cloudflare.js +0 -61
- package/dist/env/commands.d.ts +0 -99
- package/dist/env/commands.js +0 -613
- package/dist/env/discovery.d.ts +0 -15
- package/dist/env/discovery.js +0 -239
- package/dist/env/document.d.ts +0 -119
- package/dist/env/document.js +0 -323
- package/dist/env/example.d.ts +0 -68
- package/dist/env/example.js +0 -605
- package/dist/env/selection.d.ts +0 -105
- package/dist/env/selection.js +0 -228
- package/dist/env-entry.d.ts +0 -49
- package/dist/env-entry.js +0 -204
- package/dist/environment-doctor.d.ts +0 -45
- package/dist/environment-doctor.js +0 -429
- package/dist/environment.d.ts +0 -92
- package/dist/environment.js +0 -185
- package/dist/fingerprint.d.ts +0 -10
- package/dist/fingerprint.js +0 -18
- package/dist/gen/campus-map.d.ts +0 -1
- package/dist/gen/campus-map.js +0 -473
- package/dist/gen/commands.d.ts +0 -1
- package/dist/gen/commands.js +0 -25
- package/dist/gen/hypno.d.ts +0 -1
- package/dist/gen/hypno.js +0 -134
- package/dist/gh/client.d.ts +0 -80
- package/dist/gh/client.js +0 -259
- package/dist/gh/environments.d.ts +0 -83
- package/dist/gh/environments.js +0 -178
- package/dist/gh/rulesets/actors.d.ts +0 -14
- package/dist/gh/rulesets/actors.js +0 -69
- package/dist/gh/rulesets/api.d.ts +0 -15
- package/dist/gh/rulesets/api.js +0 -104
- package/dist/gh/rulesets/commands.d.ts +0 -3
- package/dist/gh/rulesets/commands.js +0 -203
- package/dist/gh/rulesets/desired.d.ts +0 -112
- package/dist/gh/rulesets/desired.js +0 -271
- package/dist/gh/rulesets/diff.d.ts +0 -65
- package/dist/gh/rulesets/diff.js +0 -170
- package/dist/gh/rulesets/types.d.ts +0 -76
- package/dist/gh/rulesets/types.js +0 -13
- package/dist/gh/settings/api.d.ts +0 -40
- package/dist/gh/settings/api.js +0 -180
- package/dist/gh/settings/commands.d.ts +0 -1
- package/dist/gh/settings/commands.js +0 -254
- package/dist/gh/settings/desired.d.ts +0 -53
- package/dist/gh/settings/desired.js +0 -43
- package/dist/gh/settings/diff.d.ts +0 -57
- package/dist/gh/settings/diff.js +0 -161
- package/dist/gh/settings/types.d.ts +0 -92
- package/dist/gh/settings/types.js +0 -11
- package/dist/gh/settings/workflows.d.ts +0 -41
- package/dist/gh/settings/workflows.js +0 -128
- package/dist/grantRoot.d.ts +0 -42
- package/dist/grantRoot.js +0 -89
- package/dist/help.d.ts +0 -18
- package/dist/help.js +0 -169
- package/dist/images/commands.d.ts +0 -32
- package/dist/images/commands.js +0 -231
- package/dist/images/events.d.ts +0 -47
- package/dist/images/events.js +0 -116
- package/dist/images/graphics.d.ts +0 -70
- package/dist/images/graphics.js +0 -234
- package/dist/images/open-graph-types.d.ts +0 -75
- package/dist/images/open-graph-types.js +0 -1
- package/dist/images/prompts.d.ts +0 -34
- package/dist/images/prompts.js +0 -100
- package/dist/images/render.d.ts +0 -21
- package/dist/images/render.js +0 -61
- package/dist/images/select.d.ts +0 -64
- package/dist/images/select.js +0 -96
- package/dist/instance.d.ts +0 -69
- package/dist/instance.js +0 -102
- package/dist/invocation.d.ts +0 -49
- package/dist/invocation.js +0 -78
- package/dist/launch-ci.d.ts +0 -1
- package/dist/launch-ci.js +0 -90
- package/dist/launch.d.ts +0 -18
- package/dist/menu.d.ts +0 -43
- package/dist/menu.js +0 -300
- package/dist/moderation.d.ts +0 -70
- package/dist/moderation.js +0 -128
- package/dist/oauth/browser.d.ts +0 -1
- package/dist/oauth/browser.js +0 -13
- package/dist/oauth/config.d.ts +0 -24
- package/dist/oauth/config.js +0 -24
- package/dist/oauth/connect-url.d.ts +0 -17
- package/dist/oauth/connect-url.js +0 -23
- package/dist/oauth/db.d.ts +0 -37
- package/dist/oauth/db.js +0 -108
- package/dist/oauth/device.d.ts +0 -93
- package/dist/oauth/device.js +0 -198
- package/dist/oauth/discovery.d.ts +0 -28
- package/dist/oauth/discovery.js +0 -54
- package/dist/oauth/env-file.d.ts +0 -6
- package/dist/oauth/env-file.js +0 -35
- package/dist/oauth/exchange.d.ts +0 -43
- package/dist/oauth/exchange.js +0 -93
- package/dist/oauth/label.d.ts +0 -1
- package/dist/oauth/label.js +0 -25
- package/dist/oauth/loopback.d.ts +0 -52
- package/dist/oauth/loopback.js +0 -168
- package/dist/oauth/pkce.d.ts +0 -6
- package/dist/oauth/pkce.js +0 -34
- package/dist/oauth/target.d.ts +0 -54
- package/dist/oauth/target.js +0 -102
- package/dist/oauth/transport.d.ts +0 -41
- package/dist/oauth/transport.js +0 -32
- package/dist/oauth/wizard.d.ts +0 -40
- package/dist/oauth/wizard.js +0 -578
- package/dist/persona.d.ts +0 -18
- package/dist/persona.js +0 -221
- package/dist/pick.d.ts +0 -6
- package/dist/pick.js +0 -64
- package/dist/pipes.d.ts +0 -1
- package/dist/pipes.js +0 -25
- package/dist/planner/checks.d.ts +0 -26
- package/dist/planner/checks.js +0 -104
- package/dist/planner/commands.d.ts +0 -10
- package/dist/planner/commands.js +0 -367
- package/dist/planner/db.d.ts +0 -15
- package/dist/planner/db.js +0 -33
- package/dist/planner/role.d.ts +0 -33
- package/dist/planner/role.js +0 -68
- package/dist/repo/peer-redirect-hooks.d.ts +0 -16
- package/dist/repo/peer-redirect-hooks.js +0 -10
- package/dist/repo/peer-redirect.d.ts +0 -4
- package/dist/repo/peer-redirect.js +0 -26
- package/dist/repo/peers.d.ts +0 -39
- package/dist/repo/peers.js +0 -139
- package/dist/repo/resolve.d.ts +0 -41
- package/dist/repo/resolve.js +0 -152
- package/dist/repo/root.d.ts +0 -23
- package/dist/repo/root.js +0 -91
- package/dist/repo/source.d.ts +0 -14
- package/dist/repo/source.js +0 -54
- package/dist/repo/supabase-project.d.ts +0 -60
- package/dist/repo/supabase-project.js +0 -124
- package/dist/repo/tsx-loader.d.ts +0 -6
- package/dist/repo/tsx-loader.js +0 -41
- package/dist/run/pick.d.ts +0 -90
- package/dist/run/pick.js +0 -664
- package/dist/setup.d.ts +0 -1
- package/dist/setup.js +0 -280
- package/dist/stack.d.ts +0 -48
- package/dist/stack.js +0 -403
- package/dist/telemetry.d.ts +0 -59
- package/dist/tier.d.ts +0 -40
- package/dist/tier.js +0 -96
- package/dist/ui.d.ts +0 -40
- package/dist/ui.js +0 -71
- package/dist/version.d.ts +0 -7
- package/dist/version.js +0 -17
- package/dist/workers.d.ts +0 -5
- package/dist/workers.js +0 -33
- package/dist/workflows/commands.d.ts +0 -54
- package/dist/workflows/commands.js +0 -763
|
@@ -1,763 +0,0 @@
|
|
|
1
|
-
/** Config-derived listing and manual triggering for Cloudflare Workflows. */
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { randomUUID } from "node:crypto";
|
|
4
|
-
import { readFileSync } from "node:fs";
|
|
5
|
-
import { createServer } from "node:net";
|
|
6
|
-
import { basename, join } from "node:path";
|
|
7
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
8
|
-
import { confirm, select, text } from "@clack/prompts";
|
|
9
|
-
import { loadEnvLoad } from "../repo/peers.js";
|
|
10
|
-
import { createTemporaryWranglerEnv, scopedProcessEnv, } from "../cf/local-env.js";
|
|
11
|
-
import { buildWorkspaceDeps, needsFrameworkBuild } from "../cf/build.js";
|
|
12
|
-
import { runWithStderr } from "../db/run.js";
|
|
13
|
-
import { findRepoRoot } from "../repo/root.js";
|
|
14
|
-
import { workerPaths } from "../workers.js";
|
|
15
|
-
import { recordResolved } from "../invocation.js";
|
|
16
|
-
import { resolveTier } from "../tier.js";
|
|
17
|
-
import { unwrap } from "../ui.js";
|
|
18
|
-
import { CRON_TIERS, discoverWranglerConfigs, isCronTier, workflowsForTier, } from "../cron/discovery.js";
|
|
19
|
-
export { renderWranglerEnvFile } from "../cf/local-env.js";
|
|
20
|
-
// Covers `vinext dev`'s first-run dependency optimization and workerd boot. It
|
|
21
|
-
// also bounds how long a broken bundle stalls the command: both runtimes report
|
|
22
|
-
// a bundling error but keep watching for changes instead of exiting.
|
|
23
|
-
const WRANGLER_READY_TIMEOUT_MS = 3 * 60_000;
|
|
24
|
-
const LOCAL_WORKFLOW_TIMEOUT_MS = 60 * 60_000;
|
|
25
|
-
const WRANGLER_PROBE_PATH = "/cdn-cgi/local/explorer/api/workflows";
|
|
26
|
-
export function workflowChoices(configs, tiers = CRON_TIERS) {
|
|
27
|
-
return configs
|
|
28
|
-
.flatMap(({ app, config }) => tiers.flatMap((tier) => workflowsForTier(config, tier).map((workflow) => ({
|
|
29
|
-
app,
|
|
30
|
-
tier,
|
|
31
|
-
binding: workflow.binding,
|
|
32
|
-
name: workflow.name,
|
|
33
|
-
className: workflow.class_name,
|
|
34
|
-
}))))
|
|
35
|
-
.sort((a, b) => a.app.localeCompare(b.app) ||
|
|
36
|
-
a.tier.localeCompare(b.tier) ||
|
|
37
|
-
a.name.localeCompare(b.name));
|
|
38
|
-
}
|
|
39
|
-
function parseOptions(argv) {
|
|
40
|
-
const options = {
|
|
41
|
-
yes: argv.includes("--yes"),
|
|
42
|
-
json: argv.includes("--json"),
|
|
43
|
-
};
|
|
44
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
45
|
-
const flag = argv[index];
|
|
46
|
-
const value = argv[index + 1];
|
|
47
|
-
if (flag === "--app")
|
|
48
|
-
options.app = value;
|
|
49
|
-
else if (flag === "--tier")
|
|
50
|
-
options.tier = value;
|
|
51
|
-
else if (flag === "--workflow")
|
|
52
|
-
options.workflow = value;
|
|
53
|
-
else if (flag === "--params")
|
|
54
|
-
options.params = value;
|
|
55
|
-
else if (flag === "--port")
|
|
56
|
-
options.port = value;
|
|
57
|
-
}
|
|
58
|
-
return options;
|
|
59
|
-
}
|
|
60
|
-
function validateTier(value, command) {
|
|
61
|
-
if (value === undefined)
|
|
62
|
-
return undefined;
|
|
63
|
-
if (isCronTier(value))
|
|
64
|
-
return value;
|
|
65
|
-
process.stderr.write(`devtools workflows ${command}: unknown tier "${value}". Expected: ${CRON_TIERS.join(", ")}.\n`);
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
export function workflowTriggerArgs(choice, options) {
|
|
69
|
-
const args = [
|
|
70
|
-
"--filter",
|
|
71
|
-
choice.app,
|
|
72
|
-
"exec",
|
|
73
|
-
"wrangler",
|
|
74
|
-
"workflows",
|
|
75
|
-
"trigger",
|
|
76
|
-
choice.name,
|
|
77
|
-
];
|
|
78
|
-
if (options.params)
|
|
79
|
-
args.push(options.params);
|
|
80
|
-
if (options.instanceId)
|
|
81
|
-
args.push("--id", options.instanceId);
|
|
82
|
-
if (choice.tier === "development") {
|
|
83
|
-
args.push("--local", "--port", options.port ?? "8787");
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
args.push("--env", choice.tier);
|
|
87
|
-
}
|
|
88
|
-
return args;
|
|
89
|
-
}
|
|
90
|
-
/** Wait until a locally-triggered instance finishes before its server is stopped. */
|
|
91
|
-
export async function waitForLocalWorkflow(workflowName, instanceId, port, options = {}) {
|
|
92
|
-
const timeoutMs = options.timeoutMs ?? LOCAL_WORKFLOW_TIMEOUT_MS;
|
|
93
|
-
const pollMs = options.pollMs ?? 500;
|
|
94
|
-
const fetcher = options.fetcher ?? fetch;
|
|
95
|
-
const deadline = Date.now() + timeoutMs;
|
|
96
|
-
const url = `http://127.0.0.1:${port}${WRANGLER_PROBE_PATH}/` +
|
|
97
|
-
`${encodeURIComponent(workflowName)}/instances/${encodeURIComponent(instanceId)}`;
|
|
98
|
-
let previousStatus;
|
|
99
|
-
while (Date.now() < deadline) {
|
|
100
|
-
const response = await fetcher(url, {
|
|
101
|
-
signal: AbortSignal.timeout(2_000),
|
|
102
|
-
});
|
|
103
|
-
if (response.status === 404) {
|
|
104
|
-
await delay(pollMs);
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
if (!response.ok) {
|
|
108
|
-
throw new Error(`Wrangler returned HTTP ${response.status} while checking Workflow instance ${instanceId}.`);
|
|
109
|
-
}
|
|
110
|
-
const envelope = (await response.json());
|
|
111
|
-
if (envelope.success !== true || !envelope.result) {
|
|
112
|
-
const message = envelope.errors?.[0]?.message ?? "unknown Wrangler error";
|
|
113
|
-
throw new Error(`Could not read Workflow instance ${instanceId}: ${message}`);
|
|
114
|
-
}
|
|
115
|
-
const { status, error } = envelope.result;
|
|
116
|
-
if (status && status !== previousStatus) {
|
|
117
|
-
process.stdout.write(`Workflow ${instanceId}: ${status}\n`);
|
|
118
|
-
previousStatus = status;
|
|
119
|
-
}
|
|
120
|
-
if (status === "complete") {
|
|
121
|
-
const output = envelope.result.output;
|
|
122
|
-
const failures = typeof output === "object" &&
|
|
123
|
-
output !== null &&
|
|
124
|
-
"failures" in output &&
|
|
125
|
-
Array.isArray(output.failures)
|
|
126
|
-
? output.failures
|
|
127
|
-
: [];
|
|
128
|
-
if (failures.length > 0) {
|
|
129
|
-
process.stderr.write(`devtools workflows run: Workflow ${instanceId} completed with ${failures.length} recorded failure${failures.length === 1 ? "" : "s"}:\n`);
|
|
130
|
-
for (const failure of failures) {
|
|
131
|
-
if (typeof failure === "object" && failure !== null) {
|
|
132
|
-
const period = "academicPeriod" in failure
|
|
133
|
-
? String(failure.academicPeriod)
|
|
134
|
-
: "unknown item";
|
|
135
|
-
const message = "error" in failure ? String(failure.error) : "unknown error";
|
|
136
|
-
process.stderr.write(` - ${period}: ${message}\n`);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
process.stderr.write(` - ${String(failure)}\n`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return 1;
|
|
143
|
-
}
|
|
144
|
-
return 0;
|
|
145
|
-
}
|
|
146
|
-
if (status === "errored" || status === "terminated") {
|
|
147
|
-
const detail = error?.message
|
|
148
|
-
? `: ${error.name ? `${error.name}: ` : ""}${error.message}`
|
|
149
|
-
: "";
|
|
150
|
-
process.stderr.write(`devtools workflows run: Workflow ${instanceId} ${status}${detail}\n`);
|
|
151
|
-
return 1;
|
|
152
|
-
}
|
|
153
|
-
await delay(pollMs);
|
|
154
|
-
}
|
|
155
|
-
process.stderr.write(`devtools workflows run: Workflow ${instanceId} did not finish within ${Math.round(timeoutMs / 60_000)} minutes.\n`);
|
|
156
|
-
return 1;
|
|
157
|
-
}
|
|
158
|
-
export function wranglerDevArgs(app, port) {
|
|
159
|
-
return [
|
|
160
|
-
"--filter",
|
|
161
|
-
app,
|
|
162
|
-
"exec",
|
|
163
|
-
"wrangler",
|
|
164
|
-
"dev",
|
|
165
|
-
"--port",
|
|
166
|
-
port,
|
|
167
|
-
"--show-interactive-dev-session=false",
|
|
168
|
-
];
|
|
169
|
-
}
|
|
170
|
-
/** `vinext dev` bound to loopback, where `isWranglerDevRunning` probes. */
|
|
171
|
-
export function vinextDevArgs(app, port) {
|
|
172
|
-
return [
|
|
173
|
-
"--filter",
|
|
174
|
-
app,
|
|
175
|
-
"exec",
|
|
176
|
-
"vinext",
|
|
177
|
-
"dev",
|
|
178
|
-
"--port",
|
|
179
|
-
port,
|
|
180
|
-
"--hostname",
|
|
181
|
-
"127.0.0.1",
|
|
182
|
-
];
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* The port of a `vinext dev` already running for `app`, from the lock file
|
|
186
|
-
* vinext keeps at `<app>/.vinext/dev/lock.json` — or undefined when there is
|
|
187
|
-
* none, or its process is gone. vinext refuses a second dev server in the same
|
|
188
|
-
* app directory, so starting one beside it fails ("Another vinext dev server
|
|
189
|
-
* is already running"); reusing it is the only way through.
|
|
190
|
-
*/
|
|
191
|
-
export function runningVinextDevPort(appDir, isAlive = pidIsAlive) {
|
|
192
|
-
try {
|
|
193
|
-
const lock = JSON.parse(readFileSync(join(appDir, ".vinext", "dev", "lock.json"), "utf8"));
|
|
194
|
-
if (typeof lock.pid !== "number" || typeof lock.port !== "number") {
|
|
195
|
-
return undefined;
|
|
196
|
-
}
|
|
197
|
-
return isAlive(lock.pid) ? String(lock.port) : undefined;
|
|
198
|
-
}
|
|
199
|
-
catch {
|
|
200
|
-
return undefined;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
function pidIsAlive(pid) {
|
|
204
|
-
try {
|
|
205
|
-
process.kill(pid, 0);
|
|
206
|
-
return true;
|
|
207
|
-
}
|
|
208
|
-
catch (error) {
|
|
209
|
-
// EPERM: alive, just not ours to signal.
|
|
210
|
-
return error.code === "EPERM";
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
function appDir(app) {
|
|
214
|
-
const path = workerPaths().find((candidate) => basename(candidate) === app);
|
|
215
|
-
return path === undefined ? undefined : join(findRepoRoot(), path);
|
|
216
|
-
}
|
|
217
|
-
/** Probe Wrangler's Workflow explorer API, rather than merely checking a port. */
|
|
218
|
-
export async function isWranglerDevRunning(port, timeoutMs = 1_000, fetcher = fetch) {
|
|
219
|
-
try {
|
|
220
|
-
const response = await fetcher(`http://127.0.0.1:${port}${WRANGLER_PROBE_PATH}`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
221
|
-
if (!response.ok)
|
|
222
|
-
return false;
|
|
223
|
-
const payload = (await response.json());
|
|
224
|
-
return payload.success === true;
|
|
225
|
-
}
|
|
226
|
-
catch {
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
function signalProcessGroup(child, signal) {
|
|
231
|
-
if (child.pid === undefined)
|
|
232
|
-
return;
|
|
233
|
-
try {
|
|
234
|
-
if (process.platform === "win32")
|
|
235
|
-
child.kill(signal);
|
|
236
|
-
else
|
|
237
|
-
process.kill(-child.pid, signal);
|
|
238
|
-
}
|
|
239
|
-
catch {
|
|
240
|
-
// It may have exited between the readiness/exit check and the signal.
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
async function stopTemporaryWrangler(child) {
|
|
244
|
-
if (child.exitCode !== null || child.signalCode !== null)
|
|
245
|
-
return;
|
|
246
|
-
signalProcessGroup(child, "SIGINT");
|
|
247
|
-
if (await waitForExit(child, 3_000))
|
|
248
|
-
return;
|
|
249
|
-
signalProcessGroup(child, "SIGTERM");
|
|
250
|
-
if (await waitForExit(child, 3_000))
|
|
251
|
-
return;
|
|
252
|
-
signalProcessGroup(child, "SIGKILL");
|
|
253
|
-
await waitForExit(child, 1_000);
|
|
254
|
-
}
|
|
255
|
-
async function waitForExit(child, timeoutMs) {
|
|
256
|
-
if (child.exitCode !== null || child.signalCode !== null)
|
|
257
|
-
return true;
|
|
258
|
-
return Promise.race([
|
|
259
|
-
new Promise((resolve) => child.once("exit", () => resolve(true))),
|
|
260
|
-
delay(timeoutMs).then(() => false),
|
|
261
|
-
]);
|
|
262
|
-
}
|
|
263
|
-
/** Whether nothing is bound to `port` on loopback — a real bind test, not a
|
|
264
|
-
* connect probe, so it answers the only question that matters here: can Wrangler
|
|
265
|
-
* take this port? */
|
|
266
|
-
export function isPortFree(port) {
|
|
267
|
-
return new Promise((resolve) => {
|
|
268
|
-
const server = createServer();
|
|
269
|
-
server.once("error", () => resolve(false));
|
|
270
|
-
server.listen({ host: "127.0.0.1", port }, () => {
|
|
271
|
-
server.close(() => resolve(true));
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
/** The first free port at or after `from`, or null if a bounded scan finds
|
|
276
|
-
* none. Bounded so an exhausted range fails loudly instead of looping. */
|
|
277
|
-
export async function findFreePort(from, attempts = 20) {
|
|
278
|
-
for (let port = from; port < from + attempts && port <= 65_535; port += 1) {
|
|
279
|
-
if (await isPortFree(port))
|
|
280
|
-
return port;
|
|
281
|
-
}
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
async function startTemporaryWrangler(app, requestedPort) {
|
|
285
|
-
const envLoad = await loadEnvLoad();
|
|
286
|
-
// Auto-pick a free port when the requested one is taken. The usual culprit is
|
|
287
|
-
// another project's `wrangler dev` holding the default 8787; without this,
|
|
288
|
-
// `wrangler dev --port <taken>` cannot bind and the session never becomes
|
|
289
|
-
// ready — read as "triggering a workflow is broken" rather than "that port is
|
|
290
|
-
// busy". The trigger below uses whatever port we actually bound.
|
|
291
|
-
let port = requestedPort;
|
|
292
|
-
if (!(await isPortFree(Number(port)))) {
|
|
293
|
-
const free = await findFreePort(Number(port) + 1);
|
|
294
|
-
if (free === null) {
|
|
295
|
-
process.stderr.write(`devtools workflows: port ${requestedPort} is in use and no free port ` +
|
|
296
|
-
`was found near it for a temporary dev session.\n`);
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
|
-
process.stdout.write(`Port ${requestedPort} is in use; starting the temporary dev session on ${free} instead.\n`);
|
|
300
|
-
port = String(free);
|
|
301
|
-
}
|
|
302
|
-
const runtime = needsFrameworkBuild(app) ? "vinext dev" : "Wrangler";
|
|
303
|
-
process.stdout.write(`Preparing and starting a temporary ${runtime} session for ${app} on port ${port}…\n`);
|
|
304
|
-
// Both callers of this function (`workflows serve`, `workflows run --tier
|
|
305
|
-
// development`) are local-only, so the tier is always development — but
|
|
306
|
-
// load it explicitly rather than letting the scoped `.dev.vars` fall back
|
|
307
|
-
// to this process's own inherited env: `override: true` re-reads `.env`
|
|
308
|
-
// fresh, so an edit made after this process started is picked up instead
|
|
309
|
-
// of a value it happened to inherit at boot.
|
|
310
|
-
let loaded;
|
|
311
|
-
try {
|
|
312
|
-
loaded = await envLoad.loadEnvironment("development", { override: true });
|
|
313
|
-
}
|
|
314
|
-
catch (err) {
|
|
315
|
-
if (err instanceof envLoad.MissingEnvFileError) {
|
|
316
|
-
process.stderr.write(`devtools workflows: ${err.message}\n`);
|
|
317
|
-
return null;
|
|
318
|
-
}
|
|
319
|
-
throw err;
|
|
320
|
-
}
|
|
321
|
-
// Their `exports` point at gitignored `dist/`, so on a fresh clone the
|
|
322
|
-
// app's `next.config.ts` can't load without this (see `cf/build.ts`).
|
|
323
|
-
const buildCode = await buildWorkspaceDeps(app);
|
|
324
|
-
if (buildCode !== 0) {
|
|
325
|
-
process.stderr.write(`devtools workflows: building ${app}'s workspace dependencies failed (exit ${buildCode}); not starting ${runtime}.\n`);
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
// A vinext app runs on `vinext dev`: the Cloudflare Vite plugin serves the
|
|
329
|
-
// same Workflow bindings and local explorer API as `wrangler dev`, straight
|
|
330
|
-
// from source. A bare `wrangler dev` against its pre-build wrangler.jsonc
|
|
331
|
-
// cannot bundle it at all (see `cf/build.ts`). The plugin takes no env file,
|
|
332
|
-
// so the scoped env rides the child's own environment instead.
|
|
333
|
-
const runtimeEnv = needsFrameworkBuild(app)
|
|
334
|
-
? undefined
|
|
335
|
-
: await createTemporaryWranglerEnv(app, loaded.env);
|
|
336
|
-
const child = spawn("pnpm", runtimeEnv
|
|
337
|
-
? [...wranglerDevArgs(app, port), "--env-file", runtimeEnv.path]
|
|
338
|
-
: vinextDevArgs(app, port), {
|
|
339
|
-
cwd: findRepoRoot(),
|
|
340
|
-
stdio: "inherit",
|
|
341
|
-
detached: process.platform !== "win32",
|
|
342
|
-
...(runtimeEnv
|
|
343
|
-
? {}
|
|
344
|
-
: { env: await scopedProcessEnv(app, loaded.env, "development") }),
|
|
345
|
-
});
|
|
346
|
-
let startupError;
|
|
347
|
-
child.once("error", (error) => {
|
|
348
|
-
startupError = error;
|
|
349
|
-
});
|
|
350
|
-
const deadline = Date.now() + WRANGLER_READY_TIMEOUT_MS;
|
|
351
|
-
while (Date.now() < deadline) {
|
|
352
|
-
if (await isWranglerDevRunning(port)) {
|
|
353
|
-
process.stdout.write(`${runtime} is ready.\n`);
|
|
354
|
-
const stopOnParentExit = () => {
|
|
355
|
-
signalProcessGroup(child, "SIGTERM");
|
|
356
|
-
runtimeEnv?.remove();
|
|
357
|
-
};
|
|
358
|
-
process.once("exit", stopOnParentExit);
|
|
359
|
-
return {
|
|
360
|
-
stop: async () => {
|
|
361
|
-
process.off("exit", stopOnParentExit);
|
|
362
|
-
await stopTemporaryWrangler(child);
|
|
363
|
-
runtimeEnv?.remove();
|
|
364
|
-
},
|
|
365
|
-
forceStop: () => {
|
|
366
|
-
process.off("exit", stopOnParentExit);
|
|
367
|
-
signalProcessGroup(child, "SIGKILL");
|
|
368
|
-
runtimeEnv?.remove();
|
|
369
|
-
},
|
|
370
|
-
port,
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
if (startupError || child.exitCode !== null || child.signalCode !== null) {
|
|
374
|
-
// Name the exit status: a silent early exit is otherwise undiagnosable,
|
|
375
|
-
// since the runtime itself may print nothing on the way out.
|
|
376
|
-
const how = startupError
|
|
377
|
-
? `: ${startupError.message}`
|
|
378
|
-
: child.signalCode !== null
|
|
379
|
-
? ` (killed by ${child.signalCode}).`
|
|
380
|
-
: ` (exit ${child.exitCode}).`;
|
|
381
|
-
process.stderr.write(`devtools workflows run: ${runtime} stopped before it became ready${how}\n`);
|
|
382
|
-
runtimeEnv?.remove();
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
await delay(250);
|
|
386
|
-
}
|
|
387
|
-
await stopTemporaryWrangler(child);
|
|
388
|
-
runtimeEnv?.remove();
|
|
389
|
-
process.stderr.write(`devtools workflows run: ${runtime} did not become ready on port ${port} within ${WRANGLER_READY_TIMEOUT_MS / 60_000} minutes.\n`);
|
|
390
|
-
return null;
|
|
391
|
-
}
|
|
392
|
-
async function prepareLocalWrangler(app, initialPort) {
|
|
393
|
-
let port = initialPort;
|
|
394
|
-
if (!(await isWranglerDevRunning(port))) {
|
|
395
|
-
const dir = appDir(app);
|
|
396
|
-
const running = dir === undefined ? undefined : runningVinextDevPort(dir);
|
|
397
|
-
if (running !== undefined && (await isWranglerDevRunning(running))) {
|
|
398
|
-
process.stdout.write(`Using the vinext dev session already running for ${app} on port ${running}.\n`);
|
|
399
|
-
return { port: running };
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
while (!(await isWranglerDevRunning(port))) {
|
|
403
|
-
if (!process.stdin.isTTY) {
|
|
404
|
-
process.stderr.write(wranglerDevNotRunningHint(app, port));
|
|
405
|
-
return undefined;
|
|
406
|
-
}
|
|
407
|
-
const action = unwrap(await select({
|
|
408
|
-
message: `No Workflow runtime (vinext dev or wrangler dev) was found on port ${port}.`,
|
|
409
|
-
options: [
|
|
410
|
-
{
|
|
411
|
-
value: "start",
|
|
412
|
-
label: "Start one temporarily",
|
|
413
|
-
hint: "stop it after this Workflow is triggered",
|
|
414
|
-
},
|
|
415
|
-
{
|
|
416
|
-
value: "port",
|
|
417
|
-
label: "Use another port",
|
|
418
|
-
hint: "a dev session may already be running there",
|
|
419
|
-
},
|
|
420
|
-
{ value: "cancel", label: "Cancel" },
|
|
421
|
-
],
|
|
422
|
-
}));
|
|
423
|
-
if (action === "cancel")
|
|
424
|
-
return undefined;
|
|
425
|
-
if (action === "port") {
|
|
426
|
-
port = unwrap(await text({
|
|
427
|
-
message: "Which port is wrangler dev using?",
|
|
428
|
-
placeholder: "8787",
|
|
429
|
-
validate: validatePort,
|
|
430
|
-
})).trim();
|
|
431
|
-
continue;
|
|
432
|
-
}
|
|
433
|
-
const temporary = await startTemporaryWrangler(app, port);
|
|
434
|
-
// `temporary.port` — not `port` — because the session may have landed on a
|
|
435
|
-
// different port when the requested one was taken; the trigger must aim at
|
|
436
|
-
// where Wrangler actually came up.
|
|
437
|
-
return temporary ? { port: temporary.port, temporary } : undefined;
|
|
438
|
-
}
|
|
439
|
-
return { port };
|
|
440
|
-
}
|
|
441
|
-
function validatePort(value) {
|
|
442
|
-
if (!value || !/^\d+$/.test(value))
|
|
443
|
-
return "Enter a port number.";
|
|
444
|
-
const port = Number(value);
|
|
445
|
-
if (port < 1 || port > 65_535)
|
|
446
|
-
return "Enter a port from 1 to 65535.";
|
|
447
|
-
return undefined;
|
|
448
|
-
}
|
|
449
|
-
export function isWranglerDevConnectionFailure(stderr) {
|
|
450
|
-
return /Could not connect to local dev session on port \d+[\s\S]*Make sure ["“]wrangler dev["”] is running/i.test(stderr);
|
|
451
|
-
}
|
|
452
|
-
export function wranglerDevConnectionHint(app, port) {
|
|
453
|
-
return ("\nThe local Workflow trigger connects to Wrangler's Worker runtime, not " +
|
|
454
|
-
"the Next.js development server. `next dev` renders the web app, but it " +
|
|
455
|
-
"does not register Cloudflare Workflow bindings or expose Wrangler's " +
|
|
456
|
-
"local control API.\n\n" +
|
|
457
|
-
"Start a separate app-scoped Wrangler session, then retry the trigger:\n" +
|
|
458
|
-
` pnpm devtools workflows serve --app ${app} --port ${port}\n` +
|
|
459
|
-
` pnpm devtools workflows run --app ${app} --tier development --port ${port}\n\n` +
|
|
460
|
-
"The serve command loads only this app's declared environment. Keep " +
|
|
461
|
-
"`next dev` running too if you also need the Next.js development UI.\n");
|
|
462
|
-
}
|
|
463
|
-
export function wranglerDevNotRunningHint(app, port) {
|
|
464
|
-
return (`devtools workflows run: no Wrangler dev session was found on port ${port}.\n` +
|
|
465
|
-
"A local Workflow needs Wrangler's Worker runtime; `next dev` only serves " +
|
|
466
|
-
"the Next.js UI and does not register Workflow bindings.\n" +
|
|
467
|
-
`Start it with: pnpm devtools workflows serve --app ${app} --port ${port}\n` +
|
|
468
|
-
`If Wrangler uses another port, rerun this command with --port <number>.\n`);
|
|
469
|
-
}
|
|
470
|
-
export async function runWorkflowsList(argv) {
|
|
471
|
-
const options = parseOptions(argv);
|
|
472
|
-
const tier = validateTier(options.tier, "list");
|
|
473
|
-
if (tier === null)
|
|
474
|
-
return 1;
|
|
475
|
-
let choices = workflowChoices(discoverWranglerConfigs(), tier ? [tier] : CRON_TIERS);
|
|
476
|
-
if (options.app)
|
|
477
|
-
choices = choices.filter((item) => item.app === options.app);
|
|
478
|
-
if (options.json) {
|
|
479
|
-
process.stdout.write(`${JSON.stringify(choices, null, 2)}\n`);
|
|
480
|
-
return 0;
|
|
481
|
-
}
|
|
482
|
-
if (choices.length === 0) {
|
|
483
|
-
process.stdout.write("(no configured Workflows found)\n");
|
|
484
|
-
return 0;
|
|
485
|
-
}
|
|
486
|
-
for (const item of choices) {
|
|
487
|
-
process.stdout.write(`${item.app} [${item.tier}]\n` +
|
|
488
|
-
` ${item.name}\n` +
|
|
489
|
-
` binding: ${item.binding}${item.className ? ` · class: ${item.className}` : ""}\n`);
|
|
490
|
-
}
|
|
491
|
-
return 0;
|
|
492
|
-
}
|
|
493
|
-
export async function runWorkflowsRun(argv) {
|
|
494
|
-
const options = parseOptions(argv);
|
|
495
|
-
// What arrived as a flag versus what a prompt will decide — only the latter
|
|
496
|
-
// is worth recording for the "skip the prompts next time" line, since a flag
|
|
497
|
-
// is already in the recorded base argv.
|
|
498
|
-
const givenApp = options.app;
|
|
499
|
-
const givenTier = options.tier;
|
|
500
|
-
const givenWorkflow = options.workflow;
|
|
501
|
-
const givenPort = options.port;
|
|
502
|
-
const tier = await resolveTier(options.tier, "Which tier should receive the Workflow?", { label: "devtools workflows run" });
|
|
503
|
-
if (!tier)
|
|
504
|
-
return 1;
|
|
505
|
-
if (options.params) {
|
|
506
|
-
try {
|
|
507
|
-
JSON.parse(options.params);
|
|
508
|
-
}
|
|
509
|
-
catch {
|
|
510
|
-
process.stderr.write("devtools workflows run: --params must be valid JSON.\n");
|
|
511
|
-
return 1;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
if (options.port) {
|
|
515
|
-
const error = validatePort(options.port);
|
|
516
|
-
if (error) {
|
|
517
|
-
process.stderr.write("devtools workflows run: --port must be an integer from 1 to 65535.\n");
|
|
518
|
-
return 1;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
let choices = workflowChoices(discoverWranglerConfigs(), [tier]);
|
|
522
|
-
if (options.app)
|
|
523
|
-
choices = choices.filter((item) => item.app === options.app);
|
|
524
|
-
let choice;
|
|
525
|
-
if (options.workflow) {
|
|
526
|
-
const matches = choices.filter((item) => item.name === options.workflow || item.binding === options.workflow);
|
|
527
|
-
if (matches.length > 1 && !options.app) {
|
|
528
|
-
process.stderr.write(`devtools workflows run: "${options.workflow}" is ambiguous; pass --app.\n`);
|
|
529
|
-
return 1;
|
|
530
|
-
}
|
|
531
|
-
choice = matches[0];
|
|
532
|
-
}
|
|
533
|
-
else if (process.stdin.isTTY) {
|
|
534
|
-
if (choices.length > 0) {
|
|
535
|
-
choice = unwrap(await select({
|
|
536
|
-
message: "Which Workflow should run?",
|
|
537
|
-
options: choices.map((item) => ({
|
|
538
|
-
value: item,
|
|
539
|
-
label: `${item.app} · ${item.name}`,
|
|
540
|
-
hint: `${item.binding}${item.className ? ` · ${item.className}` : ""}`,
|
|
541
|
-
})),
|
|
542
|
-
}));
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
if (!choice) {
|
|
546
|
-
process.stderr.write(options.workflow
|
|
547
|
-
? `devtools workflows run: no configured Workflow named "${options.workflow}" for ${tier}.\n`
|
|
548
|
-
: "devtools workflows run: no Workflow was selected; pass --workflow when no terminal is available.\n");
|
|
549
|
-
return 1;
|
|
550
|
-
}
|
|
551
|
-
if (tier !== "development" && !options.yes) {
|
|
552
|
-
if (!process.stdin.isTTY) {
|
|
553
|
-
process.stderr.write(`devtools workflows run: --yes is required to trigger ${tier}.\n`);
|
|
554
|
-
return 1;
|
|
555
|
-
}
|
|
556
|
-
const approved = unwrap(await confirm({
|
|
557
|
-
message: `Trigger ${choice.name} on ${tier}?`,
|
|
558
|
-
initialValue: false,
|
|
559
|
-
}));
|
|
560
|
-
if (!approved)
|
|
561
|
-
return 1;
|
|
562
|
-
}
|
|
563
|
-
let local;
|
|
564
|
-
// Uninstalls the signal handlers installed below, when this session's
|
|
565
|
-
// temporary Wrangler (if any) is stopped through the ordinary `finally`
|
|
566
|
-
// instead of a signal — set only on that branch, so the no-temporary-
|
|
567
|
-
// session run leaves nothing to uninstall.
|
|
568
|
-
let uninstallSignalCleanup;
|
|
569
|
-
if (tier === "development") {
|
|
570
|
-
local = await prepareLocalWrangler(choice.app, options.port ?? "8787");
|
|
571
|
-
if (!local)
|
|
572
|
-
return 1;
|
|
573
|
-
options.port = local.port;
|
|
574
|
-
// A successful trigger only means "queued". Keep the exact instance ID so
|
|
575
|
-
// we can wait for its terminal status before stopping a temporary server.
|
|
576
|
-
options.instanceId = randomUUID();
|
|
577
|
-
if (local.temporary) {
|
|
578
|
-
// Everything below can wait up to an hour for the triggered Workflow
|
|
579
|
-
// (`waitForLocalWorkflow`'s timeout) with a credential-bearing temp env
|
|
580
|
-
// file on disk and a `wrangler dev` child running. Without a handler,
|
|
581
|
-
// Ctrl+C during that wait kills only this process — Node's default
|
|
582
|
-
// SIGINT/SIGTERM disposition — and leaves both behind. `stop()` handles
|
|
583
|
-
// both (it kills the child and calls the temp env's `remove()`); once
|
|
584
|
-
// it settles, re-raise the signal with the handler already off, so the
|
|
585
|
-
// default disposition terminates the process and the exit status still
|
|
586
|
-
// reflects the signal.
|
|
587
|
-
const session = local.temporary;
|
|
588
|
-
let stopping = false;
|
|
589
|
-
const onSignal = (signal) => {
|
|
590
|
-
if (stopping)
|
|
591
|
-
return;
|
|
592
|
-
stopping = true;
|
|
593
|
-
process.off("SIGINT", onSignal);
|
|
594
|
-
process.off("SIGTERM", onSignal);
|
|
595
|
-
void session.stop().finally(() => process.kill(process.pid, signal));
|
|
596
|
-
};
|
|
597
|
-
process.on("SIGINT", onSignal);
|
|
598
|
-
process.on("SIGTERM", onSignal);
|
|
599
|
-
uninstallSignalCleanup = () => {
|
|
600
|
-
process.off("SIGINT", onSignal);
|
|
601
|
-
process.off("SIGTERM", onSignal);
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
// A remote trigger (`wrangler workflows trigger --env <tier>`) authenticates
|
|
606
|
-
// against Cloudflare with that tier's own credentials, which live only in
|
|
607
|
-
// `.env.<tier>` — never in this process's own inherited env, which is
|
|
608
|
-
// development's. Loading it here, rather than leaving the child to whatever
|
|
609
|
-
// this process happened to inherit, is an accepted behavior change: a
|
|
610
|
-
// remote trigger now requires `.env.<tier>` to be present and authenticates
|
|
611
|
-
// with ITS credentials, not development's. The local path needs none of
|
|
612
|
-
// this — the temporary Wrangler session already carries development's env.
|
|
613
|
-
const envLoad = await loadEnvLoad();
|
|
614
|
-
let triggerEnv;
|
|
615
|
-
if (tier !== "development") {
|
|
616
|
-
try {
|
|
617
|
-
triggerEnv = (await envLoad.loadEnvironment(tier, { override: true }))
|
|
618
|
-
.env;
|
|
619
|
-
}
|
|
620
|
-
catch (err) {
|
|
621
|
-
if (err instanceof envLoad.MissingEnvFileError) {
|
|
622
|
-
process.stderr.write(`devtools workflows run: ${err.message}\n`);
|
|
623
|
-
return 1;
|
|
624
|
-
}
|
|
625
|
-
throw err;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
// Record the decisions the prompts made, in flag form, so the CLI can print
|
|
629
|
-
// a command that reruns this without any of them. `--yes` is deliberately
|
|
630
|
-
// never recorded: it exists to gate a deployed trigger behind a confirm, and
|
|
631
|
-
// a copy-pasteable line that skips that confirm would be a footgun. `--app`
|
|
632
|
-
// is inferred from the chosen Workflow when it was not passed.
|
|
633
|
-
if (givenApp === undefined)
|
|
634
|
-
recordResolved("--app", choice.app);
|
|
635
|
-
if (givenWorkflow === undefined)
|
|
636
|
-
recordResolved("--workflow", choice.binding);
|
|
637
|
-
// Only when `resolveTier` actually decided something itself — its own
|
|
638
|
-
// prompt, or an explicit `--tier` above (already excluded by `givenTier`).
|
|
639
|
-
// When it fell through to the tier `launch.ts` already entered for the
|
|
640
|
-
// whole session, `recordEnteredTier` has that covered with its own
|
|
641
|
-
// `--tier` prefix; recording it again here would print the flag twice.
|
|
642
|
-
if (givenTier === undefined &&
|
|
643
|
-
tier !== (process.env.DEPLOY_ENV ?? "development")) {
|
|
644
|
-
recordResolved("--tier", tier);
|
|
645
|
-
}
|
|
646
|
-
if (givenPort === undefined && options.port && options.port !== "8787")
|
|
647
|
-
recordResolved("--port", options.port);
|
|
648
|
-
process.stdout.write(`→ ${choice.name} (${choice.app}, ${tier})\n`);
|
|
649
|
-
try {
|
|
650
|
-
const result = await runWithStderr(workflowTriggerArgs(choice, options), triggerEnv);
|
|
651
|
-
if (result.code !== 0 &&
|
|
652
|
-
tier === "development" &&
|
|
653
|
-
isWranglerDevConnectionFailure(result.stderr)) {
|
|
654
|
-
process.stderr.write(wranglerDevConnectionHint(choice.app, options.port ?? "8787"));
|
|
655
|
-
}
|
|
656
|
-
if (result.code === 0 && tier === "development" && options.instanceId) {
|
|
657
|
-
try {
|
|
658
|
-
return await waitForLocalWorkflow(choice.name, options.instanceId, options.port ?? "8787");
|
|
659
|
-
}
|
|
660
|
-
catch (error) {
|
|
661
|
-
process.stderr.write(`devtools workflows run: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
662
|
-
return 1;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
return result.code;
|
|
666
|
-
}
|
|
667
|
-
finally {
|
|
668
|
-
uninstallSignalCleanup?.();
|
|
669
|
-
if (local?.temporary) {
|
|
670
|
-
process.stdout.write("Stopping the temporary dev session…\n");
|
|
671
|
-
await local.temporary.stop();
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
export async function runWorkflowsServe(argv) {
|
|
676
|
-
const options = parseOptions(argv);
|
|
677
|
-
const port = options.port ?? "8787";
|
|
678
|
-
if (validatePort(port)) {
|
|
679
|
-
process.stderr.write("devtools workflows serve: --port must be an integer from 1 to 65535.\n");
|
|
680
|
-
return 1;
|
|
681
|
-
}
|
|
682
|
-
const apps = [
|
|
683
|
-
...new Set(workflowChoices(discoverWranglerConfigs(), ["development"]).map(({ app }) => app)),
|
|
684
|
-
];
|
|
685
|
-
let app = options.app;
|
|
686
|
-
if (app && !apps.includes(app)) {
|
|
687
|
-
process.stderr.write(`devtools workflows serve: no development Workflow is configured for "${app}".\n`);
|
|
688
|
-
return 1;
|
|
689
|
-
}
|
|
690
|
-
if (!app && process.stdin.isTTY) {
|
|
691
|
-
app = unwrap(await select({
|
|
692
|
-
message: "Which app's Workflow runtime should start?",
|
|
693
|
-
options: apps.map((value) => ({ value, label: value })),
|
|
694
|
-
}));
|
|
695
|
-
}
|
|
696
|
-
if (!app) {
|
|
697
|
-
process.stderr.write("devtools workflows serve: pass --app when no terminal is available.\n");
|
|
698
|
-
return 1;
|
|
699
|
-
}
|
|
700
|
-
// Record an app chosen at the prompt (a non-default port too), so the rerun
|
|
701
|
-
// line can start the same session without the picker.
|
|
702
|
-
if (options.app === undefined)
|
|
703
|
-
recordResolved("--app", app);
|
|
704
|
-
if (options.port === undefined && port !== "8787") {
|
|
705
|
-
recordResolved("--port", port);
|
|
706
|
-
}
|
|
707
|
-
if (await isWranglerDevRunning(port)) {
|
|
708
|
-
process.stderr.write(`devtools workflows serve: a Workflow runtime is already running on port ${port}.\n`);
|
|
709
|
-
return 1;
|
|
710
|
-
}
|
|
711
|
-
const dir = appDir(app);
|
|
712
|
-
const running = dir === undefined ? undefined : runningVinextDevPort(dir);
|
|
713
|
-
if (running !== undefined) {
|
|
714
|
-
process.stderr.write(`devtools workflows serve: vinext dev is already running for ${app} on port ${running}; ` +
|
|
715
|
-
"vinext allows one per app, and `workflows run` reuses it.\n");
|
|
716
|
-
return 1;
|
|
717
|
-
}
|
|
718
|
-
const session = await startTemporaryWrangler(app, port);
|
|
719
|
-
if (!session)
|
|
720
|
-
return 1;
|
|
721
|
-
// `session.port`, not `port`: the session auto-picks a free port when the
|
|
722
|
-
// requested one is taken, so this line must report where it actually landed.
|
|
723
|
-
process.stdout.write(`The dev session will keep running on port ${session.port}. Press Ctrl+C to stop it.\n`);
|
|
724
|
-
await new Promise((resolve) => {
|
|
725
|
-
// `process.on`, not `once`: `once` unregisters itself after the first
|
|
726
|
-
// signal, so a second Ctrl+C arriving during `session.stop()`'s up-to-
|
|
727
|
-
// 3-second graceful escalation has no listener left to catch it — Node's
|
|
728
|
-
// default SIGINT/SIGTERM disposition then kills the process before the
|
|
729
|
-
// `stop()` below (and its `runtimeEnv.remove()`) ever runs. The
|
|
730
|
-
// reentrancy guard tells the two calls apart: the first starts the
|
|
731
|
-
// graceful stop below; any further signal forces it instead, then
|
|
732
|
-
// re-raises the signal so the exit status still reflects it.
|
|
733
|
-
let stopping = false;
|
|
734
|
-
const onSignal = (signal) => {
|
|
735
|
-
if (stopping) {
|
|
736
|
-
process.off("SIGINT", onSignal);
|
|
737
|
-
process.off("SIGTERM", onSignal);
|
|
738
|
-
process.stdout.write("Stopping the dev session (forced)…\n");
|
|
739
|
-
session.forceStop();
|
|
740
|
-
process.kill(process.pid, signal);
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
stopping = true;
|
|
744
|
-
resolve();
|
|
745
|
-
};
|
|
746
|
-
process.on("SIGINT", onSignal);
|
|
747
|
-
process.on("SIGTERM", onSignal);
|
|
748
|
-
});
|
|
749
|
-
process.stdout.write("Stopping the dev session…\n");
|
|
750
|
-
await session.stop();
|
|
751
|
-
return 0;
|
|
752
|
-
}
|
|
753
|
-
export async function runWorkflows(argv) {
|
|
754
|
-
const [sub, ...rest] = argv;
|
|
755
|
-
if (sub === "list")
|
|
756
|
-
return runWorkflowsList(rest);
|
|
757
|
-
if (sub === "run")
|
|
758
|
-
return runWorkflowsRun(rest);
|
|
759
|
-
if (sub === "serve")
|
|
760
|
-
return runWorkflowsServe(rest);
|
|
761
|
-
process.stderr.write(`devtools workflows: unknown subcommand "${sub ?? "(none)"}". Expected: list, run or serve.\n`);
|
|
762
|
-
return 1;
|
|
763
|
-
}
|