@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
package/dist/completions.d.ts
DELETED
package/dist/completions.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { allPaths } from "./commands.js";
|
|
2
|
-
/**
|
|
3
|
-
* Groups all paths by their parent prefix to build per-parent completion lists.
|
|
4
|
-
*
|
|
5
|
-
* e.g. ["env", "pull"] contributes "pull" to the group keyed "env".
|
|
6
|
-
*/
|
|
7
|
-
function buildGroups() {
|
|
8
|
-
const groups = new Map();
|
|
9
|
-
for (const path of allPaths()) {
|
|
10
|
-
const parent = path.slice(0, -1).join(" ");
|
|
11
|
-
const name = path[path.length - 1];
|
|
12
|
-
const existing = groups.get(parent) ?? [];
|
|
13
|
-
existing.push(name);
|
|
14
|
-
groups.set(parent, existing);
|
|
15
|
-
}
|
|
16
|
-
return groups;
|
|
17
|
-
}
|
|
18
|
-
function generateBash() {
|
|
19
|
-
const groups = buildGroups();
|
|
20
|
-
const topLevel = (groups.get("") ?? []).join(" ");
|
|
21
|
-
const cases = [];
|
|
22
|
-
for (const [parent, children] of groups) {
|
|
23
|
-
if (!parent)
|
|
24
|
-
continue;
|
|
25
|
-
// Match "devtools <parent>" — the last typed word before cursor is a subcommand
|
|
26
|
-
cases.push(` "${parent}") words="${children.join(" ")}" ;;`);
|
|
27
|
-
}
|
|
28
|
-
return [
|
|
29
|
-
"# devtools bash completion",
|
|
30
|
-
"# Source this file or add it to /etc/bash_completion.d/",
|
|
31
|
-
'# eval "$(pnpm devtools completions --shell bash)"',
|
|
32
|
-
"_devtools_complete() {",
|
|
33
|
-
' local cur="${COMP_WORDS[COMP_CWORD]}"',
|
|
34
|
-
' local prev="${COMP_WORDS[COMP_CWORD-1]}"',
|
|
35
|
-
' local words=""',
|
|
36
|
-
"",
|
|
37
|
-
" # Build the command path from all words except the last",
|
|
38
|
-
' local cmd=""',
|
|
39
|
-
" for ((i=1; i<COMP_CWORD; i++)); do",
|
|
40
|
-
' local w="${COMP_WORDS[$i]}"',
|
|
41
|
-
' if [[ "$w" != --* ]]; then',
|
|
42
|
-
' cmd="${cmd:+$cmd }$w"',
|
|
43
|
-
" fi",
|
|
44
|
-
" done",
|
|
45
|
-
"",
|
|
46
|
-
' case "$cmd" in',
|
|
47
|
-
...cases,
|
|
48
|
-
` "") words="${topLevel}" ;;`,
|
|
49
|
-
" esac",
|
|
50
|
-
"",
|
|
51
|
-
' COMPREPLY=($(compgen -W "$words" -- "$cur"))',
|
|
52
|
-
"}",
|
|
53
|
-
"complete -F _devtools_complete devtools",
|
|
54
|
-
"",
|
|
55
|
-
].join("\n");
|
|
56
|
-
}
|
|
57
|
-
function generateZsh() {
|
|
58
|
-
const groups = buildGroups();
|
|
59
|
-
const topLevel = (groups.get("") ?? []).join(" ");
|
|
60
|
-
const cases = [];
|
|
61
|
-
for (const [parent, children] of groups) {
|
|
62
|
-
if (!parent)
|
|
63
|
-
continue;
|
|
64
|
-
cases.push(` "${parent}") candidates=(${children.join(" ")}) ;;`);
|
|
65
|
-
}
|
|
66
|
-
return [
|
|
67
|
-
"#compdef devtools",
|
|
68
|
-
"# devtools zsh completion",
|
|
69
|
-
"# Add to your .zshrc:",
|
|
70
|
-
'# eval "$(pnpm devtools completions --shell zsh)"',
|
|
71
|
-
"_devtools() {",
|
|
72
|
-
" local state",
|
|
73
|
-
` local -a top_level=(${topLevel})`,
|
|
74
|
-
"",
|
|
75
|
-
" # Build the command path from all words except the last",
|
|
76
|
-
' local cmd=""',
|
|
77
|
-
" local -i i",
|
|
78
|
-
" for ((i=2; i<CURRENT; i++)); do",
|
|
79
|
-
' local w="${words[$i]}"',
|
|
80
|
-
' if [[ "$w" != --* ]]; then',
|
|
81
|
-
' cmd="${cmd:+$cmd }$w"',
|
|
82
|
-
" fi",
|
|
83
|
-
" done",
|
|
84
|
-
"",
|
|
85
|
-
' local -a candidates=("${top_level[@]}")',
|
|
86
|
-
' case "$cmd" in',
|
|
87
|
-
...cases,
|
|
88
|
-
" *) ;;",
|
|
89
|
-
" esac",
|
|
90
|
-
"",
|
|
91
|
-
" compadd -a candidates",
|
|
92
|
-
"}",
|
|
93
|
-
"_devtools",
|
|
94
|
-
"",
|
|
95
|
-
].join("\n");
|
|
96
|
-
}
|
|
97
|
-
export function generateCompletions(shell) {
|
|
98
|
-
return shell === "zsh" ? generateZsh() : generateBash();
|
|
99
|
-
}
|
|
100
|
-
export function runCompletions(argv) {
|
|
101
|
-
const shellIdx = argv.indexOf("--shell");
|
|
102
|
-
const shell = shellIdx !== -1 ? argv[shellIdx + 1] : undefined;
|
|
103
|
-
if (shell !== "bash" && shell !== "zsh") {
|
|
104
|
-
process.stderr.write("devtools completions: --shell must be bash or zsh.\n" +
|
|
105
|
-
"Example: pnpm devtools completions --shell bash\n");
|
|
106
|
-
return 1;
|
|
107
|
-
}
|
|
108
|
-
process.stdout.write(generateCompletions(shell));
|
|
109
|
-
return 0;
|
|
110
|
-
}
|
package/dist/cron/commands.d.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { type AppCronMap, type CronTier, type WranglerConfig } from "./discovery.js";
|
|
2
|
-
/**
|
|
3
|
-
* Whether *something* answers at `url`'s origin. Any HTTP reply — even 404 or
|
|
4
|
-
* 405 — means a server is listening; only a transport failure (ECONNREFUSED, a
|
|
5
|
-
* dead host, a DNS miss) counts as unreachable. The short timeout keeps a
|
|
6
|
-
* black-hole host from hanging the command; a refused connection returns
|
|
7
|
-
* immediately regardless.
|
|
8
|
-
*/
|
|
9
|
-
export declare function originReachable(url: string, timeoutMs?: number): Promise<boolean>;
|
|
10
|
-
export interface CronListRow {
|
|
11
|
-
app: string;
|
|
12
|
-
tier: string;
|
|
13
|
-
expr: string;
|
|
14
|
-
human: string;
|
|
15
|
-
label: string;
|
|
16
|
-
kind: "route" | "workflow";
|
|
17
|
-
/** Routes fired (route kind); empty for a workflow row. */
|
|
18
|
-
routes: string[];
|
|
19
|
-
/** Workflows binding started (workflow kind). */
|
|
20
|
-
binding?: string;
|
|
21
|
-
/** The tier's wrangler workflow name for that binding, when declared. */
|
|
22
|
-
workflowName?: string;
|
|
23
|
-
status: "ok" | "never-fires" | "fires-nothing" | "misconfigured";
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Reconciles one app's `CRON_ROUTES` + `WORKFLOW_CRONS` against its Worker and
|
|
27
|
-
* Workflow schedules for each tier. Pure — it takes the already-read wrangler
|
|
28
|
-
* config — so the table is unit-tested without touching the filesystem.
|
|
29
|
-
*
|
|
30
|
-
* A workflow schedule is `ok` only when the tier declares the binding named by
|
|
31
|
-
* the map and that binding natively schedules the expression.
|
|
32
|
-
*/
|
|
33
|
-
export declare function reconcileMap(map: AppCronMap, config: WranglerConfig, tiers: readonly CronTier[]): CronListRow[];
|
|
34
|
-
export declare function runCronList(argv: readonly string[]): Promise<number>;
|
|
35
|
-
export interface CronChoice {
|
|
36
|
-
app: string;
|
|
37
|
-
expr: string;
|
|
38
|
-
label: string;
|
|
39
|
-
routes: string[];
|
|
40
|
-
scheduled: boolean;
|
|
41
|
-
}
|
|
42
|
-
/** Runnable route schedules, including intentionally unscheduled manual jobs. */
|
|
43
|
-
export declare function cronChoices(maps: readonly AppCronMap[], configs: ReadonlyMap<string, WranglerConfig>, tier: CronTier, app?: string): CronChoice[];
|
|
44
|
-
export declare function runCronRun(argv: readonly string[]): Promise<number>;
|
|
45
|
-
/** Resolve the selected app's origin without assuming every app uses port 3000. */
|
|
46
|
-
export declare function resolveBaseUrl(app: string | undefined, tier: CronTier, config: WranglerConfig | undefined, tierEnv: Record<string, string>): string;
|
|
47
|
-
export declare function devServerHint(app: string | undefined, baseUrl: string): string;
|
package/dist/cron/commands.js
DELETED
|
@@ -1,372 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `devtools cron list` and `devtools cron run`.
|
|
3
|
-
*
|
|
4
|
-
* `cron list` reconciles each app's Worker `triggers.crons` and native
|
|
5
|
-
* `workflows[].schedules` (per tier) against its `CRON_ROUTES` and
|
|
6
|
-
* `WORKFLOW_CRONS` exports and surfaces every
|
|
7
|
-
* failure shape:
|
|
8
|
-
* - a Worker cron matching no route map → fires nothing
|
|
9
|
-
* - a CRON_ROUTES / WORKFLOW_CRONS key no tier schedules → never fires
|
|
10
|
-
* - a WORKFLOW_CRONS key whose binding that tier's wrangler never declares →
|
|
11
|
-
* misconfigured
|
|
12
|
-
*
|
|
13
|
-
* `cron run` selects route-backed schedules from the same maps and sends their
|
|
14
|
-
* authenticated GETs sequentially, mirroring `cloudflare/scheduled.ts`.
|
|
15
|
-
* Workflow-backed schedules stay visible in the audit but are triggered by the
|
|
16
|
-
* separate `devtools workflows` command, which reads Wrangler bindings directly.
|
|
17
|
-
*
|
|
18
|
-
* The local run path pre-flights the target origin and, if nothing is
|
|
19
|
-
* listening, prints a tailored hint rather than a raw ECONNREFUSED.
|
|
20
|
-
*/
|
|
21
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
22
|
-
import { join } from "node:path";
|
|
23
|
-
import { confirm, select } from "@clack/prompts";
|
|
24
|
-
import { parse as parseEnv } from "dotenv";
|
|
25
|
-
import { loadEnvLoad } from "../repo/peers.js";
|
|
26
|
-
import { findRepoRoot } from "../repo/root.js";
|
|
27
|
-
import { positionals } from "../args.js";
|
|
28
|
-
import { resolveTier } from "../tier.js";
|
|
29
|
-
import { unwrap } from "../ui.js";
|
|
30
|
-
import { CRON_TIERS, cronsForTier, discoverCronMaps, discoverWranglerConfigs, isCronTier, workflowsForTier, } from "./discovery.js";
|
|
31
|
-
// ── origin reachability ───────────────────────────────────────────────────────
|
|
32
|
-
/**
|
|
33
|
-
* Whether *something* answers at `url`'s origin. Any HTTP reply — even 404 or
|
|
34
|
-
* 405 — means a server is listening; only a transport failure (ECONNREFUSED, a
|
|
35
|
-
* dead host, a DNS miss) counts as unreachable. The short timeout keeps a
|
|
36
|
-
* black-hole host from hanging the command; a refused connection returns
|
|
37
|
-
* immediately regardless.
|
|
38
|
-
*/
|
|
39
|
-
export async function originReachable(url, timeoutMs = 2500) {
|
|
40
|
-
try {
|
|
41
|
-
const origin = new URL(url).origin;
|
|
42
|
-
await fetch(origin, {
|
|
43
|
-
method: "HEAD",
|
|
44
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
45
|
-
});
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// ── Human-readable schedule translator ───────────────────────────────────────
|
|
53
|
-
function describeExpr(expr) {
|
|
54
|
-
let m;
|
|
55
|
-
if (/^0 0 \* \* \*$/.test(expr))
|
|
56
|
-
return "daily at midnight UTC";
|
|
57
|
-
if ((m = /^\*\/(\d+) \* \* \* \*$/.exec(expr)))
|
|
58
|
-
return `every ${m[1]} minutes (UTC)`;
|
|
59
|
-
if ((m = /^(\d+) (\d+) \* \* \*$/.exec(expr))) {
|
|
60
|
-
const hh = m[2].padStart(2, "0");
|
|
61
|
-
const mm = m[1].padStart(2, "0");
|
|
62
|
-
return `daily at ${hh}:${mm} UTC`;
|
|
63
|
-
}
|
|
64
|
-
return expr;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Reconciles one app's `CRON_ROUTES` + `WORKFLOW_CRONS` against its Worker and
|
|
68
|
-
* Workflow schedules for each tier. Pure — it takes the already-read wrangler
|
|
69
|
-
* config — so the table is unit-tested without touching the filesystem.
|
|
70
|
-
*
|
|
71
|
-
* A workflow schedule is `ok` only when the tier declares the binding named by
|
|
72
|
-
* the map and that binding natively schedules the expression.
|
|
73
|
-
*/
|
|
74
|
-
export function reconcileMap(map, config, tiers) {
|
|
75
|
-
const rows = [];
|
|
76
|
-
const routeExprs = new Set(Object.keys(map.routes));
|
|
77
|
-
const workflowExprs = new Set(Object.keys(map.workflows));
|
|
78
|
-
for (const tier of tiers) {
|
|
79
|
-
const workerScheduled = new Set(cronsForTier(config, tier));
|
|
80
|
-
const boundWorkflows = new Map(workflowsForTier(config, tier).map((w) => [w.binding, w]));
|
|
81
|
-
for (const expr of routeExprs) {
|
|
82
|
-
const entry = map.routes[expr];
|
|
83
|
-
rows.push({
|
|
84
|
-
app: map.app,
|
|
85
|
-
tier,
|
|
86
|
-
expr,
|
|
87
|
-
human: describeExpr(expr),
|
|
88
|
-
label: entry.label,
|
|
89
|
-
kind: "route",
|
|
90
|
-
routes: entry.routes,
|
|
91
|
-
status: workerScheduled.has(expr) ? "ok" : "never-fires",
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
for (const expr of workflowExprs) {
|
|
95
|
-
const entry = map.workflows[expr];
|
|
96
|
-
const workflow = boundWorkflows.get(entry.binding);
|
|
97
|
-
const workflowName = workflow?.name;
|
|
98
|
-
const status = workflow === undefined
|
|
99
|
-
? "misconfigured"
|
|
100
|
-
: workflow.schedules?.includes(expr)
|
|
101
|
-
? "ok"
|
|
102
|
-
: "never-fires";
|
|
103
|
-
rows.push({
|
|
104
|
-
app: map.app,
|
|
105
|
-
tier,
|
|
106
|
-
expr,
|
|
107
|
-
human: describeExpr(expr),
|
|
108
|
-
label: entry.label,
|
|
109
|
-
kind: "workflow",
|
|
110
|
-
routes: [],
|
|
111
|
-
binding: entry.binding,
|
|
112
|
-
workflowName,
|
|
113
|
-
status,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
for (const expr of workerScheduled) {
|
|
117
|
-
if (!routeExprs.has(expr)) {
|
|
118
|
-
rows.push({
|
|
119
|
-
app: map.app,
|
|
120
|
-
tier,
|
|
121
|
-
expr,
|
|
122
|
-
human: describeExpr(expr),
|
|
123
|
-
label: "(no CRON_ROUTES or WORKFLOW_CRONS entry)",
|
|
124
|
-
kind: "route",
|
|
125
|
-
routes: [],
|
|
126
|
-
status: "fires-nothing",
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return rows;
|
|
132
|
-
}
|
|
133
|
-
export async function runCronList(argv) {
|
|
134
|
-
const opts = parseCronListOptions(argv);
|
|
135
|
-
if (opts.tier && !isCronTier(opts.tier)) {
|
|
136
|
-
process.stderr.write(`devtools cron list: unknown tier "${opts.tier}". Expected: ${CRON_TIERS.join(", ")}.\n`);
|
|
137
|
-
return 1;
|
|
138
|
-
}
|
|
139
|
-
const maps = await discoverCronMaps();
|
|
140
|
-
const configs = new Map(discoverWranglerConfigs().map(({ app, config }) => [app, config]));
|
|
141
|
-
const appMaps = opts.app ? maps.filter((m) => m.app === opts.app) : maps;
|
|
142
|
-
if (opts.app && appMaps.length === 0) {
|
|
143
|
-
process.stderr.write(`devtools cron list: no cron map found for app "${opts.app}".\n`);
|
|
144
|
-
return 1;
|
|
145
|
-
}
|
|
146
|
-
const tiers = opts.tier
|
|
147
|
-
? [opts.tier]
|
|
148
|
-
: [...CRON_TIERS];
|
|
149
|
-
const rows = appMaps.flatMap((map) => {
|
|
150
|
-
const config = configs.get(map.app);
|
|
151
|
-
if (!config)
|
|
152
|
-
throw new Error(`${map.app}: wrangler.jsonc is missing`);
|
|
153
|
-
return reconcileMap(map, config, tiers);
|
|
154
|
-
});
|
|
155
|
-
if (opts.json) {
|
|
156
|
-
console.log(JSON.stringify(rows, null, 2));
|
|
157
|
-
return 0;
|
|
158
|
-
}
|
|
159
|
-
renderCronList(rows);
|
|
160
|
-
return 0;
|
|
161
|
-
}
|
|
162
|
-
function renderCronList(rows) {
|
|
163
|
-
let currentApp = "";
|
|
164
|
-
let currentTier = "";
|
|
165
|
-
for (const row of rows) {
|
|
166
|
-
if (row.app !== currentApp || row.tier !== currentTier) {
|
|
167
|
-
currentApp = row.app;
|
|
168
|
-
currentTier = row.tier;
|
|
169
|
-
console.log(`\n${row.app} [${row.tier}]`);
|
|
170
|
-
}
|
|
171
|
-
const warn = row.status === "never-fires"
|
|
172
|
-
? " ⚠ never fires (no wrangler schedule)"
|
|
173
|
-
: row.status === "fires-nothing"
|
|
174
|
-
? " ⚠ fires nothing (no CRON_ROUTES or WORKFLOW_CRONS entry)"
|
|
175
|
-
: row.status === "misconfigured"
|
|
176
|
-
? ` ⚠ misconfigured (no "${row.binding}" workflow bound in this tier)`
|
|
177
|
-
: "";
|
|
178
|
-
console.log(` ${row.expr.padEnd(18)} ${row.human}`);
|
|
179
|
-
console.log(` ${row.label}${warn}`);
|
|
180
|
-
if (row.kind === "workflow") {
|
|
181
|
-
const named = row.workflowName ? ` (${row.workflowName})` : "";
|
|
182
|
-
console.log(` → workflow: ${row.binding}${named}`);
|
|
183
|
-
}
|
|
184
|
-
for (const route of row.routes)
|
|
185
|
-
console.log(` ${route}`);
|
|
186
|
-
}
|
|
187
|
-
if (rows.length === 0)
|
|
188
|
-
console.log("(no cron maps found)");
|
|
189
|
-
}
|
|
190
|
-
function parseCronListOptions(argv) {
|
|
191
|
-
const opts = {};
|
|
192
|
-
for (let i = 0; i < argv.length; i++) {
|
|
193
|
-
if (argv[i] === "--app")
|
|
194
|
-
opts.app = argv[i + 1];
|
|
195
|
-
else if (argv[i] === "--tier")
|
|
196
|
-
opts.tier = argv[i + 1];
|
|
197
|
-
else if (argv[i] === "--json")
|
|
198
|
-
opts.json = true;
|
|
199
|
-
}
|
|
200
|
-
return opts;
|
|
201
|
-
}
|
|
202
|
-
/** Runnable route schedules, including intentionally unscheduled manual jobs. */
|
|
203
|
-
export function cronChoices(maps, configs, tier, app) {
|
|
204
|
-
return maps
|
|
205
|
-
.filter((map) => !app || map.app === app)
|
|
206
|
-
.flatMap((map) => {
|
|
207
|
-
const scheduled = new Set(cronsForTier(configs.get(map.app) ?? {}, tier));
|
|
208
|
-
return Object.entries(map.routes).map(([expr, entry]) => ({
|
|
209
|
-
app: map.app,
|
|
210
|
-
expr,
|
|
211
|
-
label: entry.label,
|
|
212
|
-
routes: entry.routes,
|
|
213
|
-
scheduled: scheduled.has(expr),
|
|
214
|
-
}));
|
|
215
|
-
})
|
|
216
|
-
.sort((a, b) => a.app.localeCompare(b.app) || a.expr.localeCompare(b.expr));
|
|
217
|
-
}
|
|
218
|
-
async function confirmDeployed(tier, choice, yes) {
|
|
219
|
-
if (tier === "development" || yes)
|
|
220
|
-
return true;
|
|
221
|
-
if (!process.stdin.isTTY) {
|
|
222
|
-
process.stderr.write(`devtools cron run: --yes is required to fire a job on ${tier}.\n`);
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
return unwrap(await confirm({
|
|
226
|
-
message: `Fire ${choice?.label ?? "this cron job"} on ${tier}?`,
|
|
227
|
-
initialValue: false,
|
|
228
|
-
}));
|
|
229
|
-
}
|
|
230
|
-
export async function runCronRun(argv) {
|
|
231
|
-
const opts = parseCronRunOptions(argv);
|
|
232
|
-
const route = positionals(argv)[0];
|
|
233
|
-
const tier = await resolveTier(opts.tier, "Which tier should receive the cron job?", { label: "devtools cron run" });
|
|
234
|
-
if (!tier)
|
|
235
|
-
return 1;
|
|
236
|
-
const maps = await discoverCronMaps();
|
|
237
|
-
const discovered = discoverWranglerConfigs();
|
|
238
|
-
const configs = new Map(discovered.map(({ app, config }) => [app, config]));
|
|
239
|
-
const choices = cronChoices(maps, configs, tier, opts.app);
|
|
240
|
-
let choice;
|
|
241
|
-
if (opts.cron) {
|
|
242
|
-
const matches = choices.filter((item) => item.expr === opts.cron);
|
|
243
|
-
if (matches.length > 1 && !opts.app) {
|
|
244
|
-
process.stderr.write(`devtools cron run: "${opts.cron}" exists in more than one app; pass --app.\n`);
|
|
245
|
-
return 1;
|
|
246
|
-
}
|
|
247
|
-
choice = matches[0];
|
|
248
|
-
}
|
|
249
|
-
else if (!route) {
|
|
250
|
-
if (choices.length === 0) {
|
|
251
|
-
process.stderr.write("devtools cron run: no route cron jobs were discovered.\n");
|
|
252
|
-
return 1;
|
|
253
|
-
}
|
|
254
|
-
if (!process.stdin.isTTY) {
|
|
255
|
-
process.stderr.write("devtools cron run: pass --cron <expr> (and --app when needed) when no terminal is available.\n");
|
|
256
|
-
return 1;
|
|
257
|
-
}
|
|
258
|
-
choice = unwrap(await select({
|
|
259
|
-
message: "Which cron job should run?",
|
|
260
|
-
options: choices.map((item) => ({
|
|
261
|
-
value: item,
|
|
262
|
-
label: `${item.app} · ${item.label}`,
|
|
263
|
-
hint: `${item.expr} · ${item.scheduled ? "scheduled" : `manual only on ${tier}`}`,
|
|
264
|
-
})),
|
|
265
|
-
}));
|
|
266
|
-
}
|
|
267
|
-
if (!route && !choice) {
|
|
268
|
-
const workflow = maps.some((map) => (!opts.app || map.app === opts.app) && map.workflows[opts.cron]);
|
|
269
|
-
process.stderr.write(workflow
|
|
270
|
-
? `devtools cron run: "${opts.cron}" starts a Workflow; use devtools workflows run.\n`
|
|
271
|
-
: `devtools cron run: no route job found for "${opts.cron}".\n`);
|
|
272
|
-
return 1;
|
|
273
|
-
}
|
|
274
|
-
if (!(await confirmDeployed(tier, choice, opts.yes ?? false)))
|
|
275
|
-
return 1;
|
|
276
|
-
const app = choice?.app ?? opts.app;
|
|
277
|
-
let tierEnv;
|
|
278
|
-
const envLoad = await loadEnvLoad();
|
|
279
|
-
try {
|
|
280
|
-
// override: true because `devtools cron run` itself runs under `with-env`
|
|
281
|
-
// (development), so process.env already holds development's values; the
|
|
282
|
-
// tier picked here — development, staging or production, `resolveTier`
|
|
283
|
-
// above refuses anything else — must win over whatever this process
|
|
284
|
-
// inherited.
|
|
285
|
-
tierEnv = (await envLoad.loadEnvironment(tier, { override: true })).env;
|
|
286
|
-
}
|
|
287
|
-
catch (err) {
|
|
288
|
-
if (err instanceof envLoad.MissingEnvFileError) {
|
|
289
|
-
process.stderr.write(`devtools cron run: ${err.message}\n`);
|
|
290
|
-
return 1;
|
|
291
|
-
}
|
|
292
|
-
throw err;
|
|
293
|
-
}
|
|
294
|
-
const baseUrl = resolveBaseUrl(app, tier, configs.get(app ?? ""), tierEnv);
|
|
295
|
-
const cronSecret = tierEnv["CRON_SECRET"] ?? "";
|
|
296
|
-
const routes = choice?.routes ?? [route];
|
|
297
|
-
// Pre-flight: on a local tier, a refused connection means the dev server
|
|
298
|
-
// isn't up — not that a route is broken. Say so, with the command to start
|
|
299
|
-
// it, instead of failing every route with a raw ECONNREFUSED.
|
|
300
|
-
if (tier === "development" && !(await originReachable(baseUrl))) {
|
|
301
|
-
process.stderr.write(devServerHint(app, baseUrl));
|
|
302
|
-
return 1;
|
|
303
|
-
}
|
|
304
|
-
let failed = false;
|
|
305
|
-
for (const r of routes) {
|
|
306
|
-
const url = new URL(r, `${baseUrl.replace(/\/+$/, "")}/`).toString();
|
|
307
|
-
process.stdout.write(`→ ${url}\n`);
|
|
308
|
-
try {
|
|
309
|
-
const response = await fetch(url, {
|
|
310
|
-
headers: { Authorization: `Bearer ${cronSecret}` },
|
|
311
|
-
});
|
|
312
|
-
if (!response.ok) {
|
|
313
|
-
process.stderr.write(` ${r}: HTTP ${response.status}\n`);
|
|
314
|
-
failed = true;
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
process.stdout.write(` ${r}: OK\n`);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
catch (err) {
|
|
321
|
-
process.stderr.write(` ${r}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
322
|
-
failed = true;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return failed ? 1 : 0;
|
|
326
|
-
}
|
|
327
|
-
function readEnvFile(path) {
|
|
328
|
-
return existsSync(path) ? parseEnv(readFileSync(path, "utf8")) : {};
|
|
329
|
-
}
|
|
330
|
-
/** Resolve the selected app's origin without assuming every app uses port 3000. */
|
|
331
|
-
export function resolveBaseUrl(app, tier, config, tierEnv) {
|
|
332
|
-
if (tier === "development" && app) {
|
|
333
|
-
const appRoot = join(findRepoRoot(), "apps", app);
|
|
334
|
-
const local = {
|
|
335
|
-
...readEnvFile(join(appRoot, ".dev.vars.example")),
|
|
336
|
-
...readEnvFile(join(appRoot, ".dev.vars")),
|
|
337
|
-
};
|
|
338
|
-
if (local["BASE_URL"])
|
|
339
|
-
return local["BASE_URL"];
|
|
340
|
-
}
|
|
341
|
-
if (tier !== "development" && config) {
|
|
342
|
-
const route = (config.env?.[tier]?.routes ?? []).find((candidate) => {
|
|
343
|
-
const pattern = typeof candidate === "string" ? candidate : candidate.pattern;
|
|
344
|
-
return Boolean(pattern && !pattern.includes("*"));
|
|
345
|
-
});
|
|
346
|
-
const pattern = typeof route === "string" ? route : route?.pattern;
|
|
347
|
-
if (pattern)
|
|
348
|
-
return `https://${pattern.replace(/\/\*$/, "")}`;
|
|
349
|
-
}
|
|
350
|
-
return tierEnv["BASE_URL"] ?? "http://localhost:3000";
|
|
351
|
-
}
|
|
352
|
-
// ── not-running hints ─────────────────────────────────────────────────────────
|
|
353
|
-
export function devServerHint(app, baseUrl) {
|
|
354
|
-
const filter = app ? `--filter ${app}` : "--filter <app>";
|
|
355
|
-
return (`devtools cron run: nothing is listening at ${new URL(baseUrl).origin}.\n` +
|
|
356
|
-
"The dev server doesn't appear to be running. Start it, then re-run:\n" +
|
|
357
|
-
` pnpm ${filter} dev\n`);
|
|
358
|
-
}
|
|
359
|
-
function parseCronRunOptions(argv) {
|
|
360
|
-
const opts = {};
|
|
361
|
-
for (let i = 0; i < argv.length; i++) {
|
|
362
|
-
if (argv[i] === "--app")
|
|
363
|
-
opts.app = argv[i + 1];
|
|
364
|
-
else if (argv[i] === "--tier")
|
|
365
|
-
opts.tier = argv[i + 1];
|
|
366
|
-
else if (argv[i] === "--cron")
|
|
367
|
-
opts.cron = argv[i + 1];
|
|
368
|
-
else if (argv[i] === "--yes")
|
|
369
|
-
opts.yes = true;
|
|
370
|
-
}
|
|
371
|
-
return opts;
|
|
372
|
-
}
|
package/dist/cron/discovery.d.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { type CronRoutes as CronRoutesType, type WorkflowCrons as WorkflowCronsType } from "./schema.js";
|
|
2
|
-
export declare const CRON_TIERS: readonly ["development", "staging", "production"];
|
|
3
|
-
export type CronTier = (typeof CRON_TIERS)[number];
|
|
4
|
-
export interface WranglerWorkflow {
|
|
5
|
-
binding: string;
|
|
6
|
-
name: string;
|
|
7
|
-
class_name?: string;
|
|
8
|
-
schedules?: readonly string[];
|
|
9
|
-
}
|
|
10
|
-
export interface WranglerRoute {
|
|
11
|
-
pattern?: string;
|
|
12
|
-
custom_domain?: boolean;
|
|
13
|
-
}
|
|
14
|
-
export interface WranglerTierBlock {
|
|
15
|
-
name?: string;
|
|
16
|
-
triggers?: {
|
|
17
|
-
crons?: readonly string[];
|
|
18
|
-
};
|
|
19
|
-
workflows?: readonly WranglerWorkflow[];
|
|
20
|
-
routes?: readonly (string | WranglerRoute)[];
|
|
21
|
-
}
|
|
22
|
-
export interface WranglerConfig extends WranglerTierBlock {
|
|
23
|
-
env?: Record<string, WranglerTierBlock | undefined>;
|
|
24
|
-
}
|
|
25
|
-
export interface AppWranglerConfig {
|
|
26
|
-
app: string;
|
|
27
|
-
path: string;
|
|
28
|
-
config: WranglerConfig;
|
|
29
|
-
}
|
|
30
|
-
export declare function isCronTier(value: string): value is CronTier;
|
|
31
|
-
/** Parse JSONC with TypeScript's production parser, preserving `//` in strings. */
|
|
32
|
-
export declare function parseWrangler(path: string): WranglerConfig;
|
|
33
|
-
export declare function blockForTier(config: WranglerConfig, tier: CronTier): WranglerTierBlock;
|
|
34
|
-
export declare function cronsForTier(config: WranglerConfig, tier: CronTier): string[];
|
|
35
|
-
export declare function workflowsForTier(config: WranglerConfig, tier: CronTier): WranglerWorkflow[];
|
|
36
|
-
/** Every app that actually carries a Wrangler configuration. */
|
|
37
|
-
export declare function discoverWranglerConfigs(): AppWranglerConfig[];
|
|
38
|
-
export interface AppCronMap {
|
|
39
|
-
app: string;
|
|
40
|
-
/** Absolute path to the scheduled.ts file. */
|
|
41
|
-
path: string;
|
|
42
|
-
routes: CronRoutesType;
|
|
43
|
-
/**
|
|
44
|
-
* Workflow-backed schedules. `{}` for an app that exports no `WORKFLOW_CRONS`
|
|
45
|
-
* (the common case — only schedule-builder has one today).
|
|
46
|
-
*/
|
|
47
|
-
workflows: WorkflowCronsType;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Imports every app's `cloudflare/scheduled.ts` and returns the parsed
|
|
51
|
-
* `CRON_ROUTES` for each app that has one.
|
|
52
|
-
*
|
|
53
|
-
* `apps/sandbox` has no scheduled.ts and no crons; absent is not an error.
|
|
54
|
-
*/
|
|
55
|
-
export declare function discoverCronMaps(): Promise<AppCronMap[]>;
|
package/dist/cron/discovery.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Discovers each app's `CRON_ROUTES` (and optional `WORKFLOW_CRONS`) export by
|
|
3
|
-
* dynamically importing its `cloudflare/scheduled.ts`. Mirrors the
|
|
4
|
-
* env-discovery pattern: found by known path, zod-validated at the boundary. An
|
|
5
|
-
* app without the file has no crons and is skipped; an app whose export fails
|
|
6
|
-
* validation throws with the path.
|
|
7
|
-
*/
|
|
8
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import { pathToFileURL } from "node:url";
|
|
11
|
-
import { flattenDiagnosticMessageText, parseConfigFileTextToJson, } from "typescript";
|
|
12
|
-
import { findRepoRoot } from "../repo/root.js";
|
|
13
|
-
import { CronRoutes, WorkflowCrons, } from "./schema.js";
|
|
14
|
-
export const CRON_TIERS = ["development", "staging", "production"];
|
|
15
|
-
export function isCronTier(value) {
|
|
16
|
-
return CRON_TIERS.includes(value);
|
|
17
|
-
}
|
|
18
|
-
/** Parse JSONC with TypeScript's production parser, preserving `//` in strings. */
|
|
19
|
-
export function parseWrangler(path) {
|
|
20
|
-
const result = parseConfigFileTextToJson(path, readFileSync(path, "utf8"));
|
|
21
|
-
if (result.error) {
|
|
22
|
-
throw new Error(`${path}: ${flattenDiagnosticMessageText(result.error.messageText, "\n")}`);
|
|
23
|
-
}
|
|
24
|
-
return result.config;
|
|
25
|
-
}
|
|
26
|
-
export function blockForTier(config, tier) {
|
|
27
|
-
return tier === "development" ? config : (config.env?.[tier] ?? {});
|
|
28
|
-
}
|
|
29
|
-
export function cronsForTier(config, tier) {
|
|
30
|
-
return [...(blockForTier(config, tier).triggers?.crons ?? [])];
|
|
31
|
-
}
|
|
32
|
-
export function workflowsForTier(config, tier) {
|
|
33
|
-
return [...(blockForTier(config, tier).workflows ?? [])];
|
|
34
|
-
}
|
|
35
|
-
/** Every app that actually carries a Wrangler configuration. */
|
|
36
|
-
export function discoverWranglerConfigs() {
|
|
37
|
-
const appsRoot = join(findRepoRoot(), "apps");
|
|
38
|
-
return readdirSync(appsRoot, { withFileTypes: true })
|
|
39
|
-
.filter((entry) => entry.isDirectory())
|
|
40
|
-
.flatMap((entry) => {
|
|
41
|
-
const path = join(appsRoot, entry.name, "wrangler.jsonc");
|
|
42
|
-
return existsSync(path)
|
|
43
|
-
? [{ app: entry.name, path, config: parseWrangler(path) }]
|
|
44
|
-
: [];
|
|
45
|
-
})
|
|
46
|
-
.sort((a, b) => a.app.localeCompare(b.app));
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Imports every app's `cloudflare/scheduled.ts` and returns the parsed
|
|
50
|
-
* `CRON_ROUTES` for each app that has one.
|
|
51
|
-
*
|
|
52
|
-
* `apps/sandbox` has no scheduled.ts and no crons; absent is not an error.
|
|
53
|
-
*/
|
|
54
|
-
export async function discoverCronMaps() {
|
|
55
|
-
const results = [];
|
|
56
|
-
for (const { app } of discoverWranglerConfigs()) {
|
|
57
|
-
const scheduledPath = join(findRepoRoot(), "apps", app, "cloudflare", "scheduled.ts");
|
|
58
|
-
if (!existsSync(scheduledPath))
|
|
59
|
-
continue;
|
|
60
|
-
const mod = (await import(pathToFileURL(scheduledPath).href));
|
|
61
|
-
const parsed = CronRoutes.safeParse(mod.CRON_ROUTES);
|
|
62
|
-
if (!parsed.success) {
|
|
63
|
-
throw new Error(`${scheduledPath}: CRON_ROUTES does not match the required shape.\n` +
|
|
64
|
-
parsed.error.issues
|
|
65
|
-
.map((i) => ` ${i.path.join(".")}: ${i.message}`)
|
|
66
|
-
.join("\n"));
|
|
67
|
-
}
|
|
68
|
-
// Optional: an app with no Workflow schedules omits the export entirely,
|
|
69
|
-
// which parses as `{}`. A present-but-malformed export still fails closed.
|
|
70
|
-
const parsedWorkflows = WorkflowCrons.safeParse(mod.WORKFLOW_CRONS ?? {});
|
|
71
|
-
if (!parsedWorkflows.success) {
|
|
72
|
-
throw new Error(`${scheduledPath}: WORKFLOW_CRONS does not match the required shape.\n` +
|
|
73
|
-
parsedWorkflows.error.issues
|
|
74
|
-
.map((i) => ` ${i.path.join(".")}: ${i.message}`)
|
|
75
|
-
.join("\n"));
|
|
76
|
-
}
|
|
77
|
-
results.push({
|
|
78
|
-
app,
|
|
79
|
-
path: scheduledPath,
|
|
80
|
-
routes: parsed.data,
|
|
81
|
-
workflows: parsedWorkflows.data,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
return results;
|
|
85
|
-
}
|