@zerotal/core 1.9.0 → 1.10.0
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/CHANGELOG.md +117 -0
- package/api-surface.md +148 -81
- package/package.json +2 -1
- package/src/application/Application.ts +4 -4
- package/src/build/codemod.ts +1 -1
- package/src/command/CommandRunner.ts +2 -0
- package/src/command/builtin/AssetsPruneCommand.ts +138 -0
- package/src/command/builtin/DeployCommand.ts +19 -0
- package/src/command/builtin/index.ts +1 -0
- package/src/command/startZerotal.ts +36 -9
- package/src/config/AppConfig.ts +10 -10
- package/src/conventions/ConventionLoader.ts +39 -2
- package/src/dev/BuildOutput.ts +58 -6
- package/src/dev/CssPlugins.ts +2 -2
- package/src/dev/DevDeck.ts +3 -3
- package/src/dev/DevProcess.ts +2 -2
- package/src/dev/DevSupervisor.ts +5 -5
- package/src/dev/index.ts +6 -1
- package/src/dev/startDevMode.ts +1 -1
- package/src/doctor/throttleIdentity.ts +6 -2
- package/src/health/Health.ts +5 -5
- package/src/helpers/index.ts +2 -1
- package/src/helpers/markdown.ts +7 -0
- package/src/http/UploadedFile.ts +2 -2
- package/src/index.ts +2 -1
- package/src/lock/LockManager.ts +4 -4
- package/src/logger/LoggerMiddleware.ts +1 -1
- package/src/logger/types.ts +4 -4
- package/src/middleware/CorsMiddleware.ts +6 -6
- package/src/middleware/RateLimiter.ts +105 -22
- package/src/middleware/SecureHeadersMiddleware.ts +8 -8
- package/src/middleware/ThrottleMiddleware.ts +57 -4
- package/src/middleware/WebhookMiddleware.ts +7 -7
- package/src/router/RouteHandler.ts +2 -2
- package/src/router/Router.ts +5 -5
- package/src/security/redactGraph.ts +1 -1
- package/src/shared/format.ts +8 -8
- package/src/storage/StorageFilesMiddleware.ts +2 -2
- package/src/storage/types.ts +10 -10
- package/src/support/cookie.ts +6 -6
- package/src/support/deepMerge.ts +49 -0
- package/src/support/runtime.ts +91 -1
package/src/support/deepMerge.ts
CHANGED
|
@@ -155,3 +155,52 @@ export function deepMerge<T extends object>(base: T, override: DeepPartial<T>):
|
|
|
155
155
|
}
|
|
156
156
|
return result;
|
|
157
157
|
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A shape whose optional properties have all been given a value.
|
|
161
|
+
*
|
|
162
|
+
* `Required<T>` is not this, which is the trap. `-?` removes the *optionality* a
|
|
163
|
+
* `?` introduced; it does not remove an `undefined` that was written into the type.
|
|
164
|
+
* So once an options shape says `retentionDays?: number | undefined` — as every
|
|
165
|
+
* public option shape here does, so that `{ x: a ?? undefined }` is writable —
|
|
166
|
+
* `Required<Options>` still hands back `number | undefined`, and the "defaults have
|
|
167
|
+
* been applied" type quietly stops meaning that.
|
|
168
|
+
*
|
|
169
|
+
* Use this for the object a constructor builds after applying its defaults.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts
|
|
173
|
+
* private readonly _opts: Resolved<Pick<Options, "retentionDays">> & Omit<Options, "retentionDays">;
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export type Resolved<T> = { [K in keyof T]-?: Exclude<T[K], undefined> };
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* `value` without the keys whose value is `undefined`.
|
|
180
|
+
*
|
|
181
|
+
* Object spread copies own properties even when they hold `undefined`, so
|
|
182
|
+
* `{ ...defaults, ...options }` lets an explicitly-`undefined` field overwrite a
|
|
183
|
+
* default rather than leave it alone. Every public option shape accepts an explicit
|
|
184
|
+
* `undefined` — that is what makes `{ x: a ?? undefined }` writable — so the merge
|
|
185
|
+
* is where "supplied as undefined" has to be turned back into "not supplied".
|
|
186
|
+
*
|
|
187
|
+
* The return type keeps each key optional while dropping `undefined` from its type,
|
|
188
|
+
* which is exactly the guarantee this provides.
|
|
189
|
+
*
|
|
190
|
+
* @param value - The bag to filter.
|
|
191
|
+
* @returns A new object with the `undefined`-valued keys absent.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* const merged = { ...DEFAULTS, ...definedOnly(options) };
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
export function definedOnly<T extends object>(
|
|
199
|
+
value: T,
|
|
200
|
+
): { [K in keyof T]?: Exclude<T[K], undefined> } {
|
|
201
|
+
const out: Record<string, unknown> = {};
|
|
202
|
+
for (const [key, item] of Object.entries(value)) {
|
|
203
|
+
if (item !== undefined) out[key] = item;
|
|
204
|
+
}
|
|
205
|
+
return out as { [K in keyof T]?: Exclude<T[K], undefined> };
|
|
206
|
+
}
|
package/src/support/runtime.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
import { readFileSync } from "node:fs";
|
|
25
25
|
import { dirname, join } from "node:path";
|
|
26
26
|
|
|
27
|
-
/** Set to `1`/`true` to downgrade a runtime
|
|
27
|
+
/** Set to `1`/`true` to downgrade a runtime disagreement from a refusal to a warning. */
|
|
28
28
|
export const RUNTIME_MISMATCH_ESCAPE = "ZT_ALLOW_RUNTIME_MISMATCH";
|
|
29
29
|
|
|
30
30
|
/** A running runtime and the installed one it disagrees with. */
|
|
@@ -134,3 +134,93 @@ export function bunBinary(): string {
|
|
|
134
134
|
const path = process.execPath;
|
|
135
135
|
return path && path.length > 0 ? path : "bun";
|
|
136
136
|
}
|
|
137
|
+
|
|
138
|
+
/** A running runtime and the floor the project says it needs. */
|
|
139
|
+
export interface RuntimeFloor {
|
|
140
|
+
/** `Bun.version` — the binary this process is executing under. */
|
|
141
|
+
running: string;
|
|
142
|
+
/** The `engines.bun` range the project declares. */
|
|
143
|
+
required: string;
|
|
144
|
+
/** Absolute path of the manifest `required` was read from. */
|
|
145
|
+
manifest: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The project's declared Bun floor, by walking up from `cwd` for a `package.json`
|
|
150
|
+
* with an `engines.bun`.
|
|
151
|
+
*
|
|
152
|
+
* Walking rather than reading one fixed path, for the same reason
|
|
153
|
+
* {@link installedBunVersion} walks: in a workspace the app and the manifest that
|
|
154
|
+
* pins the runtime are not always the same directory. The first `engines.bun` found
|
|
155
|
+
* wins — the nearest one is the app's own.
|
|
156
|
+
*
|
|
157
|
+
* @param cwd - Directory to start from.
|
|
158
|
+
* @returns `{ range, manifest }`, or `null` when nothing up the tree declares one.
|
|
159
|
+
*/
|
|
160
|
+
export function declaredBunFloor(cwd: string): { range: string; manifest: string } | null {
|
|
161
|
+
let dir = cwd;
|
|
162
|
+
// Bounded by the filesystem root: dirname("/") === "/" and dirname("C:\\") === "C:\\".
|
|
163
|
+
for (;;) {
|
|
164
|
+
const manifest = join(dir, "package.json");
|
|
165
|
+
try {
|
|
166
|
+
const parsed = JSON.parse(readFileSync(manifest, "utf8")) as {
|
|
167
|
+
engines?: { bun?: string };
|
|
168
|
+
};
|
|
169
|
+
const range = parsed.engines?.bun;
|
|
170
|
+
if (range) return { range, manifest };
|
|
171
|
+
} catch {
|
|
172
|
+
// No manifest here, or not one we can read — keep climbing.
|
|
173
|
+
}
|
|
174
|
+
const parent = dirname(dir);
|
|
175
|
+
if (parent === dir) return null;
|
|
176
|
+
dir = parent;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Whether this process is running below the Bun version the project says it needs.
|
|
182
|
+
*
|
|
183
|
+
* `engines.bun` is written by every generated app and enforced by nothing, which is
|
|
184
|
+
* the gap {@link runtimeMismatch} does not cover: that one compares the running
|
|
185
|
+
* runtime against an *installed* one, and most projects do not install Bun as a
|
|
186
|
+
* package, so it has nothing to compare against and correctly says nothing.
|
|
187
|
+
*
|
|
188
|
+
* The failure this catches is narrow and expensive. `Intl` output differs between
|
|
189
|
+
* Bun releases, so a suite with currency or date assertions goes red on a runtime
|
|
190
|
+
* that is otherwise fine, and the failures name the code they touch rather than the
|
|
191
|
+
* binary. The version is the last thing anyone checks.
|
|
192
|
+
*
|
|
193
|
+
* An unparseable range is treated as satisfied. A refusal because we could not read
|
|
194
|
+
* a version range is worse than the mismatch it would have prevented.
|
|
195
|
+
*
|
|
196
|
+
* @param cwd - Project root to look under. Defaults to the working directory.
|
|
197
|
+
* @returns The shortfall, or `null` when the floor is met or none is declared.
|
|
198
|
+
*/
|
|
199
|
+
export function runtimeBelowFloor(cwd: string = process.cwd()): RuntimeFloor | null {
|
|
200
|
+
const running = typeof Bun === "undefined" ? "" : Bun.version;
|
|
201
|
+
if (!running) return null;
|
|
202
|
+
const declared = declaredBunFloor(cwd);
|
|
203
|
+
if (!declared) return null;
|
|
204
|
+
if (Bun.semver.satisfies(running, declared.range)) return null;
|
|
205
|
+
return { running, required: declared.range, manifest: declared.manifest };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The message shown when the runtime is below the project's floor.
|
|
210
|
+
*
|
|
211
|
+
* @param floor - The shortfall to describe.
|
|
212
|
+
*/
|
|
213
|
+
export function runtimeBelowFloorMessage(floor: RuntimeFloor): string {
|
|
214
|
+
return (
|
|
215
|
+
`This process is Bun ${floor.running}, and the project requires ${floor.required} ` +
|
|
216
|
+
`(${floor.manifest}).\n\n` +
|
|
217
|
+
` The difference between two Bun releases is real and narrow: Intl formatting, the ` +
|
|
218
|
+
`SQLite bindings and node: compatibility all move, so a handful of assertions are ` +
|
|
219
|
+
`runtime-sensitive and the rest are not. When two of them fail you go looking for a bug ` +
|
|
220
|
+
`in the code they touch, because nothing in the failure says "wrong binary".\n\n` +
|
|
221
|
+
` Fix it by upgrading the runtime, or by lowering engines.bun if this version is ` +
|
|
222
|
+
`genuinely supported:\n` +
|
|
223
|
+
` bun upgrade\n\n` +
|
|
224
|
+
` To run anyway, set ${RUNTIME_MISMATCH_ESCAPE}=1 — it downgrades this to a warning.`
|
|
225
|
+
);
|
|
226
|
+
}
|