@adrianhall/cloudflare-toolkit 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +59 -0
- package/THIRD-PARTY-NOTICES.md +75 -0
- package/dist/cli/generate-wrangler-types/index.d.ts +1 -0
- package/dist/cli/generate-wrangler-types/index.js +329 -0
- package/dist/cli/generate-wrangler-types/index.js.map +1 -0
- package/dist/error-CLYcAvBM.d.ts +28 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors-Ciipq_zr.js +58 -0
- package/dist/errors-Ciipq_zr.js.map +1 -0
- package/dist/factory-BI5gVL_P.js +220 -0
- package/dist/factory-BI5gVL_P.js.map +1 -0
- package/dist/generators-D8WWEHa1.js +165 -0
- package/dist/generators-D8WWEHa1.js.map +1 -0
- package/dist/guards/index.d.ts +2 -0
- package/dist/guards/index.js +2 -0
- package/dist/guards-6K1CVAr5.js +61 -0
- package/dist/guards-6K1CVAr5.js.map +1 -0
- package/dist/hono/index.d.ts +347 -0
- package/dist/hono/index.js +307 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/index-434HN8jN.d.ts +43 -0
- package/dist/index-Byl-ZrCy.d.ts +138 -0
- package/dist/index-CUICemFw.d.ts +129 -0
- package/dist/index-DRIhR-Xn.d.ts +81 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/jwt-BvuKtvby.js +328 -0
- package/dist/jwt-BvuKtvby.js.map +1 -0
- package/dist/logging/index.d.ts +3 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging-CGHjOVLM.js +24 -0
- package/dist/logging-CGHjOVLM.js.map +1 -0
- package/dist/policy-CvS6AvvD.js +20 -0
- package/dist/policy-CvS6AvvD.js.map +1 -0
- package/dist/problem-details/index.d.ts +4 -0
- package/dist/problem-details/index.js +3 -0
- package/dist/problem-details-CuRsLy3Q.js +37 -0
- package/dist/problem-details-CuRsLy3Q.js.map +1 -0
- package/dist/silent-CWpHE65X.js +652 -0
- package/dist/silent-CWpHE65X.js.map +1 -0
- package/dist/testing/index.d.ts +44 -0
- package/dist/testing/index.js +2 -0
- package/dist/types-Cx6NNILW.d.ts +44 -0
- package/dist/types-DCSMb1cp.d.ts +195 -0
- package/dist/types-DXboaWOT.d.ts +29 -0
- package/dist/vite/index.d.ts +79 -0
- package/dist/vite/index.js +417 -0
- package/dist/vite/index.js.map +1 -0
- package/package.json +137 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adrian Hall
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @adrianhall/cloudflare-toolkit
|
|
2
|
+
|
|
3
|
+
A toolkit of utilities and skills for developing Workers on the Cloudflare Dev Platform.
|
|
4
|
+
`@adrianhall/cloudflare-toolkit` replaces the boilerplate historically scattered across
|
|
5
|
+
multiple GitHub-only libraries — defensive guards, HTTP error generators, RFC 9457 problem
|
|
6
|
+
details, structured logging, and Cloudflare Access-aware Hono/Vite middleware — with a single,
|
|
7
|
+
MIT-licensed, npm-installable package.
|
|
8
|
+
|
|
9
|
+
**Full documentation, guides, and API reference:**
|
|
10
|
+
https://adrianhall.github.io/cloudflare-toolkit
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install @adrianhall/cloudflare-toolkit
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quickstart
|
|
19
|
+
|
|
20
|
+
The `hono` subpath exports four independent middleware for a Cloudflare Access-protected Hono
|
|
21
|
+
app: structured logging, Access enforcement, and problem-details-based error handling. Each is
|
|
22
|
+
wired independently:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import {
|
|
26
|
+
cloudflareAccess,
|
|
27
|
+
cloudflareLogger,
|
|
28
|
+
problemDetailsErrorHandler,
|
|
29
|
+
notFoundHandler
|
|
30
|
+
} from "@adrianhall/cloudflare-toolkit/hono";
|
|
31
|
+
import { Hono } from "hono";
|
|
32
|
+
|
|
33
|
+
const app = new Hono();
|
|
34
|
+
|
|
35
|
+
app.use(cloudflareLogger({/* ... */}));
|
|
36
|
+
app.use(cloudflareAccess({/* ... */}));
|
|
37
|
+
|
|
38
|
+
app.onError(problemDetailsErrorHandler());
|
|
39
|
+
app.notFound(notFoundHandler());
|
|
40
|
+
|
|
41
|
+
export default app;
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
See the [documentation site](https://adrianhall.github.io/cloudflare-toolkit) for guides
|
|
45
|
+
covering every export, including the defensive guards, HTTP error generators, the standalone
|
|
46
|
+
logging core, the Vite plugin, and the `generate-wrangler-types` CLI.
|
|
47
|
+
|
|
48
|
+
## AI Skill
|
|
49
|
+
|
|
50
|
+
An installable [Agent Skill](https://www.npmjs.com/package/skills) teaches coding agents how to
|
|
51
|
+
use every export in this package:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
npx skills add adrianhall/cloudflare-toolkit
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
This package vendors (copies source into this repository, rather than depending on) code from
|
|
4
|
+
third-party, MIT-licensed projects. This notice preserves the required copyright/license
|
|
5
|
+
attribution for that vendored code, per docs/specs/SPECv2.md §5.4.
|
|
6
|
+
|
|
7
|
+
## `src/lib/problem-details/`
|
|
8
|
+
|
|
9
|
+
The RFC 9457 Problem Details core primitives under `src/lib/problem-details/` (`ProblemDetailsError`,
|
|
10
|
+
`problemDetails()`, `statusToPhrase`, `statusToSlug`, `createProblemTypeRegistry`, and the
|
|
11
|
+
`ProblemDetails`/`ProblemDetailsInput` types) are a vendored port of:
|
|
12
|
+
|
|
13
|
+
- [`adrianhall/hono-problem-details`](https://github.com/adrianhall/hono-problem-details) — MIT
|
|
14
|
+
License, Copyright (c) 2026 hono-problem-details contributors.
|
|
15
|
+
- Itself a fork of [`paveg/hono-problem-details`](https://github.com/paveg/hono-problem-details) —
|
|
16
|
+
MIT License, Copyright (c) 2026 hono-problem-details contributors.
|
|
17
|
+
|
|
18
|
+
This code is vendored — copied at a point in time — rather than depended upon, because
|
|
19
|
+
`adrianhall/hono-problem-details` is not published to npm and so cannot be a resolvable
|
|
20
|
+
`dependency`/`peerDependency` of a published package. Vendoring does not imply any ongoing
|
|
21
|
+
relationship with, or endorsement by, either upstream project. This notice is the authoritative
|
|
22
|
+
record of the vendored files' origin — the files themselves carry only a purpose-focused `@file`
|
|
23
|
+
comment, not a source-attribution header.
|
|
24
|
+
|
|
25
|
+
Only the Hono-free core primitives were ported. The `zod`/`valibot`/`openapi`/`standard-schema`/
|
|
26
|
+
`opentelemetry` integrations, and the Hono-specific `problemDetailsHandler`, were intentionally
|
|
27
|
+
**not** ported into this subpath — see docs/specs/SPECv2.md §5.4. The Hono-specific handler is instead
|
|
28
|
+
vendored separately, directly below.
|
|
29
|
+
|
|
30
|
+
## `src/lib/hono/error-handler.ts`
|
|
31
|
+
|
|
32
|
+
The `problemDetailsErrorHandler` function is a vendored port of upstream's `problemDetailsHandler`
|
|
33
|
+
(renamed to match this toolkit's naming), from the same two projects and under the same MIT
|
|
34
|
+
license as above:
|
|
35
|
+
|
|
36
|
+
- [`adrianhall/hono-problem-details`](https://github.com/adrianhall/hono-problem-details)'s
|
|
37
|
+
`src/handler.ts` — MIT License, Copyright (c) 2026 hono-problem-details contributors.
|
|
38
|
+
- Itself a fork of [`paveg/hono-problem-details`](https://github.com/paveg/hono-problem-details) —
|
|
39
|
+
MIT License, Copyright (c) 2026 hono-problem-details contributors.
|
|
40
|
+
|
|
41
|
+
It is a **direct re-export**, not a toolkit-authored wrapper (docs/specs/SPECv2.md §5.4/§5.5, §9) — the
|
|
42
|
+
only change beyond the rename is dropping the `otelApi` option and its backing
|
|
43
|
+
`integrations/opentelemetry.ts` file, since the opentelemetry integration (along with
|
|
44
|
+
`zod`/`valibot`/`openapi`/`standard-schema`) is explicitly out of scope for v1 (docs/specs/SPECv2.md
|
|
45
|
+
§5.4). It shares the Hono-free primitives above (`statusToPhrase`, `buildProblemResponse`, etc.)
|
|
46
|
+
rather than duplicating them.
|
|
47
|
+
|
|
48
|
+
`notFoundHandler` in the same directory has no vendored equivalent — it is toolkit-authored (see
|
|
49
|
+
docs/specs/SPECv2.md §5.5) and is not covered by this notice.
|
|
50
|
+
|
|
51
|
+
### MIT License text
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
MIT License
|
|
55
|
+
|
|
56
|
+
Copyright (c) 2026 hono-problem-details contributors
|
|
57
|
+
|
|
58
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
59
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
60
|
+
in the Software without restriction, including without limitation the rights
|
|
61
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
62
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
63
|
+
furnished to do so, subject to the following conditions:
|
|
64
|
+
|
|
65
|
+
The above copyright notice and this permission notice shall be included in all
|
|
66
|
+
copies or substantial portions of the Software.
|
|
67
|
+
|
|
68
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
69
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
70
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
71
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
72
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
73
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
74
|
+
SOFTWARE.
|
|
75
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process$1 from "node:process";
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import { access, stat } from "node:fs/promises";
|
|
5
|
+
import { isAbsolute, resolve } from "node:path";
|
|
6
|
+
import { Command, CommanderError } from "commander";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import spawn from "cross-spawn";
|
|
9
|
+
//#region src/cli/generate-wrangler-types/fs.ts
|
|
10
|
+
/**
|
|
11
|
+
* @file A Node.js filesystem adapter for the `generate-wrangler-types` CLI. Wraps
|
|
12
|
+
* `node:fs/promises` behind the {@link FileSystem} interface so that file I/O can be swapped for
|
|
13
|
+
* an in-memory stub in unit tests without mocking the built-in module directly.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Creates a {@link FileSystem} backed by Node's `fs/promises` module.
|
|
17
|
+
*
|
|
18
|
+
* `fileExists` uses `access(F_OK)` and returns `true` for any accessible path.
|
|
19
|
+
* `getModifiedTime` uses `stat().mtimeMs` to retrieve the last-modified time.
|
|
20
|
+
*
|
|
21
|
+
* @returns A {@link FileSystem} implementation that queries real files.
|
|
22
|
+
*/
|
|
23
|
+
function createFileSystem() {
|
|
24
|
+
return {
|
|
25
|
+
async fileExists(path) {
|
|
26
|
+
try {
|
|
27
|
+
await access(path, constants.F_OK);
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
async getModifiedTime(path) {
|
|
34
|
+
return (await stat(path)).mtimeMs;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/cli/generate-wrangler-types/logger.ts
|
|
40
|
+
/**
|
|
41
|
+
* @file A private stderr logger for the `generate-wrangler-types` CLI. Not exported from any
|
|
42
|
+
* barrel — this logger is an internal implementation detail of this one CLI, not part of the
|
|
43
|
+
* toolkit's public API surface, and is intentionally a separate, simpler abstraction from the
|
|
44
|
+
* `logging` subpath's `Logger`/`Transport` contract (`src/lib/logging/types.ts`).
|
|
45
|
+
*
|
|
46
|
+
* The split is deliberate, not an oversight: this is a Node-only CLI concern (colored, leveled
|
|
47
|
+
* `stderr` output for a `bin` entry point) rather than the flagship Worker/browser structured
|
|
48
|
+
* logging core, and it is a verbatim port from `cloudflare-scripts`. See
|
|
49
|
+
* `docs/specs/SPECv2.md` §12.3 (ARCH-003) for the full rationale.
|
|
50
|
+
*
|
|
51
|
+
* All output is written to `stderr`. Color is applied when `process.stderr.isTTY === true`;
|
|
52
|
+
* otherwise output is plain text. Log line format: `<ISO-UTC-ms> [<level>] <message>`.
|
|
53
|
+
*/
|
|
54
|
+
/** Numeric ordering used to compare {@link LogLevel} values. */
|
|
55
|
+
const LOG_LEVEL_ORDER = {
|
|
56
|
+
debug: 0,
|
|
57
|
+
info: 1,
|
|
58
|
+
warn: 2,
|
|
59
|
+
error: 3
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Applies a chalk color to `line` based on the log level.
|
|
63
|
+
*
|
|
64
|
+
* - `debug` → blue
|
|
65
|
+
* - `info` → green
|
|
66
|
+
* - `warn` → yellow
|
|
67
|
+
* - `error` → red
|
|
68
|
+
*
|
|
69
|
+
* @param level - The log level that determines the color.
|
|
70
|
+
* @param line - The fully-formatted log line to colorize.
|
|
71
|
+
* @returns The colorized string.
|
|
72
|
+
*/
|
|
73
|
+
function colorize(level, line) {
|
|
74
|
+
switch (level) {
|
|
75
|
+
case "debug": return chalk.blue(line);
|
|
76
|
+
case "info": return chalk.green(line);
|
|
77
|
+
case "warn": return chalk.yellow(line);
|
|
78
|
+
case "error": return chalk.red(line);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Creates the default {@link LogSink} that writes to `process.stderr`.
|
|
83
|
+
*
|
|
84
|
+
* Each line is formatted as `<ISO-UTC-ms> [<level>] <message>`. Color is applied when
|
|
85
|
+
* `process.stderr.isTTY === true`.
|
|
86
|
+
*
|
|
87
|
+
* @returns A `LogSink` that writes to `stderr`.
|
|
88
|
+
*/
|
|
89
|
+
function createDefaultSink() {
|
|
90
|
+
const useColor = process.stderr.isTTY === true;
|
|
91
|
+
return (level, message) => {
|
|
92
|
+
const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${message}`;
|
|
93
|
+
const output = useColor ? colorize(level, line) : line;
|
|
94
|
+
process.stderr.write(`${output}\n`);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates a {@link Logger} that forwards messages at or above `options.level` to the configured
|
|
99
|
+
* sink.
|
|
100
|
+
*
|
|
101
|
+
* @param options - Logger configuration including the minimum level and an optional custom sink.
|
|
102
|
+
* @returns A {@link Logger} instance.
|
|
103
|
+
*/
|
|
104
|
+
function createLogger(options) {
|
|
105
|
+
const { level, sink = createDefaultSink() } = options;
|
|
106
|
+
const minOrder = LOG_LEVEL_ORDER[level];
|
|
107
|
+
function log(msgLevel, message) {
|
|
108
|
+
if (LOG_LEVEL_ORDER[msgLevel] >= minOrder) sink(msgLevel, message);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
debug: (message) => log("debug", message),
|
|
112
|
+
info: (message) => log("info", message),
|
|
113
|
+
warn: (message) => log("warn", message),
|
|
114
|
+
error: (message) => log("error", message)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/cli/generate-wrangler-types/run.ts
|
|
119
|
+
/**
|
|
120
|
+
* @file Core orchestration logic for the `generate-wrangler-types` CLI.
|
|
121
|
+
*
|
|
122
|
+
* This module owns the full execution pipeline:
|
|
123
|
+
* 1. Argument parsing (Commander, with `--` passthrough for wrangler args)
|
|
124
|
+
* 2. Logger creation
|
|
125
|
+
* 3. Path resolution
|
|
126
|
+
* 4. Wrangler config existence check
|
|
127
|
+
* 5. Freshness check (compare config vs. output modification times)
|
|
128
|
+
* 6. `wrangler types` execution
|
|
129
|
+
*
|
|
130
|
+
* All external I/O is accessed through the injected {@link GenerateWranglerTypesDeps} interfaces,
|
|
131
|
+
* making the function fully testable without touching the real filesystem or spawning processes.
|
|
132
|
+
*/
|
|
133
|
+
/**
|
|
134
|
+
* Extracts a human-readable message from an unknown `catch` value.
|
|
135
|
+
*
|
|
136
|
+
* Returns `err.message` when the value is an `Error`, otherwise stringifies it with `String()`.
|
|
137
|
+
*
|
|
138
|
+
* @param err - The caught value (may be anything).
|
|
139
|
+
* @returns A string suitable for log messages or user-facing error output.
|
|
140
|
+
*/
|
|
141
|
+
function getErrorMessage(err) {
|
|
142
|
+
return err instanceof Error ? err.message : String(err);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Runs the `generate-wrangler-types` CLI pipeline and returns a POSIX exit code.
|
|
146
|
+
*
|
|
147
|
+
* The function is intentionally pure with respect to side effects: all I/O is delegated to
|
|
148
|
+
* `deps` so that every code path can be exercised in tests without spawning child processes or
|
|
149
|
+
* touching real files.
|
|
150
|
+
*
|
|
151
|
+
* Exit codes:
|
|
152
|
+
* | Code | Meaning |
|
|
153
|
+
* |------|---------|
|
|
154
|
+
* | `0` | Types are already fresh (skipped), or `wrangler types` succeeded |
|
|
155
|
+
* | `1` | Wrangler config file not found (run `provision` first) |
|
|
156
|
+
* | `2` | `wrangler` could not be executed (binary not on PATH / ENOENT) |
|
|
157
|
+
* | `3` | `wrangler types` exited with a non-zero code (code is logged) |
|
|
158
|
+
* | `6` | Argument error (`--verbose` + `--quiet`, unknown option) |
|
|
159
|
+
* | `99` | Unexpected internal error |
|
|
160
|
+
*
|
|
161
|
+
* @param argv - The raw `process.argv` array (first two elements are skipped by Commander).
|
|
162
|
+
* @param deps - Injected I/O dependencies.
|
|
163
|
+
* @returns A numeric exit code as listed above.
|
|
164
|
+
*/
|
|
165
|
+
async function run(argv, deps) {
|
|
166
|
+
const { wrangler, logSink } = deps;
|
|
167
|
+
const fs = deps.fs;
|
|
168
|
+
const separatorIndex = argv.indexOf("--");
|
|
169
|
+
const commanderArgv = separatorIndex === -1 ? argv : argv.slice(0, separatorIndex);
|
|
170
|
+
const extraArgs = separatorIndex === -1 ? [] : argv.slice(separatorIndex + 1);
|
|
171
|
+
const program = new Command();
|
|
172
|
+
program.name("generate-wrangler-types").description("Regenerate worker-configuration.d.ts only when wrangler.jsonc has changed").version("0.0.1", "--version", "Print version and exit").option("-c, --config <file>", "Wrangler config file to watch", "wrangler.jsonc").option("-d, --dir <dir>", "Base directory for resolving relative paths", ".").option("-f, --force", "Force regeneration even if types are already fresh").option("-o, --output <file>", "Output .d.ts file path (relative to --dir)", "worker-configuration.d.ts").option("-q, --quiet", "Quiet logging (min level: warn)").option("-v, --verbose", "Verbose logging (min level: debug)").allowUnknownOption(false);
|
|
173
|
+
program.exitOverride();
|
|
174
|
+
try {
|
|
175
|
+
program.parse(commanderArgv);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if (err instanceof CommanderError) {
|
|
178
|
+
if (err.code === "commander.helpDisplayed" || err.code === "commander.version") return 0;
|
|
179
|
+
return 6;
|
|
180
|
+
}
|
|
181
|
+
process.stderr.write(`Internal error during argument parsing: ${String(err)}\n`);
|
|
182
|
+
return 99;
|
|
183
|
+
}
|
|
184
|
+
const opts = program.opts();
|
|
185
|
+
if (opts.verbose && opts.quiet) {
|
|
186
|
+
process.stderr.write("Error: --verbose (-v) and --quiet (-q) are mutually exclusive\n");
|
|
187
|
+
return 6;
|
|
188
|
+
}
|
|
189
|
+
const logger = createLogger({
|
|
190
|
+
level: opts.verbose ? "debug" : opts.quiet ? "warn" : "info",
|
|
191
|
+
sink: logSink
|
|
192
|
+
});
|
|
193
|
+
const baseDir = opts.dir;
|
|
194
|
+
const configPath = isAbsolute(opts.config) ? opts.config : resolve(baseDir, opts.config);
|
|
195
|
+
const outputPath = isAbsolute(opts.output) ? opts.output : resolve(baseDir, opts.output);
|
|
196
|
+
logger.debug(`Config path: ${configPath}`);
|
|
197
|
+
logger.debug(`Output path: ${outputPath}`);
|
|
198
|
+
if (extraArgs.length > 0) logger.debug(`Wrangler args: ${extraArgs.join(" ")}`);
|
|
199
|
+
if (!await fs.fileExists(configPath)) {
|
|
200
|
+
logger.error(`Wrangler config not found: ${configPath}\n Run \`npm run provision\` to provision infrastructure and generate it.`);
|
|
201
|
+
return 1;
|
|
202
|
+
}
|
|
203
|
+
if (!opts.force) if (await fs.fileExists(outputPath)) {
|
|
204
|
+
let configMtime;
|
|
205
|
+
let outputMtime;
|
|
206
|
+
try {
|
|
207
|
+
configMtime = await fs.getModifiedTime(configPath);
|
|
208
|
+
outputMtime = await fs.getModifiedTime(outputPath);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
process.stderr.write(`Internal error reading file modification times: ${getErrorMessage(err)}\n`);
|
|
211
|
+
return 99;
|
|
212
|
+
}
|
|
213
|
+
if (outputMtime > configMtime) {
|
|
214
|
+
logger.debug(`Types are fresh (output newer than config by ${Math.round(outputMtime - configMtime)}ms) — skipping`);
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
logger.info(`generate-types: ${opts.config} is newer than ${opts.output} — regenerating...`);
|
|
218
|
+
} else logger.info(`generate-types: ${opts.output} not found — generating...`);
|
|
219
|
+
else logger.debug("--force: skipping freshness check");
|
|
220
|
+
logger.debug(`Running: npx wrangler types ${outputPath}${extraArgs.length > 0 ? ` ${extraArgs.join(" ")}` : ""}`);
|
|
221
|
+
let result;
|
|
222
|
+
try {
|
|
223
|
+
result = await wrangler.runTypes(outputPath, extraArgs, resolve(baseDir));
|
|
224
|
+
} catch (err) {
|
|
225
|
+
logger.error(`Failed to launch wrangler: ${getErrorMessage(err)}`);
|
|
226
|
+
return 2;
|
|
227
|
+
}
|
|
228
|
+
if (result.stdout.trim().length > 0) for (const line of result.stdout.trimEnd().split("\n")) logger.debug(`wrangler: ${line}`);
|
|
229
|
+
if (result.stderr.trim().length > 0) for (const line of result.stderr.trimEnd().split("\n")) logger.debug(`wrangler (stderr): ${line}`);
|
|
230
|
+
const exitCode = result.exitCode ?? 1;
|
|
231
|
+
if (exitCode !== 0) {
|
|
232
|
+
logger.error(`wrangler types failed with exit code ${exitCode}`);
|
|
233
|
+
return 3;
|
|
234
|
+
}
|
|
235
|
+
logger.info(`Wrote ${outputPath}`);
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/cli/generate-wrangler-types/wrangler.ts
|
|
240
|
+
/**
|
|
241
|
+
* The default {@link ExecRunner} that spawns a real child process via `cross-spawn`.
|
|
242
|
+
*
|
|
243
|
+
* Captures stdout and stderr as strings. `cross-spawn` resolves Windows `.cmd`/`.bat` shims
|
|
244
|
+
* (e.g. `npx.cmd`) and safely quotes arguments for `cmd.exe` when unavoidable, without ever
|
|
245
|
+
* handing `command`/`args` to a shell as an unescaped, concatenated string (SEC-002).
|
|
246
|
+
*
|
|
247
|
+
* Exported for direct testing of the real spawn path.
|
|
248
|
+
*
|
|
249
|
+
* @param command - The executable to spawn (always `"npx"` in practice).
|
|
250
|
+
* @param args - Arguments passed to `command`.
|
|
251
|
+
* @param options - Options forwarded to `cross-spawn`.
|
|
252
|
+
* @param options.cwd - Working directory for the spawned process.
|
|
253
|
+
* @returns The process result including exit code and captured output.
|
|
254
|
+
* @throws If the process cannot be spawned (e.g. ENOENT).
|
|
255
|
+
*/
|
|
256
|
+
async function defaultExecRunner(command, args, options) {
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
const child = spawn(command, args, {
|
|
259
|
+
cwd: options.cwd,
|
|
260
|
+
stdio: [
|
|
261
|
+
"ignore",
|
|
262
|
+
"pipe",
|
|
263
|
+
"pipe"
|
|
264
|
+
]
|
|
265
|
+
});
|
|
266
|
+
let stdout = "";
|
|
267
|
+
let stderr = "";
|
|
268
|
+
child.stdout.on("data", (chunk) => {
|
|
269
|
+
stdout += chunk.toString();
|
|
270
|
+
});
|
|
271
|
+
child.stderr.on("data", (chunk) => {
|
|
272
|
+
stderr += chunk.toString();
|
|
273
|
+
});
|
|
274
|
+
child.on("error", (err) => {
|
|
275
|
+
reject(err);
|
|
276
|
+
});
|
|
277
|
+
child.on("close", (code) => {
|
|
278
|
+
resolve({
|
|
279
|
+
exitCode: code,
|
|
280
|
+
stdout,
|
|
281
|
+
stderr
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Creates a {@link WranglerRunner} that executes `npx wrangler types` as a real child process.
|
|
288
|
+
*
|
|
289
|
+
* @param execRunner - Optional override for the process spawner. When omitted, the default
|
|
290
|
+
* `cross-spawn`-backed wrapper is used. Inject a stub in tests.
|
|
291
|
+
* @returns A {@link WranglerRunner} implementation.
|
|
292
|
+
*/
|
|
293
|
+
function createWranglerRunner(execRunner) {
|
|
294
|
+
const exec = execRunner ?? defaultExecRunner;
|
|
295
|
+
return { async runTypes(outputPath, extraArgs, cwd) {
|
|
296
|
+
const args = [
|
|
297
|
+
"wrangler",
|
|
298
|
+
"types",
|
|
299
|
+
outputPath,
|
|
300
|
+
...extraArgs
|
|
301
|
+
];
|
|
302
|
+
return exec("npx", args, { cwd });
|
|
303
|
+
} };
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/cli/generate-wrangler-types/index.ts
|
|
307
|
+
/**
|
|
308
|
+
* @file Entry point for the `generate-wrangler-types` CLI binary.
|
|
309
|
+
*
|
|
310
|
+
* Wires together the real filesystem adapter and Wrangler runner, then delegates to {@link run}
|
|
311
|
+
* which owns all argument parsing and business logic. The process exits with the numeric code
|
|
312
|
+
* returned by `run`.
|
|
313
|
+
*
|
|
314
|
+
* The shebang above is preserved verbatim by tsdown in the built
|
|
315
|
+
* `dist/cli/generate-wrangler-types/index.js` so that `package.json#bin` resolves to a directly
|
|
316
|
+
* executable file once npm links it.
|
|
317
|
+
*
|
|
318
|
+
* This file is a thin wiring shim around `run()`, which is fully covered by
|
|
319
|
+
* `test/node/cli/generate-wrangler-types/run.test.ts`, and is excluded from coverage thresholds.
|
|
320
|
+
*/
|
|
321
|
+
const exitCode = await run(process$1.argv, {
|
|
322
|
+
wrangler: createWranglerRunner(),
|
|
323
|
+
fs: createFileSystem()
|
|
324
|
+
});
|
|
325
|
+
process$1.exit(exitCode);
|
|
326
|
+
//#endregion
|
|
327
|
+
export {};
|
|
328
|
+
|
|
329
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["process"],"sources":["../../../src/cli/generate-wrangler-types/fs.ts","../../../src/cli/generate-wrangler-types/logger.ts","../../../src/cli/generate-wrangler-types/run.ts","../../../src/cli/generate-wrangler-types/wrangler.ts","../../../src/cli/generate-wrangler-types/index.ts"],"sourcesContent":["/**\n * @file A Node.js filesystem adapter for the `generate-wrangler-types` CLI. Wraps\n * `node:fs/promises` behind the {@link FileSystem} interface so that file I/O can be swapped for\n * an in-memory stub in unit tests without mocking the built-in module directly.\n */\nimport { constants } from \"node:fs\";\nimport { access, stat } from \"node:fs/promises\";\nimport type { FileSystem } from \"./types.js\";\n\n/**\n * Creates a {@link FileSystem} backed by Node's `fs/promises` module.\n *\n * `fileExists` uses `access(F_OK)` and returns `true` for any accessible path.\n * `getModifiedTime` uses `stat().mtimeMs` to retrieve the last-modified time.\n *\n * @returns A {@link FileSystem} implementation that queries real files.\n */\nexport function createFileSystem(): FileSystem {\n return {\n async fileExists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n },\n\n async getModifiedTime(path: string): Promise<number> {\n const s = await stat(path);\n return s.mtimeMs;\n }\n };\n}\n","/**\n * @file A private stderr logger for the `generate-wrangler-types` CLI. Not exported from any\n * barrel — this logger is an internal implementation detail of this one CLI, not part of the\n * toolkit's public API surface, and is intentionally a separate, simpler abstraction from the\n * `logging` subpath's `Logger`/`Transport` contract (`src/lib/logging/types.ts`).\n *\n * The split is deliberate, not an oversight: this is a Node-only CLI concern (colored, leveled\n * `stderr` output for a `bin` entry point) rather than the flagship Worker/browser structured\n * logging core, and it is a verbatim port from `cloudflare-scripts`. See\n * `docs/specs/SPECv2.md` §12.3 (ARCH-003) for the full rationale.\n *\n * All output is written to `stderr`. Color is applied when `process.stderr.isTTY === true`;\n * otherwise output is plain text. Log line format: `<ISO-UTC-ms> [<level>] <message>`.\n */\nimport chalk from \"chalk\";\n\n/**\n * The set of supported log levels, ordered from least to most severe.\n *\n * - `debug` — verbose diagnostic output (enabled with `-v`)\n * - `info` — normal operational messages (default)\n * - `warn` — non-fatal anomalies\n * - `error` — failures that halt or degrade operation\n */\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\n/**\n * A low-level output function that receives a fully-resolved log record.\n *\n * The default sink writes colored (or plain) timestamped lines to `stderr`. Tests inject an\n * in-memory sink to capture and assert on log output without printing to the console.\n *\n * @param level - The severity level of the message.\n * @param message - The plain-text message body.\n */\nexport type LogSink = (level: LogLevel, message: string) => void;\n\n/**\n * A structured logger with one method per {@link LogLevel}.\n *\n * Messages below the configured minimum level are silently dropped.\n */\nexport interface Logger {\n /** Emit a `debug`-level message. Dropped unless the logger level is `debug`. */\n debug(message: string): void;\n /** Emit an `info`-level message. */\n info(message: string): void;\n /** Emit a `warn`-level message. */\n warn(message: string): void;\n /** Emit an `error`-level message. */\n error(message: string): void;\n}\n\n/**\n * Options accepted by {@link createLogger}.\n */\nexport interface LoggerOptions {\n /** The minimum severity level that will be forwarded to the sink. */\n level: LogLevel;\n /**\n * Optional custom sink. Defaults to a stderr writer that applies chalk colors when\n * `process.stderr.isTTY` is `true`.\n */\n sink?: LogSink;\n}\n\n/** Numeric ordering used to compare {@link LogLevel} values. */\nconst LOG_LEVEL_ORDER: Readonly<Record<LogLevel, number>> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3\n};\n\n/**\n * Applies a chalk color to `line` based on the log level.\n *\n * - `debug` → blue\n * - `info` → green\n * - `warn` → yellow\n * - `error` → red\n *\n * @param level - The log level that determines the color.\n * @param line - The fully-formatted log line to colorize.\n * @returns The colorized string.\n */\nfunction colorize(level: LogLevel, line: string): string {\n switch (level) {\n case \"debug\":\n return chalk.blue(line);\n case \"info\":\n return chalk.green(line);\n case \"warn\":\n return chalk.yellow(line);\n case \"error\":\n return chalk.red(line);\n }\n}\n\n/**\n * Creates the default {@link LogSink} that writes to `process.stderr`.\n *\n * Each line is formatted as `<ISO-UTC-ms> [<level>] <message>`. Color is applied when\n * `process.stderr.isTTY === true`.\n *\n * @returns A `LogSink` that writes to `stderr`.\n */\nfunction createDefaultSink(): LogSink {\n const useColor = process.stderr.isTTY === true;\n return (level: LogLevel, message: string): void => {\n const timestamp = new Date().toISOString();\n const line = `${timestamp} [${level}] ${message}`;\n const output = useColor ? colorize(level, line) : line;\n process.stderr.write(`${output}\\n`);\n };\n}\n\n/**\n * Creates a {@link Logger} that forwards messages at or above `options.level` to the configured\n * sink.\n *\n * @param options - Logger configuration including the minimum level and an optional custom sink.\n * @returns A {@link Logger} instance.\n */\nexport function createLogger(options: LoggerOptions): Logger {\n const { level, sink = createDefaultSink() } = options;\n const minOrder = LOG_LEVEL_ORDER[level];\n\n function log(msgLevel: LogLevel, message: string): void {\n if (LOG_LEVEL_ORDER[msgLevel] >= minOrder) {\n sink(msgLevel, message);\n }\n }\n\n return {\n debug: (message) => log(\"debug\", message),\n info: (message) => log(\"info\", message),\n warn: (message) => log(\"warn\", message),\n error: (message) => log(\"error\", message)\n };\n}\n","/**\n * @file Core orchestration logic for the `generate-wrangler-types` CLI.\n *\n * This module owns the full execution pipeline:\n * 1. Argument parsing (Commander, with `--` passthrough for wrangler args)\n * 2. Logger creation\n * 3. Path resolution\n * 4. Wrangler config existence check\n * 5. Freshness check (compare config vs. output modification times)\n * 6. `wrangler types` execution\n *\n * All external I/O is accessed through the injected {@link GenerateWranglerTypesDeps} interfaces,\n * making the function fully testable without touching the real filesystem or spawning processes.\n */\nimport { isAbsolute, resolve } from \"node:path\";\nimport { Command, CommanderError } from \"commander\";\nimport type { LogLevel, LogSink } from \"./logger.js\";\nimport { createLogger } from \"./logger.js\";\nimport type { FileSystem, WranglerRunner } from \"./types.js\";\n\n// CLI_VERSION is replaced at build time by tsdown's `define` option (tsdown.config.ts).\ndeclare const CLI_VERSION: string;\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts a human-readable message from an unknown `catch` value.\n *\n * Returns `err.message` when the value is an `Error`, otherwise stringifies it with `String()`.\n *\n * @param err - The caught value (may be anything).\n * @returns A string suitable for log messages or user-facing error output.\n */\nfunction getErrorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * External dependencies injected into {@link run} to keep I/O decoupled from business logic and\n * facilitate unit testing.\n */\nexport interface GenerateWranglerTypesDeps {\n /** Wrangler CLI adapter used to execute `wrangler types`. */\n wrangler: WranglerRunner;\n /** Filesystem adapter used for existence and modification time checks. */\n fs: FileSystem;\n /** If provided, used as the logger sink instead of the default stderr writer. */\n logSink?: LogSink;\n}\n\n// ---------------------------------------------------------------------------\n// Core logic (exported for testing)\n// ---------------------------------------------------------------------------\n\n/**\n * Runs the `generate-wrangler-types` CLI pipeline and returns a POSIX exit code.\n *\n * The function is intentionally pure with respect to side effects: all I/O is delegated to\n * `deps` so that every code path can be exercised in tests without spawning child processes or\n * touching real files.\n *\n * Exit codes:\n * | Code | Meaning |\n * |------|---------|\n * | `0` | Types are already fresh (skipped), or `wrangler types` succeeded |\n * | `1` | Wrangler config file not found (run `provision` first) |\n * | `2` | `wrangler` could not be executed (binary not on PATH / ENOENT) |\n * | `3` | `wrangler types` exited with a non-zero code (code is logged) |\n * | `6` | Argument error (`--verbose` + `--quiet`, unknown option) |\n * | `99` | Unexpected internal error |\n *\n * @param argv - The raw `process.argv` array (first two elements are skipped by Commander).\n * @param deps - Injected I/O dependencies.\n * @returns A numeric exit code as listed above.\n */\nexport async function run(argv: string[], deps: GenerateWranglerTypesDeps): Promise<number> {\n const { wrangler, logSink } = deps;\n const fs = deps.fs;\n\n // -------------------------------------------------------------------------\n // Step 1 — Parse arguments\n //\n // Pre-split argv on `--` so that everything after the separator is forwarded verbatim to\n // `wrangler types`, independently of Commander's passthrough handling. Commander only ever\n // sees the first segment.\n // -------------------------------------------------------------------------\n const separatorIndex = argv.indexOf(\"--\");\n const commanderArgv = separatorIndex === -1 ? argv : argv.slice(0, separatorIndex);\n // Everything after `--` is forwarded verbatim to `wrangler types`.\n const extraArgs = separatorIndex === -1 ? [] : argv.slice(separatorIndex + 1);\n\n const program = new Command();\n program\n .name(\"generate-wrangler-types\")\n .description(\"Regenerate worker-configuration.d.ts only when wrangler.jsonc has changed\")\n .version(CLI_VERSION, \"--version\", \"Print version and exit\")\n .option(\"-c, --config <file>\", \"Wrangler config file to watch\", \"wrangler.jsonc\")\n .option(\"-d, --dir <dir>\", \"Base directory for resolving relative paths\", \".\")\n .option(\"-f, --force\", \"Force regeneration even if types are already fresh\")\n .option(\n \"-o, --output <file>\",\n \"Output .d.ts file path (relative to --dir)\",\n \"worker-configuration.d.ts\"\n )\n .option(\"-q, --quiet\", \"Quiet logging (min level: warn)\")\n .option(\"-v, --verbose\", \"Verbose logging (min level: debug)\")\n .allowUnknownOption(false);\n\n program.exitOverride();\n\n try {\n program.parse(commanderArgv);\n } catch (err: unknown) {\n if (err instanceof CommanderError) {\n // --help and --version exit with code 0 after printing.\n if (err.code === \"commander.helpDisplayed\" || err.code === \"commander.version\") {\n return 0;\n }\n // All other parse errors are argument problems → exit 6.\n return 6;\n }\n // Unexpected error during parse.\n process.stderr.write(`Internal error during argument parsing: ${String(err)}\\n`);\n return 99;\n }\n\n const opts = program.opts<{\n config: string;\n dir: string;\n force?: boolean;\n output: string;\n quiet?: boolean;\n verbose?: boolean;\n }>();\n\n // -------------------------------------------------------------------------\n // Step 2 — Check -v / -q conflict\n // -------------------------------------------------------------------------\n if (opts.verbose && opts.quiet) {\n process.stderr.write(\"Error: --verbose (-v) and --quiet (-q) are mutually exclusive\\n\");\n return 6;\n }\n\n // Create the logger now that we know the level.\n const level: LogLevel =\n opts.verbose ? \"debug\"\n : opts.quiet ? \"warn\"\n : \"info\";\n const logger = createLogger({ level, sink: logSink });\n\n // -------------------------------------------------------------------------\n // Step 3 — Resolve paths\n // -------------------------------------------------------------------------\n const baseDir = opts.dir;\n\n const configPath = isAbsolute(opts.config) ? opts.config : resolve(baseDir, opts.config);\n\n const outputPath = isAbsolute(opts.output) ? opts.output : resolve(baseDir, opts.output);\n\n logger.debug(`Config path: ${configPath}`);\n logger.debug(`Output path: ${outputPath}`);\n if (extraArgs.length > 0) {\n logger.debug(`Wrangler args: ${extraArgs.join(\" \")}`);\n }\n\n // -------------------------------------------------------------------------\n // Step 4 — Check config exists\n // -------------------------------------------------------------------------\n const configExists = await fs.fileExists(configPath);\n if (!configExists) {\n logger.error(\n `Wrangler config not found: ${configPath}\\n`\n + \" Run `npm run provision` to provision infrastructure and generate it.\"\n );\n return 1;\n }\n\n // -------------------------------------------------------------------------\n // Step 5 — Freshness check (skip if output is newer than config)\n // -------------------------------------------------------------------------\n if (!opts.force) {\n const outputExists = await fs.fileExists(outputPath);\n if (outputExists) {\n let configMtime: number;\n let outputMtime: number;\n try {\n configMtime = await fs.getModifiedTime(configPath);\n outputMtime = await fs.getModifiedTime(outputPath);\n } catch (err: unknown) {\n process.stderr.write(\n `Internal error reading file modification times: ${getErrorMessage(err)}\\n`\n );\n return 99;\n }\n\n if (outputMtime > configMtime) {\n logger.debug(\n `Types are fresh (output newer than config by ${Math.round(outputMtime - configMtime)}ms) — skipping`\n );\n return 0;\n }\n\n logger.info(`generate-types: ${opts.config} is newer than ${opts.output} — regenerating...`);\n } else {\n logger.info(`generate-types: ${opts.output} not found — generating...`);\n }\n } else {\n logger.debug(\"--force: skipping freshness check\");\n }\n\n // -------------------------------------------------------------------------\n // Step 6 — Run wrangler types\n // -------------------------------------------------------------------------\n logger.debug(\n `Running: npx wrangler types ${outputPath}${extraArgs.length > 0 ? ` ${extraArgs.join(\" \")}` : \"\"}`\n );\n\n let result: Awaited<ReturnType<WranglerRunner[\"runTypes\"]>>;\n try {\n result = await wrangler.runTypes(outputPath, extraArgs, resolve(baseDir));\n } catch (err: unknown) {\n logger.error(`Failed to launch wrangler: ${getErrorMessage(err)}`);\n return 2;\n }\n\n // Log any captured wrangler output at debug level.\n if (result.stdout.trim().length > 0) {\n for (const line of result.stdout.trimEnd().split(\"\\n\")) {\n logger.debug(`wrangler: ${line}`);\n }\n }\n if (result.stderr.trim().length > 0) {\n for (const line of result.stderr.trimEnd().split(\"\\n\")) {\n logger.debug(`wrangler (stderr): ${line}`);\n }\n }\n\n const exitCode = result.exitCode ?? 1;\n\n if (exitCode !== 0) {\n logger.error(`wrangler types failed with exit code ${exitCode}`);\n return 3;\n }\n\n logger.info(`Wrote ${outputPath}`);\n return 0;\n}\n","/**\n * @file A Wrangler CLI adapter for the `generate-wrangler-types` CLI. Wraps the execution of\n * `npx wrangler types` behind the {@link WranglerRunner} interface so that tests can substitute\n * a stub without spawning a real process.\n *\n * The real implementation spawns `npx wrangler types <outputPath> [extraArgs]` in the given\n * working directory and captures stdout/stderr for logging.\n *\n * `outputPath` (attacker-influenceable via `-o/--output`) and `extraArgs` (everything after a\n * `--` separator, forwarded verbatim) are never passed to a shell for interpretation — see\n * SEC-002 (https://github.com/adrianhall/cloudflare-toolkit/issues/47). Process spawning goes\n * through `cross-spawn` instead of `node:child_process.spawn`'s own `shell: true` option: on\n * POSIX it spawns the target binary directly with no shell involved at all, and on Windows it\n * resolves the `npx`/`wrangler` shim's actual `.cmd` file and safely quotes each argument for\n * `cmd.exe` itself, rather than handing it a single, unescaped, attacker-influenceable command\n * line. A value like `--output \"x;rm -rf ~\"` is therefore passed through as one literal argv\n * element, not interpreted as shell syntax.\n */\nimport type { ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport spawn from \"cross-spawn\";\nimport type { WranglerResult, WranglerRunner } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// ExecRunner abstraction\n// ---------------------------------------------------------------------------\n\n/**\n * A function that spawns a child process and returns its result. Injectable so tests can stub\n * process execution without real spawning.\n */\nexport type ExecRunner = (\n command: string,\n args: string[],\n options: { cwd: string }\n) => Promise<WranglerResult>;\n\n// ---------------------------------------------------------------------------\n// Default real implementation\n// ---------------------------------------------------------------------------\n\n/**\n * The default {@link ExecRunner} that spawns a real child process via `cross-spawn`.\n *\n * Captures stdout and stderr as strings. `cross-spawn` resolves Windows `.cmd`/`.bat` shims\n * (e.g. `npx.cmd`) and safely quotes arguments for `cmd.exe` when unavoidable, without ever\n * handing `command`/`args` to a shell as an unescaped, concatenated string (SEC-002).\n *\n * Exported for direct testing of the real spawn path.\n *\n * @param command - The executable to spawn (always `\"npx\"` in practice).\n * @param args - Arguments passed to `command`.\n * @param options - Options forwarded to `cross-spawn`.\n * @param options.cwd - Working directory for the spawned process.\n * @returns The process result including exit code and captured output.\n * @throws If the process cannot be spawned (e.g. ENOENT).\n */\nexport async function defaultExecRunner(\n command: string,\n args: string[],\n options: { cwd: string }\n): Promise<WranglerResult> {\n return new Promise((resolve, reject) => {\n // `cross-spawn`'s type declarations expose the generic `child_process.SpawnOptions`\n // signature rather than Node's own stdio-tuple-narrowed overloads, so TypeScript sees\n // `stdout`/`stderr` below as possibly `null`. The `[\"ignore\", \"pipe\", \"pipe\"]` tuple\n // guarantees both are real `Readable` streams at runtime — Node's own `spawn`\n // implementation (which `cross-spawn` delegates to) never omits a stream for a `\"pipe\"`\n // stdio element — so this cast restores that guarantee for the type checker.\n const child = spawn(command, args, {\n cwd: options.cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\"]\n }) as ChildProcessWithoutNullStreams;\n\n let stdout = \"\";\n let stderr = \"\";\n\n child.stdout.on(\"data\", (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n\n child.stderr.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n\n child.on(\"error\", (err: Error) => {\n reject(err);\n });\n\n child.on(\"close\", (code: number | null) => {\n resolve({ exitCode: code, stdout, stderr });\n });\n });\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a {@link WranglerRunner} that executes `npx wrangler types` as a real child process.\n *\n * @param execRunner - Optional override for the process spawner. When omitted, the default\n * `cross-spawn`-backed wrapper is used. Inject a stub in tests.\n * @returns A {@link WranglerRunner} implementation.\n */\nexport function createWranglerRunner(execRunner?: ExecRunner): WranglerRunner {\n const exec = execRunner ?? defaultExecRunner;\n\n return {\n async runTypes(outputPath: string, extraArgs: string[], cwd: string): Promise<WranglerResult> {\n const args = [\"wrangler\", \"types\", outputPath, ...extraArgs];\n return exec(\"npx\", args, { cwd });\n }\n };\n}\n","#!/usr/bin/env node\n/// <reference types=\"node\" />\n/**\n * @file Entry point for the `generate-wrangler-types` CLI binary.\n *\n * Wires together the real filesystem adapter and Wrangler runner, then delegates to {@link run}\n * which owns all argument parsing and business logic. The process exits with the numeric code\n * returned by `run`.\n *\n * The shebang above is preserved verbatim by tsdown in the built\n * `dist/cli/generate-wrangler-types/index.js` so that `package.json#bin` resolves to a directly\n * executable file once npm links it.\n *\n * This file is a thin wiring shim around `run()`, which is fully covered by\n * `test/node/cli/generate-wrangler-types/run.test.ts`, and is excluded from coverage thresholds.\n */\nimport process from \"node:process\";\nimport { createFileSystem } from \"./fs.js\";\nimport { run } from \"./run.js\";\nimport { createWranglerRunner } from \"./wrangler.js\";\n\nconst exitCode = await run(process.argv, {\n wrangler: createWranglerRunner(),\n fs: createFileSystem()\n});\nprocess.exit(exitCode);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { n as ProblemDetailsInput, t as ProblemDetails } from "./types-Cx6NNILW.js";
|
|
2
|
+
//#region src/lib/problem-details/error.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Error class for RFC 9457 Problem Details.
|
|
5
|
+
* Thrown by {@link problemDetails} and caught by the Hono error handler (`@adrianhall/cloudflare-toolkit/hono`)
|
|
6
|
+
* to produce `application/problem+json` responses.
|
|
7
|
+
*/
|
|
8
|
+
declare class ProblemDetailsError extends Error {
|
|
9
|
+
/** The normalized RFC 9457 Problem Details object. */
|
|
10
|
+
readonly problemDetails: ProblemDetails;
|
|
11
|
+
/**
|
|
12
|
+
* Create a new {@link ProblemDetailsError}.
|
|
13
|
+
*
|
|
14
|
+
* @param input - Missing `type` defaults to `"about:blank"`; missing `title` is derived from
|
|
15
|
+
* the HTTP status code.
|
|
16
|
+
*/
|
|
17
|
+
constructor(input: ProblemDetailsInput);
|
|
18
|
+
/**
|
|
19
|
+
* Build a standalone `application/problem+json` {@link Response} without any Hono handler
|
|
20
|
+
* middleware.
|
|
21
|
+
*
|
|
22
|
+
* @returns The resulting `Response`.
|
|
23
|
+
*/
|
|
24
|
+
getResponse(): Response;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { ProblemDetailsError as t };
|
|
28
|
+
//# sourceMappingURL=error-CLYcAvBM.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as forbidden, c as methodNotAllowed, d as serviceUnavailable, f as unauthorized, i as contentTooLarge, l as notFound, m as unsupportedMediaType, n as InvalidShapeError, o as gone, p as unprocessableContent, r as badRequest, s as internalServerError, t as NullError, u as notImplemented } from "../index-CUICemFw.js";
|
|
2
|
+
export { InvalidShapeError, NullError, badRequest, contentTooLarge, forbidden, gone, internalServerError, methodNotAllowed, notFound, notImplemented, serviceUnavailable, unauthorized, unprocessableContent, unsupportedMediaType };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as internalServerError, c as notImplemented, d as unprocessableContent, f as unsupportedMediaType, i as gone, l as serviceUnavailable, n as contentTooLarge, o as methodNotAllowed, r as forbidden, s as notFound, t as badRequest, u as unauthorized } from "../generators-D8WWEHa1.js";
|
|
2
|
+
import { n as InvalidShapeError, t as NullError } from "../errors-Ciipq_zr.js";
|
|
3
|
+
export { InvalidShapeError, NullError, badRequest, contentTooLarge, forbidden, gone, internalServerError, methodNotAllowed, notFound, notImplemented, serviceUnavailable, unauthorized, unprocessableContent, unsupportedMediaType };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { n as ProblemDetailsError } from "./factory-BI5gVL_P.js";
|
|
2
|
+
import "./generators-D8WWEHa1.js";
|
|
3
|
+
//#region src/lib/errors/invalid-shape-error.ts
|
|
4
|
+
/**
|
|
5
|
+
* @file A specialized internal-server-error class for a non-null value that does not have the
|
|
6
|
+
* shape it was expected to have.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* A specialized `internalServerError()`-shaped {@link ProblemDetailsError} for a non-null value
|
|
10
|
+
* that does not have the shape it was expected to have (e.g. not an object, or a property that is
|
|
11
|
+
* missing or the wrong type). Exists as a distinct, named error class — separate from `NullError`
|
|
12
|
+
* — so guard functions (`sqlCount`) have a single, greppable call site for "wrong shape" failures
|
|
13
|
+
* as opposed to "unexpectedly null/undefined" ones. Still handled uniformly by
|
|
14
|
+
* `problemDetailsErrorHandler` because it remains a `ProblemDetailsError`.
|
|
15
|
+
*/
|
|
16
|
+
var InvalidShapeError = class extends ProblemDetailsError {
|
|
17
|
+
/**
|
|
18
|
+
* Create a new {@link InvalidShapeError}.
|
|
19
|
+
*
|
|
20
|
+
* @param message - Human-readable explanation of what had an unexpected shape or type.
|
|
21
|
+
*/
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super({
|
|
24
|
+
status: 500,
|
|
25
|
+
detail: message
|
|
26
|
+
});
|
|
27
|
+
this.name = "InvalidShapeError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/lib/errors/null-error.ts
|
|
32
|
+
/**
|
|
33
|
+
* @file A specialized internal-server-error class for an unexpected `null`/`undefined` value.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* A specialized `internalServerError()`-shaped {@link ProblemDetailsError} for an unexpected
|
|
37
|
+
* `null`/`undefined` value. Exists as a distinct, named error class so guard functions
|
|
38
|
+
* (`throwIfNull`, `sqlCount`) have a single, greppable call site — it is still handled uniformly
|
|
39
|
+
* by `problemDetailsErrorHandler` because it remains a `ProblemDetailsError`.
|
|
40
|
+
*/
|
|
41
|
+
var NullError = class extends ProblemDetailsError {
|
|
42
|
+
/**
|
|
43
|
+
* Create a new {@link NullError}.
|
|
44
|
+
*
|
|
45
|
+
* @param message - Human-readable explanation of what was unexpectedly null/undefined.
|
|
46
|
+
*/
|
|
47
|
+
constructor(message) {
|
|
48
|
+
super({
|
|
49
|
+
status: 500,
|
|
50
|
+
detail: message
|
|
51
|
+
});
|
|
52
|
+
this.name = "NullError";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
//#endregion
|
|
56
|
+
export { InvalidShapeError as n, NullError as t };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=errors-Ciipq_zr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-Ciipq_zr.js","names":[],"sources":["../src/lib/errors/invalid-shape-error.ts","../src/lib/errors/null-error.ts"],"sourcesContent":["/**\n * @file A specialized internal-server-error class for a non-null value that does not have the\n * shape it was expected to have.\n */\nimport { ProblemDetailsError } from \"../problem-details/error.js\";\n\n/**\n * A specialized `internalServerError()`-shaped {@link ProblemDetailsError} for a non-null value\n * that does not have the shape it was expected to have (e.g. not an object, or a property that is\n * missing or the wrong type). Exists as a distinct, named error class — separate from `NullError`\n * — so guard functions (`sqlCount`) have a single, greppable call site for \"wrong shape\" failures\n * as opposed to \"unexpectedly null/undefined\" ones. Still handled uniformly by\n * `problemDetailsErrorHandler` because it remains a `ProblemDetailsError`.\n */\nexport class InvalidShapeError extends ProblemDetailsError {\n /**\n * Create a new {@link InvalidShapeError}.\n *\n * @param message - Human-readable explanation of what had an unexpected shape or type.\n */\n constructor(message: string) {\n super({ status: 500, detail: message });\n this.name = \"InvalidShapeError\";\n }\n}\n","/**\n * @file A specialized internal-server-error class for an unexpected `null`/`undefined` value.\n */\nimport { ProblemDetailsError } from \"../problem-details/error.js\";\n\n/**\n * A specialized `internalServerError()`-shaped {@link ProblemDetailsError} for an unexpected\n * `null`/`undefined` value. Exists as a distinct, named error class so guard functions\n * (`throwIfNull`, `sqlCount`) have a single, greppable call site — it is still handled uniformly\n * by `problemDetailsErrorHandler` because it remains a `ProblemDetailsError`.\n */\nexport class NullError extends ProblemDetailsError {\n /**\n * Create a new {@link NullError}.\n *\n * @param message - Human-readable explanation of what was unexpectedly null/undefined.\n */\n constructor(message: string) {\n super({ status: 500, detail: message });\n this.name = \"NullError\";\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,IAAa,oBAAb,cAAuC,oBAAoB;;;;;;CAMzD,YAAY,SAAiB;EAC3B,MAAM;GAAE,QAAQ;GAAK,QAAQ;EAAQ,CAAC;EACtC,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;ACbA,IAAa,YAAb,cAA+B,oBAAoB;;;;;;CAMjD,YAAY,SAAiB;EAC3B,MAAM;GAAE,QAAQ;GAAK,QAAQ;EAAQ,CAAC;EACtC,KAAK,OAAO;CACd;AACF"}
|