@zerotal/core 1.4.0 → 1.5.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 +351 -0
- package/package.json +1 -1
- package/src/application/Application.ts +107 -9
- package/src/application/DevErrorPage.ts +82 -0
- package/src/application/diagnostics.ts +111 -0
- package/src/command/CommandRunner.ts +82 -1
- package/src/command/builtin/AssetsBuildCommand.ts +102 -0
- package/src/command/builtin/DeployCommand.ts +315 -0
- package/src/command/builtin/DevCommand.ts +88 -0
- package/src/command/builtin/DoctorCommand.ts +97 -0
- package/src/command/builtin/MakeCommandCommand.ts +2 -0
- package/src/command/builtin/RouteTypesCommand.ts +56 -0
- package/src/command/builtin/ServeCommand.ts +232 -44
- package/src/command/builtin/index.ts +5 -0
- package/src/command/scaffold/zerotal.ts.txt +2 -10
- package/src/config/AppConfig.ts +109 -2
- package/src/config/DeployConfig.ts +71 -0
- package/src/config/index.ts +2 -0
- package/src/config/registry.ts +1 -0
- package/src/container/Container.ts +3 -3
- package/src/container/inject.ts +3 -2
- package/src/context/RequestContext.ts +60 -0
- package/src/contracts/session.ts +18 -3
- package/src/dev/BuildCache.ts +312 -0
- package/src/dev/CssPlugins.ts +93 -7
- package/src/dev/DevBuildHook.ts +14 -1
- package/src/dev/DevDeck.ts +549 -0
- package/src/dev/DevOrchestrator.ts +166 -31
- package/src/dev/DevProcess.ts +221 -0
- package/src/dev/DevReloadMiddleware.ts +1 -1
- package/src/dev/DevSupervisor.ts +363 -0
- package/src/dev/bootBuild.ts +94 -0
- package/src/dev/index.ts +24 -0
- package/src/dev/startDevMode.ts +145 -0
- package/src/doctor/AppDoctor.ts +399 -0
- package/src/doctor/TransportProbe.ts +169 -0
- package/src/events/Emitter.ts +4 -3
- package/src/facade/facades/App.ts +10 -2
- package/src/helpers/index.ts +23 -1
- package/src/helpers/response.ts +18 -8
- package/src/http/Uri.ts +7 -3
- package/src/http/originGuard.ts +1 -1
- package/src/http/url.ts +10 -4
- package/src/index.ts +43 -0
- package/src/lock/LockManager.ts +190 -14
- package/src/lock/drivers/LockDriver.ts +11 -0
- package/src/lock/drivers/MemoryLockDriver.ts +21 -1
- package/src/lock/drivers/RedisLockDriver.ts +64 -8
- package/src/lock/drivers/SqliteLockDriver.ts +13 -0
- package/src/lock/errors.ts +26 -0
- package/src/lock/facades/Lock.ts +30 -5
- package/src/lock/index.ts +2 -2
- package/src/macros/config.macro.ts +2 -0
- package/src/provider/ServiceProvider.ts +40 -0
- package/src/router/Router.ts +111 -13
- package/src/router/registry.ts +123 -0
- package/src/router/routeTypes.ts +132 -0
- package/src/support/classRef.ts +27 -0
- package/src/support/env.ts +69 -2
- package/src/support/unroutedRoutes.ts +37 -0
package/src/support/env.ts
CHANGED
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
* suppressed. Accepts `"production"`, `"prod"`, and `"staging"`,
|
|
14
14
|
* case-insensitively.
|
|
15
15
|
*
|
|
16
|
+
* Pass {@link deployEnv}, not `Bun.env["APP_ENV"]` — after `setAppEnv()` the latter
|
|
17
|
+
* holds a runtime mode, and `isProdLike("web")` is `false` for every deployment.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
16
20
|
* @example
|
|
17
|
-
* if (!isProdLike(
|
|
21
|
+
* if (!isProdLike(deployEnv())) return renderDevErrorPage(error);
|
|
18
22
|
*/
|
|
19
23
|
export function isProdLike(env: string): boolean {
|
|
20
24
|
const normalized = env.trim().toLowerCase();
|
|
@@ -43,6 +47,34 @@ export function isDevSurfaceAllowed(env: string): boolean {
|
|
|
43
47
|
*/
|
|
44
48
|
export const DEV_WORKER_ENV_VAR = "ZT_DEV";
|
|
45
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Where `setAppEnv()` parks the deployment name before overwriting `APP_ENV`.
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export const DEPLOY_ENV_VAR = "ZT_APP_ENV";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The deployment name this process was started with — `production`, `staging`,
|
|
58
|
+
* `local`, whatever the operator set — as opposed to the runtime *mode*.
|
|
59
|
+
*
|
|
60
|
+
* `APP_ENV` carries both meanings, and the second one destroys the first:
|
|
61
|
+
* `setAppEnv()` overwrites it with `web` / `console` / `worker` before the app
|
|
62
|
+
* boots, so a gate that asks `isProdLike(Bun.env["APP_ENV"])` after startup is
|
|
63
|
+
* asking whether `"web"` is production and always getting no. That was not
|
|
64
|
+
* theoretical — it silently disabled the weak-`APP_KEY` refusal and left the
|
|
65
|
+
* ORM's N+1 detector wrapping every query in production.
|
|
66
|
+
*
|
|
67
|
+
* `setAppEnv()` now preserves the original value, and this reads it back. Prefer
|
|
68
|
+
* it to `Bun.env["APP_ENV"]` for **any** production decision. Config is an
|
|
69
|
+
* equally correct source where it is available (`config("app.env")`), but this
|
|
70
|
+
* works before config is loaded and in processes that have none.
|
|
71
|
+
*
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
export function deployEnv(): string {
|
|
75
|
+
return Bun.env[DEPLOY_ENV_VAR] ?? Bun.env["APP_ENV"] ?? "";
|
|
76
|
+
}
|
|
77
|
+
|
|
46
78
|
/**
|
|
47
79
|
* Whether *this process* may expose dev-only surfaces — the stack-trace error
|
|
48
80
|
* page, the trace inspector, an open monitor panel.
|
|
@@ -56,7 +88,9 @@ export const DEV_WORKER_ENV_VAR = "ZT_DEV";
|
|
|
56
88
|
* construction. This is the case that carries dev mode, because `APP_ENV`
|
|
57
89
|
* cannot: `setAppEnv()` overwrites it with a *runtime mode* (`web`,
|
|
58
90
|
* `worker`, `console`) before the app boots, so by the time any gate reads
|
|
59
|
-
* it
|
|
91
|
+
* `APP_ENV` it holds the mode rather than the deployment name. (The name
|
|
92
|
+
* itself is not lost — `setAppEnv()` parks it, and {@link deployEnv} reads
|
|
93
|
+
* it back. What is lost is the ability to learn it from `APP_ENV`.)
|
|
60
94
|
*
|
|
61
95
|
* - `APP_ENV` still names an explicitly non-production environment. This
|
|
62
96
|
* covers processes started outside the CLI — the test harness, and any
|
|
@@ -69,3 +103,36 @@ export function devSurfacesEnabled(): boolean {
|
|
|
69
103
|
if (Bun.env[DEV_WORKER_ENV_VAR] === "1") return true;
|
|
70
104
|
return isDevSurfaceAllowed(Bun.env["APP_ENV"] ?? "");
|
|
71
105
|
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Whether a `DevOrchestrator` owns asset builds for this process.
|
|
109
|
+
*
|
|
110
|
+
* View providers build their bundles once at boot so assets are ready before
|
|
111
|
+
* the first request. Under `serve --dev` that is redundant three times over:
|
|
112
|
+
* the orchestrator process boots the app (registering hooks, and building), then
|
|
113
|
+
* runs the hooks itself, then spawns a worker that boots the app and builds
|
|
114
|
+
* again. Every backend save paid for two of those.
|
|
115
|
+
*
|
|
116
|
+
* Both processes are recognised, for different reasons:
|
|
117
|
+
*
|
|
118
|
+
* - The supervised worker carries {@link DEV_WORKER_ENV_VAR}. The orchestrator
|
|
119
|
+
* has already built and pruned before spawning it, so its assets are on disk
|
|
120
|
+
* before it binds a port.
|
|
121
|
+
* - The orchestrator itself is recognised from `argv`, not an environment
|
|
122
|
+
* variable, because providers boot *before* `ServeCommand.run()` gets to set
|
|
123
|
+
* one — the flag would always arrive too late to be read.
|
|
124
|
+
*
|
|
125
|
+
* Anything else — a plain `serve`, a queue worker, a test — is unsupervised and
|
|
126
|
+
* still builds at boot.
|
|
127
|
+
*
|
|
128
|
+
* @param env Environment to read; defaults to the process environment.
|
|
129
|
+
* @param argv Command line to read; defaults to the process command line.
|
|
130
|
+
* @internal
|
|
131
|
+
*/
|
|
132
|
+
export function isDevOrchestrated(
|
|
133
|
+
env: Record<string, string | undefined> = Bun.env,
|
|
134
|
+
argv: readonly string[] = Bun.argv,
|
|
135
|
+
): boolean {
|
|
136
|
+
if (env[DEV_WORKER_ENV_VAR] === "1") return true;
|
|
137
|
+
return argv.includes("serve") && argv.includes("--dev");
|
|
138
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unrouted `routes/` directory check.
|
|
3
|
+
*
|
|
4
|
+
* A conventional `routes/index.ts` full of `Router.get(...)` calls does nothing
|
|
5
|
+
* until a `routing()` group imports it — the file typechecks, the calls would
|
|
6
|
+
* execute cleanly, and every path in it 404s in a way that is indistinguishable
|
|
7
|
+
* from a typo'd URL. The scaffold wires only `fileBasedRouting()`, so "I made a
|
|
8
|
+
* routes file" and "it needs registering" are otherwise never connected.
|
|
9
|
+
*/
|
|
10
|
+
import { readdirSync } from "node:fs";
|
|
11
|
+
import { resolve, sep } from "node:path";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A warning message when `<root>/routes` holds route files that no `routing()`
|
|
15
|
+
* group loads, or `null` when the directory is absent, empty, or covered.
|
|
16
|
+
*/
|
|
17
|
+
export function unroutedRoutesWarning(root: string, routedFiles: string[]): string | null {
|
|
18
|
+
const dir = resolve(root, "routes");
|
|
19
|
+
let files: string[];
|
|
20
|
+
try {
|
|
21
|
+
files = readdirSync(dir).filter((f) => /\.(ts|js)$/.test(f) && !/\.test\.(ts|js)$/.test(f));
|
|
22
|
+
} catch {
|
|
23
|
+
return null; // no routes/ directory — nothing to warn about
|
|
24
|
+
}
|
|
25
|
+
if (files.length === 0) return null;
|
|
26
|
+
const insideDir = (file: string) => {
|
|
27
|
+
const normalized = resolve(file);
|
|
28
|
+
return normalized === dir || normalized.startsWith(dir + sep);
|
|
29
|
+
};
|
|
30
|
+
if (routedFiles.some(insideDir)) return null;
|
|
31
|
+
const suggestion = files.includes("index.ts") ? "index.ts" : files[0];
|
|
32
|
+
return (
|
|
33
|
+
`routes/ contains ${files.join(", ")} but no routing() group loads it — ` +
|
|
34
|
+
`every route in it will 404. Add .routing("./routes/${suggestion}") to your ` +
|
|
35
|
+
`Application, or remove the directory.`
|
|
36
|
+
);
|
|
37
|
+
}
|