Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7bddb80
Add real JSDoc types to foundational lib for strict mode
claude Jun 29, 2026
02ffb31
Add real JSDoc types to lib/wrangler, commands, and bin for strict mode
claude Jun 29, 2026
3f52838
Type test suite and enable global strict mode
claude Jun 29, 2026
94d457e
Harden service-binding validation and whoami control-URL guard
claude Jun 29, 2026
00b0c3a
Apply code-review cleanups from the strict conversion
claude Jun 29, 2026
f3af8c2
Trim type-checker-mechanics comments from the strict conversion
claude Jun 29, 2026
519b65f
Document the strict-typecheck bar for contributors
claude Jun 29, 2026
ba92e16
Finish asRecord consolidation and restore a deploy cast note
claude Jun 29, 2026
da6ca0a
Apply full-branch review cleanups
claude Jun 29, 2026
ee8c972
Drop a leftover type-mechanics comment in deploy
claude Jun 29, 2026
e0aa939
Fix phantom `control` flag in tail/doctor values JSDoc
claude Jun 29, 2026
8a4aa84
Fail fast on a non-array `routes` in collectRoutes
claude Jun 29, 2026
d978351
Validate assets.directory as a non-empty string
claude Jun 29, 2026
1fade06
Trim a redundant comment on the assets.directory gate
claude Jun 29, 2026
8c9e5fa
Use a nullish presence check in parseServicesFromCfg
claude Jun 29, 2026
f2acd93
Compute the worker-presence predicate once in secret.js
claude Jun 29, 2026
604bbb7
Merge two stacked comment blocks in parseServicesFromCfg
claude Jun 29, 2026
3acfb49
Annotate empty Map/Set containers to avoid implicit any
claude Jun 29, 2026
58dc33e
Replace the last three `any` casts in tests with unknown/Record
claude Jun 30, 2026
a53ffa0
Validate cfg.name as a non-empty string in packWranglerProject
claude Jun 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ indentation, double quotes, semicolons, and small named functions. Prefer
dependency injection for testable command behavior, as seen in
`runDeployCommand` and `runSecretCommand`. Use kebab-case CLI flags
(`--control-url`) and uppercase environment variables (`ADMIN_TOKEN`,
`CONTROL_URL`, `WDL_NS`).
`CONTROL_URL`, `WDL_NS`). Types are JSDoc, checked by `npm run typecheck`
(`tsc --noEmit`) under `strict`: annotate new parameters and returns with real
types rather than `any`, and use `unknown` plus narrowing for values validated
at runtime.

Markdown wrapping is bilingual by design, normalized with Prettier
(`--embedded-language-formatting=off`; code blocks are hand-formatted) and kept
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ resolution) is exercisable offline. To try commands end to end, point
## Architecture

The CLI is plain ESM JavaScript — no build step; `tsc --noEmit` typechecks the
JSDoc types.
JSDoc types under `strict`, so new code needs real JSDoc annotations on
parameters and returns (no implicit `any`).

| Path | Role |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand Down
23 changes: 22 additions & 1 deletion bin/wdl.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const REGISTRY = [initCmd, deployCmd, secretCmd, workersCmd, deleteCmd, d1Cmd, r
// Alias -> canonical command name.
const ALIASES = { secrets: "secret" };

/** @type {Record<string, CommandModule>} */
const COMMANDS = Object.fromEntries(REGISTRY.map((c) => [c.meta.name, c]));
for (const [alias, target] of Object.entries(ALIASES)) COMMANDS[alias] = COMMANDS[target];

Expand All @@ -38,6 +39,19 @@ for (const c of REGISTRY) {
if (!c.meta.parseOptions) throw new Error(`command "${c.meta.name}" is missing meta.parseOptions`);
}

/**
* One entry of {@link REGISTRY}: a command module exposing its run entrypoint
* and the metadata the dispatcher reads.
* @typedef {{
* main: (argv?: string[]) => Promise<void>,
* meta: { name: string, summary: string, autoloadEnv: boolean, parseOptions: import("node:util").ParseArgsOptionsConfig },
* }} CommandModule
*/

/**
* @param {string[]} [argv]
* @param {{ env?: NodeJS.ProcessEnv, loadEnv?: NonNullable<Parameters<typeof import("../lib/credentials.js").loadCliControlEnv>[1]>["loadEnv"] | null }} [deps]
*/
export async function main(argv = process.argv.slice(2), deps = {}) {
const [command, ...rest] = argv;

Expand All @@ -60,7 +74,8 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
const scanned = scanCommandArgs(commandModule, rest);
// Tests pass loadEnv: null to disable autoload; an injected loader (or the
// real default when undefined) flows straight into loadCliControlEnv.
const loadEnvOverride = Object.hasOwn(deps, "loadEnv") ? deps.loadEnv : undefined;
/** @type {NonNullable<Parameters<typeof loadCliControlEnv>[1]>["loadEnv"]} */
const loadEnvOverride = (Object.hasOwn(deps, "loadEnv") ? deps.loadEnv : undefined) ?? undefined;
const skipAutoload = Object.hasOwn(deps, "loadEnv") && !deps.loadEnv;
// Help never needs credentials, so a malformed .env must not block it.
if (!skipAutoload && commandModule.meta.autoloadEnv && !scanned.help) {
Expand All @@ -86,6 +101,10 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
// positional alias `wdl <command> [flags] help` — are recognized with the
// framework's own isHelpAlias. strict:false never throws on argv input; only
// a broken option schema can throw, and that should surface loudly.
/**
* @param {CommandModule} commandModule
* @param {string[]} args
*/
function scanCommandArgs(commandModule, args) {
const { values, positionals } = parseArgs({
args,
Expand All @@ -108,8 +127,10 @@ function scanCommandArgs(commandModule, args) {
};
}

/** @param {number} exitCode */
function usage(exitCode) {
// Aliases grouped by the command they point at, for the "(alias: …)" note.
/** @type {Record<string, string[]>} */
const aliasesByTarget = {};
for (const [alias, target] of Object.entries(ALIASES)) {
(aliasesByTarget[target] ??= []).push(alias);
Expand Down
23 changes: 21 additions & 2 deletions commands/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const main = command.main;
export const runConfigCommand = command.run;
export const meta = command.meta;

/** @param {{ values: Record<string, any>, positionals: string[], context: import("../lib/command.js").CommandContext }} arg */
/** @param {{ values: { json?: boolean }, positionals: string[], context: import("../lib/command.js").CommandContext }} arg */
async function runConfig({ values, positionals, context }) {
const [subcommand, extra] = positionals;
if (subcommand !== "explain" || extra) throw new CliError(usageText());
Expand All @@ -29,10 +29,22 @@ async function runConfig({ values, positionals, context }) {
controlUrl: publicEntry(state.controlUrl),
token: publicEntry(state.token),
};
writeResult(values.json, body, () => formatConfigExplain(body), context.stdout);
writeResult(values.json === true, body, () => formatConfigExplain(body), context.stdout);
}

/**
* @typedef {object} PublicConfigEntry
* @property {string} value
* @property {string} source
* @property {string} [error]
*/

/**
* @param {import("../lib/config-state.js").ConfigEntry} entry
* @returns {PublicConfigEntry}
*/
function publicEntry(entry) {
/** @type {PublicConfigEntry} */
const out = {
value: entry.display,
source: entry.source,
Expand All @@ -41,6 +53,9 @@ function publicEntry(entry) {
return out;
}

/**
* @param {{ namespace: PublicConfigEntry, controlUrl: PublicConfigEntry, token: PublicConfigEntry }} body
*/
function formatConfigExplain(body) {
return [
...formatBlock("namespace", body.namespace),
Expand All @@ -51,6 +66,10 @@ function formatConfigExplain(body) {
];
}

/**
* @param {string} name
* @param {PublicConfigEntry} entry
*/
function formatBlock(name, entry) {
const lines = [
`${name}:`,
Expand Down
Loading