toiljs 0.0.115 → 0.0.117
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 +10 -0
- package/README.md +16 -7
- package/TYPESCRIPT_LAW.md +1946 -2073
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +2375 -258
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/dev/devtools.js +1 -1
- package/build/client/dev/error-overlay.js +1 -1
- package/build/client/routing/mount.js +1 -1
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/auth-emails.js +7 -3
- package/build/compiler/emails.js +1 -1
- package/build/compiler/index.d.ts +1 -0
- package/build/compiler/index.js +25 -4
- package/build/compiler/pages.js +2 -3
- package/build/compiler/prerender.d.ts +3 -8
- package/build/compiler/prerender.js +88 -127
- package/build/compiler/ssg.js +3 -4
- package/build/compiler/template-build.js +3 -4
- package/build/compiler/toil-docs.generated.js +6 -5
- package/build/compiler/vite.js +3 -6
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/db/database.js +7 -3
- package/build/devserver/db/types.js +1 -1
- package/build/devserver/production.js +4 -3
- package/build/devserver/runtime/crypto.d.ts +1 -1
- package/build/io/.tsbuildinfo +1 -1
- package/build/logger/.tsbuildinfo +1 -1
- package/build/shared/.tsbuildinfo +1 -1
- package/examples/basic/client/routes/analytics.tsx +7 -5
- package/examples/basic/server/tsconfig.json +1 -3
- package/package.json +45 -44
- package/presets/no-uint8array-tostring.js +120 -153
- package/presets/oxlint.d.ts +5 -0
- package/presets/oxlint.js +224 -0
- package/server/runtime/tsconfig.json +1 -3
- package/tsconfig.json +2 -11
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -43
- package/.github/changelog-config.json +0 -45
- package/.github/dependabot.yml +0 -27
- package/.github/workflows/ci.yml +0 -222
- package/.vscode/settings.json +0 -10
- package/docs/README.md +0 -107
- package/docs/auth/README.md +0 -104
- package/docs/auth/configuration.md +0 -105
- package/docs/auth/emails.md +0 -92
- package/docs/auth/extending.md +0 -202
- package/docs/auth/how-it-works.md +0 -138
- package/docs/auth/usage.md +0 -188
- package/docs/backend/README.md +0 -143
- package/docs/backend/data.md +0 -351
- package/docs/backend/rest.md +0 -402
- package/docs/backend/rpc.md +0 -226
- package/docs/background/README.md +0 -114
- package/docs/background/daemons.md +0 -276
- package/docs/background/derive.md +0 -179
- package/docs/cli/README.md +0 -379
- package/docs/concepts/ai-guide.md +0 -132
- package/docs/concepts/config.md +0 -416
- package/docs/concepts/decorators.md +0 -127
- package/docs/concepts/security.md +0 -108
- package/docs/concepts/tiers.md +0 -166
- package/docs/concepts/types.md +0 -216
- package/docs/database/README.md +0 -143
- package/docs/database/capacity.md +0 -350
- package/docs/database/counters.md +0 -174
- package/docs/database/documents.md +0 -277
- package/docs/database/events.md +0 -307
- package/docs/database/membership.md +0 -246
- package/docs/database/setup.md +0 -155
- package/docs/database/unique.md +0 -216
- package/docs/database/views.md +0 -246
- package/docs/frontend/README.md +0 -104
- package/docs/frontend/components.md +0 -286
- package/docs/frontend/data-fetching.md +0 -260
- package/docs/frontend/images.md +0 -148
- package/docs/frontend/metadata.md +0 -344
- package/docs/frontend/navigation.md +0 -296
- package/docs/frontend/rendering.md +0 -236
- package/docs/frontend/routing.md +0 -346
- package/docs/frontend/scripts.md +0 -118
- package/docs/frontend/search.md +0 -191
- package/docs/frontend/styling.md +0 -147
- package/docs/frontend/toil-global.md +0 -216
- package/docs/getting-started/README.md +0 -81
- package/docs/getting-started/create-project.md +0 -131
- package/docs/getting-started/deploy.md +0 -101
- package/docs/getting-started/first-app.md +0 -215
- package/docs/getting-started/installation.md +0 -124
- package/docs/getting-started/migrating.md +0 -125
- package/docs/getting-started/project-structure.md +0 -163
- package/docs/introduction/README.md +0 -66
- package/docs/introduction/design-principles.md +0 -81
- package/docs/introduction/distributed.md +0 -91
- package/docs/introduction/how-it-works.md +0 -121
- package/docs/introduction/hyperscale.md +0 -70
- package/docs/introduction/modern-stack.md +0 -85
- package/docs/introduction/vs-other-frameworks.md +0 -64
- package/docs/introduction/why-toil.md +0 -99
- package/docs/llms.txt +0 -95
- package/docs/realtime/README.md +0 -102
- package/docs/realtime/channels.md +0 -211
- package/docs/realtime/streams.md +0 -369
- package/docs/services/README.md +0 -60
- package/docs/services/analytics.md +0 -279
- package/docs/services/caching.md +0 -175
- package/docs/services/cookies.md +0 -235
- package/docs/services/crypto.md +0 -209
- package/docs/services/email.md +0 -289
- package/docs/services/environment.md +0 -141
- package/docs/services/ratelimit.md +0 -174
- package/docs/services/time.md +0 -85
- package/eslint.config.js +0 -57
- package/examples/basic/client/routes/docs/[...slug].tsx +0 -12
- package/presets/eslint.js +0 -88
- package/src/backend/index.ts +0 -204
- package/src/cli/configure.ts +0 -363
- package/src/cli/create.ts +0 -924
- package/src/cli/db.ts +0 -158
- package/src/cli/diagnostics.ts +0 -665
- package/src/cli/doctor.ts +0 -982
- package/src/cli/features.ts +0 -170
- package/src/cli/index.ts +0 -326
- package/src/cli/notify.ts +0 -120
- package/src/cli/proc.ts +0 -56
- package/src/cli/ui.ts +0 -169
- package/src/cli/update.ts +0 -248
- package/src/cli/updates.ts +0 -92
- package/src/cli/validate.ts +0 -31
- package/src/cli/version-check.ts +0 -125
- package/src/client/auth.ts +0 -869
- package/src/client/channel/channel.ts +0 -146
- package/src/client/components/Form.tsx +0 -65
- package/src/client/components/Image.tsx +0 -160
- package/src/client/components/Script.tsx +0 -113
- package/src/client/components/Slot.tsx +0 -21
- package/src/client/dev/devtools.tsx +0 -1089
- package/src/client/dev/error-overlay.tsx +0 -225
- package/src/client/errors.ts +0 -11
- package/src/client/head/head.ts +0 -182
- package/src/client/head/metadata.ts +0 -110
- package/src/client/index.ts +0 -130
- package/src/client/navigation/Link.tsx +0 -108
- package/src/client/navigation/NavLink.tsx +0 -86
- package/src/client/navigation/navigation.ts +0 -235
- package/src/client/navigation/prefetch.ts +0 -169
- package/src/client/navigation/scroll.ts +0 -53
- package/src/client/routing/Router.tsx +0 -216
- package/src/client/routing/action.ts +0 -122
- package/src/client/routing/error-boundary.tsx +0 -43
- package/src/client/routing/hooks.ts +0 -140
- package/src/client/routing/lazy.ts +0 -77
- package/src/client/routing/loader.ts +0 -381
- package/src/client/routing/match.ts +0 -47
- package/src/client/routing/mount.tsx +0 -90
- package/src/client/routing/params-context.ts +0 -10
- package/src/client/routing/slot-context.ts +0 -7
- package/src/client/rpc.ts +0 -97
- package/src/client/search/search.ts +0 -189
- package/src/client/search/use-page-search.ts +0 -73
- package/src/client/ssr/markers.tsx +0 -172
- package/src/client/stream/client.ts +0 -237
- package/src/client/types.ts +0 -73
- package/src/compiler/auth-emails.ts +0 -312
- package/src/compiler/config.ts +0 -369
- package/src/compiler/docs.ts +0 -127
- package/src/compiler/email-preview.ts +0 -314
- package/src/compiler/emails.ts +0 -404
- package/src/compiler/fonts.ts +0 -89
- package/src/compiler/generate.ts +0 -471
- package/src/compiler/image-blur.ts +0 -42
- package/src/compiler/image-report.ts +0 -88
- package/src/compiler/index.ts +0 -1362
- package/src/compiler/pages.ts +0 -49
- package/src/compiler/plugin.ts +0 -391
- package/src/compiler/prerender.ts +0 -263
- package/src/compiler/routes.ts +0 -170
- package/src/compiler/seo.ts +0 -415
- package/src/compiler/sri.ts +0 -144
- package/src/compiler/ssg.ts +0 -177
- package/src/compiler/ssr-codegen.ts +0 -85
- package/src/compiler/template-build.ts +0 -676
- package/src/compiler/template.ts +0 -262
- package/src/compiler/toil-docs.generated.ts +0 -77
- package/src/compiler/vite.ts +0 -245
- package/src/devserver/analytics/index.ts +0 -317
- package/src/devserver/config/dotenv.ts +0 -102
- package/src/devserver/config/env.ts +0 -30
- package/src/devserver/config/ratelimit.ts +0 -123
- package/src/devserver/daemon/catalog.ts +0 -120
- package/src/devserver/daemon/cron.ts +0 -87
- package/src/devserver/daemon/host.ts +0 -134
- package/src/devserver/daemon/index.ts +0 -369
- package/src/devserver/daemon/runtime.ts +0 -48
- package/src/devserver/db/catalog.ts +0 -109
- package/src/devserver/db/database.ts +0 -1834
- package/src/devserver/db/derives.ts +0 -121
- package/src/devserver/db/index.ts +0 -20
- package/src/devserver/db/routeKinds.ts +0 -191
- package/src/devserver/db/types.ts +0 -168
- package/src/devserver/email/caps.ts +0 -0
- package/src/devserver/email/config.ts +0 -141
- package/src/devserver/email/console.ts +0 -348
- package/src/devserver/email/index.ts +0 -155
- package/src/devserver/email/providers.ts +0 -131
- package/src/devserver/email/status.ts +0 -23
- package/src/devserver/email/validate.ts +0 -37
- package/src/devserver/email/wire.ts +0 -55
- package/src/devserver/http/cache.ts +0 -0
- package/src/devserver/http/envelope.ts +0 -157
- package/src/devserver/http/proxy.ts +0 -172
- package/src/devserver/http/runtime.ts +0 -387
- package/src/devserver/index.ts +0 -43
- package/src/devserver/production-ipc.ts +0 -63
- package/src/devserver/production-worker.ts +0 -83
- package/src/devserver/production.ts +0 -718
- package/src/devserver/runtime/crypto.ts +0 -587
- package/src/devserver/runtime/host.ts +0 -427
- package/src/devserver/runtime/module.ts +0 -442
- package/src/devserver/server.ts +0 -251
- package/src/devserver/ssr.ts +0 -208
- package/src/devserver/stream/catalog.ts +0 -106
- package/src/devserver/stream/host.ts +0 -42
- package/src/devserver/stream/index.ts +0 -301
- package/src/devserver/stream/manager.ts +0 -167
- package/src/devserver/stream/router.ts +0 -101
- package/src/devserver/stream/wire.ts +0 -58
- package/src/devserver/stream/ws.ts +0 -78
- package/src/devserver/wasm/sections.ts +0 -59
- package/src/devserver/wasm/surface.ts +0 -115
- package/src/io/FastMap.ts +0 -151
- package/src/io/FastSet.ts +0 -113
- package/src/io/codec.ts +0 -362
- package/src/io/index.ts +0 -10
- package/src/io/lengths.ts +0 -14
- package/src/io/types.ts +0 -19
- package/src/logger/index.ts +0 -22
- package/src/shared/index.ts +0 -46
- package/test/analytics-dev.test.ts +0 -113
- package/test/assembly/aspect-shim.ts +0 -33
- package/test/assembly/cookie.spec.ts +0 -303
- package/test/assembly/example.spec.ts +0 -22
- package/test/assembly/ssr.spec.ts +0 -95
- package/test/assembly.test.ts +0 -59
- package/test/auth-emails.test.ts +0 -118
- package/test/built-ssr.test.ts +0 -148
- package/test/channel.test.ts +0 -21
- package/test/config-host-port.test.ts +0 -59
- package/test/configure.test.ts +0 -94
- package/test/daemon-build.test.ts +0 -278
- package/test/daemon-catalog.test.ts +0 -349
- package/test/daemon-emulation.test.ts +0 -256
- package/test/db.test.ts +0 -0
- package/test/devserver-database.test.ts +0 -1102
- package/test/devserver-email.test.ts +0 -306
- package/test/devserver-pqauth.test.ts +0 -93
- package/test/devserver-secrets.test.ts +0 -63
- package/test/devserver.test.ts +0 -259
- package/test/doctor.test.ts +0 -298
- package/test/dom/Image.test.tsx +0 -144
- package/test/dom/Link.test.tsx +0 -67
- package/test/dom/NavLink.test.tsx +0 -37
- package/test/dom/Script.test.tsx +0 -48
- package/test/dom/action.test.tsx +0 -146
- package/test/dom/error-overlay.test.tsx +0 -44
- package/test/dom/loader.test.tsx +0 -121
- package/test/dom/navigation.test.ts +0 -59
- package/test/dom/revalidate.test.tsx +0 -38
- package/test/dom/route-head.test.tsx +0 -56
- package/test/dom/router-loading.test.tsx +0 -44
- package/test/dom/scroll.test.ts +0 -56
- package/test/dom/slot.test.tsx +0 -131
- package/test/dom/use-metadata.test.tsx +0 -58
- package/test/dom/view-transitions.test.tsx +0 -53
- package/test/dynamic-routing.test.ts +0 -132
- package/test/email-console.test.ts +0 -86
- package/test/email-preview.test.ts +0 -73
- package/test/emails.test.ts +0 -58
- package/test/errors.test.ts +0 -21
- package/test/example-guestbook.test.ts +0 -123
- package/test/features.test.ts +0 -149
- package/test/fixtures/bignum-wire/spec.ts +0 -25
- package/test/fixtures/daemon-app.ts +0 -65
- package/test/fixtures/stream-echo.ts +0 -26
- package/test/fixtures/stream-gate.ts +0 -28
- package/test/fixtures/stream-trap.ts +0 -18
- package/test/fixtures/stream-typed.ts +0 -41
- package/test/fonts.test.ts +0 -28
- package/test/global-setup.ts +0 -17
- package/test/head.test.ts +0 -42
- package/test/image-blur.test.ts +0 -50
- package/test/io.test.ts +0 -117
- package/test/metadata.test.ts +0 -40
- package/test/navlink.test.ts +0 -28
- package/test/pages.test.ts +0 -105
- package/test/placeholder.test.ts +0 -9
- package/test/pqauth-e2e.test.ts +0 -216
- package/test/pqauth-email.test.ts +0 -801
- package/test/prerender.test.ts +0 -116
- package/test/prettier-plugin.test.ts +0 -46
- package/test/routes.test.ts +0 -76
- package/test/rpc-bignum-wire.test.ts +0 -164
- package/test/rpc-dispatch.test.ts +0 -132
- package/test/rpc-kinds.test.ts +0 -18
- package/test/rpc.test.ts +0 -66
- package/test/search.test.ts +0 -114
- package/test/seo.test.ts +0 -175
- package/test/slot-layouts.test.ts +0 -69
- package/test/sri.test.ts +0 -129
- package/test/ssg.test.ts +0 -36
- package/test/ssr-hydration.test.tsx +0 -122
- package/test/ssr-render.test.ts +0 -197
- package/test/ssr-template.test.tsx +0 -482
- package/test/stream-emulation.test.ts +0 -479
- package/test/ui.test.ts +0 -40
- package/test/update.test.ts +0 -58
- package/test/validate.test.ts +0 -42
- package/test/version-check.test.ts +0 -130
- package/tests/data-parity/generated-parity.ts +0 -99
- package/tests/data-parity/parity.ts +0 -80
- package/tests/data-parity/spec.ts +0 -46
package/src/cli/ui.ts
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared CLI presentation: the toiljs brand banner, gradient text, and small helpers.
|
|
3
|
-
* Kept dependency-light (only picocolors); the gradient is hand-rolled truecolor ANSI so
|
|
4
|
-
* the logo pops without pulling in a gradient library.
|
|
5
|
-
*/
|
|
6
|
-
import fs from 'node:fs';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import { fileURLToPath } from 'node:url';
|
|
9
|
-
|
|
10
|
-
import pc from 'picocolors';
|
|
11
|
-
|
|
12
|
-
type RGB = readonly [number, number, number];
|
|
13
|
-
|
|
14
|
-
/** toiljs brand palette. */
|
|
15
|
-
const PRIMARY: RGB = [37, 99, 255];
|
|
16
|
-
const SECONDARY: RGB = [124, 58, 237];
|
|
17
|
-
const ACCENT: RGB = [34, 227, 171];
|
|
18
|
-
|
|
19
|
-
/** Logo gradient stops: blue → purple → teal. */
|
|
20
|
-
const GRADIENT: readonly RGB[] = [PRIMARY, SECONDARY, ACCENT];
|
|
21
|
-
|
|
22
|
-
/** ANSI-shadow "TOIL" wordmark. */
|
|
23
|
-
const ART: readonly string[] = [
|
|
24
|
-
'████████╗ ██████╗ ██╗ ██╗ ',
|
|
25
|
-
'╚══██╔══╝ ██╔═══██╗ ██║ ██║ ',
|
|
26
|
-
' ██║ ██║ ██║ ██║ ██║ ',
|
|
27
|
-
' ██║ ██║ ██║ ██║ ██║ ',
|
|
28
|
-
' ██║ ╚██████╔╝ ██║ ███████╗',
|
|
29
|
-
' ╚═╝ ╚═════╝ ╚═╝ ╚══════╝',
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
export const dim = pc.dim;
|
|
33
|
-
export const bold = pc.bold;
|
|
34
|
-
|
|
35
|
-
/** True when we should emit ANSI color: a TTY (or FORCE_COLOR), and not disabled via NO_COLOR. */
|
|
36
|
-
function colorEnabled(): boolean {
|
|
37
|
-
if (process.env.NO_COLOR) return false;
|
|
38
|
-
if (process.env.FORCE_COLOR) return true;
|
|
39
|
-
return process.stdout.isTTY;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function rgb(color: RGB, s: string): string {
|
|
43
|
-
return colorEnabled() ? `\x1b[38;2;${color[0]};${color[1]};${color[2]}m${s}\x1b[39m` : s;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** The primary brand accent (blue). No-ops to plain text when color is disabled. */
|
|
47
|
-
export function brand(s: string): string {
|
|
48
|
-
return rgb(PRIMARY, s);
|
|
49
|
-
}
|
|
50
|
-
export const accent = brand;
|
|
51
|
-
|
|
52
|
-
/** Success/positive accent (teal). */
|
|
53
|
-
export function success(s: string): string {
|
|
54
|
-
return rgb(ACCENT, s);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Error accent (red, kept outside the brand palette since errors should read as errors). */
|
|
58
|
-
export const danger = pc.red;
|
|
59
|
-
|
|
60
|
-
/** Warning accent (yellow, outside the brand palette so warnings read as warnings). */
|
|
61
|
-
export const warn = pc.yellow;
|
|
62
|
-
|
|
63
|
-
function lerp(a: number, b: number, t: number): number {
|
|
64
|
-
return Math.round(a + (b - a) * t);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Samples the multi-stop brand gradient at `t` in [0, 1]. */
|
|
68
|
-
function gradientAt(t: number): RGB {
|
|
69
|
-
const segments = GRADIENT.length - 1;
|
|
70
|
-
const scaled = t * segments;
|
|
71
|
-
const i = Math.min(Math.floor(scaled), segments - 1);
|
|
72
|
-
const a = GRADIENT[i];
|
|
73
|
-
const b = GRADIENT[i + 1];
|
|
74
|
-
const localT = scaled - i;
|
|
75
|
-
return [lerp(a[0], b[0], localT), lerp(a[1], b[1], localT), lerp(a[2], b[2], localT)];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Colors each character of `line` along the left→right brand gradient. */
|
|
79
|
-
function gradientLine(line: string): string {
|
|
80
|
-
const n = line.length;
|
|
81
|
-
let out = '';
|
|
82
|
-
for (let i = 0; i < n; i++) {
|
|
83
|
-
const [r, g, b] = gradientAt(n > 1 ? i / (n - 1) : 0);
|
|
84
|
-
out += `\x1b[38;2;${r};${g};${b}m${line[i]}`;
|
|
85
|
-
}
|
|
86
|
-
return out + '\x1b[39m';
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** Reads the toiljs package version (CLI lives at build/cli/, package root is two up). */
|
|
90
|
-
export function version(): string {
|
|
91
|
-
try {
|
|
92
|
-
const pkgPath = path.resolve(
|
|
93
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
|
94
|
-
'..',
|
|
95
|
-
'..',
|
|
96
|
-
'package.json',
|
|
97
|
-
);
|
|
98
|
-
const raw = fs.readFileSync(pkgPath, 'utf8');
|
|
99
|
-
const match = /"version"\s*:\s*"([^"]+)"/.exec(raw);
|
|
100
|
-
if (match && match[1]) return match[1];
|
|
101
|
-
} catch {}
|
|
102
|
-
return '0.0.0';
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// eslint-disable-next-line no-control-regex -- matching our own escape sequences is the point
|
|
106
|
-
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
107
|
-
|
|
108
|
-
/** The on-screen width of `s`, ignoring ANSI color codes. */
|
|
109
|
-
function visibleWidth(s: string): number {
|
|
110
|
-
return s.replace(ANSI_RE, '').length;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Frames already-colored lines in a rounded box sized to the widest line. `paint` colors the
|
|
115
|
-
* border (the content keeps its own colors); padding is measured on the visible text, so ANSI
|
|
116
|
-
* codes inside the lines never skew the right edge. Returns the box without a trailing newline.
|
|
117
|
-
*/
|
|
118
|
-
export function box(lines: readonly string[], paint: (s: string) => string = (s) => s): string {
|
|
119
|
-
const width = lines.reduce((w, l) => Math.max(w, visibleWidth(l)), 0);
|
|
120
|
-
const side = paint('│');
|
|
121
|
-
const body = lines.map((l) => ` ${side} ${l}${' '.repeat(width - visibleWidth(l))} ${side}`);
|
|
122
|
-
return [
|
|
123
|
-
' ' + paint(`╭${'─'.repeat(width + 4)}╮`),
|
|
124
|
-
...body,
|
|
125
|
-
' ' + paint(`╰${'─'.repeat(width + 4)}╯`),
|
|
126
|
-
].join('\n');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Banner taglines, one is picked at random per invocation. Each is a function so the accented
|
|
131
|
-
* words pick up the brand color (or stay plain when color is disabled). The theme: TOIL is the
|
|
132
|
-
* first full-stack framework for a globally distributed application delivery network.
|
|
133
|
-
*/
|
|
134
|
-
const TAGLINES: ReadonlyArray<(a: (s: string) => string) => string> = [
|
|
135
|
-
(a) => `bringing ${a('hyper scale')} to anyone`,
|
|
136
|
-
(a) => `the first full-stack ${a('application delivery network')}`,
|
|
137
|
-
(a) => `your app, ${a('globally distributed')} by default`,
|
|
138
|
-
(a) => `one build, ${a('the whole planet')}`,
|
|
139
|
-
(a) => `full stack, ${a('zero distance')} to your users`,
|
|
140
|
-
(a) => `${a('react')} up front, ${a('wasm')} at every edge`,
|
|
141
|
-
(a) => `deployed where your ${a('users')} are`,
|
|
142
|
-
(a) => `the framework with a ${a('delivery network')} built in`,
|
|
143
|
-
(a) => `no regions, just ${a('the world')}`,
|
|
144
|
-
(a) => `${a('planet-scale')} apps from a single repo`,
|
|
145
|
-
(a) => `every request served ${a('next door')}`,
|
|
146
|
-
(a) => `frontend, backend, ${a('worldwide')}`,
|
|
147
|
-
(a) => `${a('hyper scale')} without the ops team`,
|
|
148
|
-
(a) => `your backend, ${a('compiled to wasm')}, running everywhere`,
|
|
149
|
-
(a) => `the internet is your ${a('runtime')}`,
|
|
150
|
-
(a) => `the speed of light is the ${a('only bottleneck')}`,
|
|
151
|
-
(a) => `static speed, ${a('dynamic everything')}`,
|
|
152
|
-
(a) => `scale to ${a('millions')} before lunch`,
|
|
153
|
-
(a) => `latency is a choice, choose ${a('zero')}`,
|
|
154
|
-
(a) => `build ${a('better')}, ship ${a('faster')}`,
|
|
155
|
-
];
|
|
156
|
-
|
|
157
|
-
/** A random brand tagline, accent words colored. */
|
|
158
|
-
export function tagline(): string {
|
|
159
|
-
return TAGLINES[Math.floor(Math.random() * TAGLINES.length)](brand);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** Prints the brand banner: gradient logo + random tagline + version. */
|
|
163
|
-
export function banner(): void {
|
|
164
|
-
const lines = colorEnabled() ? ART.map(gradientLine) : ART.slice();
|
|
165
|
-
const ver = `${dim(' v')}${brand(version())}`;
|
|
166
|
-
process.stdout.write('\n' + lines.join('\n') + '\n\n ' + tagline() + ' ' + ver + '\n\n');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
package/src/cli/update.ts
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `toiljs update`, a friendly wrapper over `npm-check-updates`: checks the registry for newer
|
|
3
|
-
* dependency versions, shows them grouped by semver bump (major / minor / patch) with colors, lets
|
|
4
|
-
* you pick which to apply (or `-y` for all), bumps package.json, and runs the project's package
|
|
5
|
-
* manager install. ncu is invoked via `npx --yes` so it isn't a permanent dependency of toiljs.
|
|
6
|
-
*/
|
|
7
|
-
import fs from 'node:fs';
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
|
|
10
|
-
import { cancel, intro, isCancel, multiselect, note, outro, spinner } from '@clack/prompts';
|
|
11
|
-
|
|
12
|
-
import { MIGRATIONS_README } from './create.js';
|
|
13
|
-
import { capture, run } from './proc.js';
|
|
14
|
-
import { buildRows, type Bump, parseNcuJson, type UpdateRow, withheldUpgrades } from './updates.js';
|
|
15
|
-
import { accent, danger, dim, success, warn } from './ui.js';
|
|
16
|
-
|
|
17
|
-
export interface UpdateOptions {
|
|
18
|
-
readonly root?: string;
|
|
19
|
-
readonly cwd: string;
|
|
20
|
-
/** Apply all available updates without the interactive picker. */
|
|
21
|
-
readonly yes?: boolean;
|
|
22
|
-
/** ncu `--target` (latest | minor | patch | newest | greatest). Default latest. */
|
|
23
|
-
readonly target?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface PackageManager {
|
|
27
|
-
readonly name: string;
|
|
28
|
-
readonly ncuName: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Detects the package manager from the project's lockfile (defaults to npm). */
|
|
32
|
-
export function detectPackageManager(root: string): PackageManager {
|
|
33
|
-
if (fs.existsSync(path.join(root, 'pnpm-lock.yaml'))) return { name: 'pnpm', ncuName: 'pnpm' };
|
|
34
|
-
if (fs.existsSync(path.join(root, 'yarn.lock'))) return { name: 'yarn', ncuName: 'yarn' };
|
|
35
|
-
if (fs.existsSync(path.join(root, 'bun.lockb'))) return { name: 'bun', ncuName: 'bun' };
|
|
36
|
-
return { name: 'npm', ncuName: 'npm' };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Reads the merged dependency ranges (deps + devDeps) from a package.json. */
|
|
40
|
-
function readDependencies(pkgPath: string): Record<string, string> {
|
|
41
|
-
const parsed: unknown = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
42
|
-
if (typeof parsed !== 'object' || parsed === null) return {};
|
|
43
|
-
const pkg = parsed as Record<string, unknown>;
|
|
44
|
-
const merge = (v: unknown): Record<string, string> => {
|
|
45
|
-
if (typeof v !== 'object' || v === null) return {};
|
|
46
|
-
const out: Record<string, string> = {};
|
|
47
|
-
for (const [k, val] of Object.entries(v)) if (typeof val === 'string') out[k] = val;
|
|
48
|
-
return out;
|
|
49
|
-
};
|
|
50
|
-
return { ...merge(pkg.dependencies), ...merge(pkg.devDependencies) };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* The server dir(s) of a project: the directories of the toilconfig.json `entries`, conventionally
|
|
55
|
-
* `server/`. Falls back to `<root>/server` when there is no toilconfig (or it has no string entries),
|
|
56
|
-
* matching how `doctor` locates the server tree.
|
|
57
|
-
*/
|
|
58
|
-
function serverDirs(root: string): string[] {
|
|
59
|
-
const dirs = new Set<string>();
|
|
60
|
-
try {
|
|
61
|
-
const parsed: unknown = JSON.parse(
|
|
62
|
-
fs.readFileSync(path.join(root, 'toilconfig.json'), 'utf8'),
|
|
63
|
-
);
|
|
64
|
-
const entries =
|
|
65
|
-
typeof parsed === 'object' &&
|
|
66
|
-
parsed !== null &&
|
|
67
|
-
Array.isArray((parsed as { entries?: unknown }).entries)
|
|
68
|
-
? (parsed as { entries: unknown[] }).entries.filter(
|
|
69
|
-
(e): e is string => typeof e === 'string',
|
|
70
|
-
)
|
|
71
|
-
: [];
|
|
72
|
-
for (const e of entries) dirs.add(path.dirname(path.resolve(root, e)));
|
|
73
|
-
} catch {
|
|
74
|
-
// no/unreadable toilconfig.json: fall back to the conventional server/ below
|
|
75
|
-
}
|
|
76
|
-
if (dirs.size === 0) dirs.add(path.join(root, 'server'));
|
|
77
|
-
return [...dirs];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Ensures the ToilDB `migrations/` folder exists under each server dir that exists. `@migrate`
|
|
82
|
-
* functions MUST live in a `*.migration.ts` file under `migrations/` (folder + extension is a
|
|
83
|
-
* compile error otherwise) and the build auto-discovers them, so an up-to-date project keeps the
|
|
84
|
-
* folder ready. Idempotent: only writes the folder + README where the server dir exists and the
|
|
85
|
-
* folder is absent. Returns the project-relative paths created (for the note), empty if nothing.
|
|
86
|
-
*/
|
|
87
|
-
function ensureMigrationsDirs(root: string): string[] {
|
|
88
|
-
const created: string[] = [];
|
|
89
|
-
for (const dir of serverDirs(root)) {
|
|
90
|
-
if (!fs.existsSync(dir)) continue;
|
|
91
|
-
const migrations = path.join(dir, 'migrations');
|
|
92
|
-
if (fs.existsSync(migrations)) continue;
|
|
93
|
-
fs.mkdirSync(migrations, { recursive: true });
|
|
94
|
-
fs.writeFileSync(path.join(migrations, 'README.md'), MIGRATIONS_README);
|
|
95
|
-
created.push(path.relative(root, migrations) || 'migrations');
|
|
96
|
-
}
|
|
97
|
-
return created;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function bumpColor(bump: Bump, text: string): string {
|
|
101
|
-
if (bump === 'major') return danger(text);
|
|
102
|
-
if (bump === 'minor') return warn(text);
|
|
103
|
-
if (bump === 'patch') return success(text);
|
|
104
|
-
return dim(text);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Renders a row as `name from -> to`, the version part colored by bump. */
|
|
108
|
-
function rowLine(row: UpdateRow): string {
|
|
109
|
-
return `${row.name} ${dim(row.from)} ${dim('->')} ${bumpColor(row.bump, row.to)}`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** Tells the user which upgrades were held back, and why they are not simply missing. */
|
|
113
|
-
function noteWithheld(names: readonly string[]): void {
|
|
114
|
-
note(
|
|
115
|
-
names.map((n) => `${dim('-')} ${n}`).join('\n') +
|
|
116
|
-
'\n\n' +
|
|
117
|
-
dim(
|
|
118
|
-
'Held back: this major is not supported by toiljs yet. TypeScript 7 is the native\n' +
|
|
119
|
-
'port and ships no JavaScript compiler API, so route metadata would stop being\n' +
|
|
120
|
-
'baked into the built HTML and typescript-eslint would not load.',
|
|
121
|
-
),
|
|
122
|
-
warn('Not upgraded'),
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const TARGETS = new Set(['latest', 'minor', 'patch', 'newest', 'greatest']);
|
|
127
|
-
|
|
128
|
-
export async function runUpdate(opts: UpdateOptions): Promise<void> {
|
|
129
|
-
const root = path.resolve(opts.root ?? opts.cwd);
|
|
130
|
-
const pkgPath = path.join(root, 'package.json');
|
|
131
|
-
if (!fs.existsSync(pkgPath)) {
|
|
132
|
-
throw new Error('No package.json here. Run from your project root or pass --root <dir>.');
|
|
133
|
-
}
|
|
134
|
-
const currentDeps = readDependencies(pkgPath);
|
|
135
|
-
const pm = detectPackageManager(root);
|
|
136
|
-
const target = opts.target && TARGETS.has(opts.target) ? opts.target : 'latest';
|
|
137
|
-
|
|
138
|
-
const ncuArgs = (extra: string[]): string[] => [
|
|
139
|
-
'--yes',
|
|
140
|
-
'npm-check-updates',
|
|
141
|
-
'--packageManager',
|
|
142
|
-
pm.ncuName,
|
|
143
|
-
'--target',
|
|
144
|
-
target,
|
|
145
|
-
...extra,
|
|
146
|
-
];
|
|
147
|
-
|
|
148
|
-
intro(accent('toiljs update'));
|
|
149
|
-
|
|
150
|
-
// Bring the project's structure up to date: ensure the ToilDB migrations folder exists (it is
|
|
151
|
-
// newer than some projects, and the compiler requires @migrate functions to live under it).
|
|
152
|
-
const createdMigrations = ensureMigrationsDirs(root);
|
|
153
|
-
if (createdMigrations.length > 0) {
|
|
154
|
-
note(
|
|
155
|
-
createdMigrations.map((p) => `${dim('+')} ${p}/`).join('\n'),
|
|
156
|
-
'Created ToilDB migrations folder',
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const s = spinner();
|
|
161
|
-
s.start('Checking the registry for updates');
|
|
162
|
-
const res = await capture('npx', ncuArgs(['--jsonUpgraded']), root);
|
|
163
|
-
if (res.code !== 0 && res.stdout.indexOf('{') === -1) {
|
|
164
|
-
s.stop('Could not check for updates');
|
|
165
|
-
note(dim(res.stderr.trim() || 'npm-check-updates failed.'), 'Error');
|
|
166
|
-
process.exitCode = 1;
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
// Hold back upgrades into a major toiljs cannot run on (typescript 7), so neither the picker nor
|
|
170
|
-
// a `-y` run can install one. `--reject` keeps ncu from applying them during `-u` below.
|
|
171
|
-
const upgraded = parseNcuJson(res.stdout);
|
|
172
|
-
const withheld = withheldUpgrades(upgraded);
|
|
173
|
-
for (const name of withheld) delete upgraded[name];
|
|
174
|
-
|
|
175
|
-
const rows = buildRows(upgraded, currentDeps);
|
|
176
|
-
if (rows.length === 0) {
|
|
177
|
-
s.stop('Everything is up to date');
|
|
178
|
-
if (withheld.length > 0) noteWithheld(withheld);
|
|
179
|
-
outro(success('Nothing to update.'));
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
s.stop(`${String(rows.length)} update${rows.length === 1 ? '' : 's'} available`);
|
|
183
|
-
if (withheld.length > 0) noteWithheld(withheld);
|
|
184
|
-
|
|
185
|
-
const counts = { major: 0, minor: 0, patch: 0, other: 0 };
|
|
186
|
-
for (const r of rows) counts[r.bump]++;
|
|
187
|
-
note(
|
|
188
|
-
rows.map(rowLine).join('\n'),
|
|
189
|
-
`${danger(`${String(counts.major)} major`)} ${warn(`${String(counts.minor)} minor`)} ${success(`${String(counts.patch)} patch`)}`,
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
let selected: string[];
|
|
193
|
-
if (opts.yes) {
|
|
194
|
-
selected = rows.map((r) => r.name);
|
|
195
|
-
} else {
|
|
196
|
-
const answer = await multiselect<string>({
|
|
197
|
-
message: 'Select packages to update (space to toggle, enter to confirm)',
|
|
198
|
-
options: rows.map((r) => ({
|
|
199
|
-
value: r.name,
|
|
200
|
-
label: r.name,
|
|
201
|
-
hint: `${r.from} -> ${r.to}`,
|
|
202
|
-
})),
|
|
203
|
-
initialValues: rows.map((r) => r.name),
|
|
204
|
-
required: false,
|
|
205
|
-
});
|
|
206
|
-
if (isCancel(answer)) {
|
|
207
|
-
cancel('Update cancelled.');
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
selected = answer;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (selected.length === 0) {
|
|
214
|
-
outro(dim('No packages selected.'));
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
s.start('Updating package.json');
|
|
219
|
-
const applyAll = selected.length === rows.length;
|
|
220
|
-
const reject = withheld.length > 0 ? ['--reject', withheld.join(' ')] : [];
|
|
221
|
-
await run(
|
|
222
|
-
'npx',
|
|
223
|
-
ncuArgs(applyAll ? ['-u', ...reject] : ['-u', '--filter', selected.join(' '), ...reject]),
|
|
224
|
-
root,
|
|
225
|
-
);
|
|
226
|
-
s.stop('package.json updated');
|
|
227
|
-
|
|
228
|
-
// Run the install with VISIBLE output (so a failure is diagnosable — npm
|
|
229
|
-
// prints the real error) and handle a non-zero exit gracefully, instead of
|
|
230
|
-
// leaving the spinner spinning forever on a failed install.
|
|
231
|
-
note(dim(`Running ${pm.name} install…`), 'Install');
|
|
232
|
-
try {
|
|
233
|
-
await run(pm.name, ['install'], root, { stdio: 'inherit' });
|
|
234
|
-
} catch {
|
|
235
|
-
outro(
|
|
236
|
-
danger(
|
|
237
|
-
`${pm.name} install failed — package.json was updated to the new versions, but the ` +
|
|
238
|
-
`install did not finish. Fix the error printed above, then run \`${pm.name} install\`.`,
|
|
239
|
-
),
|
|
240
|
-
);
|
|
241
|
-
process.exitCode = 1;
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
outro(
|
|
246
|
-
success(`Updated ${String(selected.length)} package${selected.length === 1 ? '' : 's'}.`),
|
|
247
|
-
);
|
|
248
|
-
}
|
package/src/cli/updates.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure helpers for `toiljs update`: parse `npm-check-updates --jsonUpgraded` output and classify the
|
|
3
|
-
* semver bump of each upgrade. IO-free so it can be unit-tested; the spawn/UI live in `update.ts`.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/** The kind of version jump an upgrade represents. */
|
|
7
|
-
export type Bump = 'major' | 'minor' | 'patch' | 'other';
|
|
8
|
-
|
|
9
|
-
/** One available upgrade: package, current range, target range, and bump kind. */
|
|
10
|
-
export interface UpdateRow {
|
|
11
|
-
readonly name: string;
|
|
12
|
-
readonly from: string;
|
|
13
|
-
readonly to: string;
|
|
14
|
-
readonly bump: Bump;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Extracts a version's leading `x.y.z` (ignoring `^`, `~`, `>=`, etc.); missing parts become 0. */
|
|
18
|
-
function parseVersion(v: string): [number, number, number] {
|
|
19
|
-
const m = /(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(v);
|
|
20
|
-
if (!m) return [0, 0, 0];
|
|
21
|
-
return [Number(m[1]), Number(m[2] ?? 0), Number(m[3] ?? 0)];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Classifies the bump from `from` to `to` (both may be ranges like `^1.2.3`). */
|
|
25
|
-
export function classifyBump(from: string, to: string): Bump {
|
|
26
|
-
const [fa, fb, fc] = parseVersion(from);
|
|
27
|
-
const [ta, tb, tc] = parseVersion(to);
|
|
28
|
-
if (ta !== fa) return 'major';
|
|
29
|
-
if (tb !== fb) return 'minor';
|
|
30
|
-
if (tc !== fc) return 'patch';
|
|
31
|
-
return 'other';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Parses the JSON object `npm-check-updates --jsonUpgraded` prints (a `{ name: range }` map). Tolerant
|
|
36
|
-
* of leading/trailing noise (npx banners) by slicing to the outermost braces. Returns `{}` on failure.
|
|
37
|
-
*/
|
|
38
|
-
export function parseNcuJson(stdout: string): Record<string, string> {
|
|
39
|
-
const start = stdout.indexOf('{');
|
|
40
|
-
const end = stdout.lastIndexOf('}');
|
|
41
|
-
if (start === -1 || end <= start) return {};
|
|
42
|
-
try {
|
|
43
|
-
const parsed: unknown = JSON.parse(stdout.slice(start, end + 1));
|
|
44
|
-
if (typeof parsed !== 'object' || parsed === null) return {};
|
|
45
|
-
const out: Record<string, string> = {};
|
|
46
|
-
for (const [k, v] of Object.entries(parsed)) if (typeof v === 'string') out[k] = v;
|
|
47
|
-
return out;
|
|
48
|
-
} catch {
|
|
49
|
-
return {};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Majors `toiljs update` refuses to upgrade into, by package. TypeScript 7 is the native (Go) port:
|
|
55
|
-
* it ships `tsc` but no JavaScript compiler API, which toiljs's route-metadata extractor, the eslint
|
|
56
|
-
* preset, and typedoc all load. Upgrading into it silently stops SEO metadata being baked into the
|
|
57
|
-
* built HTML and hard-crashes typescript-eslint, so the upgrade is withheld until the ecosystem
|
|
58
|
-
* catches up. Bumps *within* the supported major are still offered.
|
|
59
|
-
*/
|
|
60
|
-
export const UNSUPPORTED_MAJOR: Readonly<Record<string, number>> = { typescript: 7 };
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* The names ncu wants to upgrade into a major toiljs does not support (see {@link UNSUPPORTED_MAJOR}).
|
|
64
|
-
* These are dropped from the picker and passed to ncu's `--reject`, so `-u` cannot apply them either.
|
|
65
|
-
*/
|
|
66
|
-
export function withheldUpgrades(upgraded: Record<string, string>): string[] {
|
|
67
|
-
return Object.entries(upgraded)
|
|
68
|
-
.filter(([name, to]) => {
|
|
69
|
-
const ceiling = UNSUPPORTED_MAJOR[name];
|
|
70
|
-
return ceiling !== undefined && parseVersion(to)[0] >= ceiling;
|
|
71
|
-
})
|
|
72
|
-
.map(([name]) => name)
|
|
73
|
-
.sort();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const SEVERITY: Record<Bump, number> = { major: 0, minor: 1, patch: 2, other: 3 };
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Builds the upgrade rows from the ncu map and the project's current dependency ranges, sorted by
|
|
80
|
-
* bump severity (major first) then name.
|
|
81
|
-
*/
|
|
82
|
-
export function buildRows(
|
|
83
|
-
upgraded: Record<string, string>,
|
|
84
|
-
currentDeps: Record<string, string>,
|
|
85
|
-
): UpdateRow[] {
|
|
86
|
-
return Object.entries(upgraded)
|
|
87
|
-
.map(([name, to]) => {
|
|
88
|
-
const from = currentDeps[name] ?? '?';
|
|
89
|
-
return { name, from, to, bump: classifyBump(from, to) };
|
|
90
|
-
})
|
|
91
|
-
.sort((a, b) => SEVERITY[a.bump] - SEVERITY[b.bump] || a.name.localeCompare(b.name));
|
|
92
|
-
}
|
package/src/cli/validate.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure input validation for `toiljs create`, kept dependency-light (only node:path) so it can be
|
|
3
|
-
* unit-tested without pulling in the rest of the CLI.
|
|
4
|
-
*/
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
|
|
7
|
-
/** Package managers the scaffolder may invoke. Allowlisted so a hostile `--pm` can't inject a shell command. */
|
|
8
|
-
export const PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'];
|
|
9
|
-
|
|
10
|
-
/** Validates a project name's characters. Returns `true`, or an error message. */
|
|
11
|
-
export function isValidName(name: string): true | string {
|
|
12
|
-
if (!name.trim()) return 'Please enter a project name.';
|
|
13
|
-
if (!/^[a-z0-9._@/-]+$/i.test(name)) return 'Use letters, numbers, dashes, dots or slashes.';
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Resolves `name` to an absolute directory under `cwd`, refusing to escape it (a name like
|
|
19
|
-
* `../x` or an absolute path). Returns the resolved dir, or `null` if it would escape `cwd`.
|
|
20
|
-
*/
|
|
21
|
-
export function resolveProjectDir(cwd: string, name: string): string | null {
|
|
22
|
-
const target = path.resolve(cwd, name);
|
|
23
|
-
const rel = path.relative(cwd, target);
|
|
24
|
-
if (rel.startsWith('..') || path.isAbsolute(rel)) return null;
|
|
25
|
-
return target;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Whether `pm` is a supported package manager (guards shell use of `--pm`). */
|
|
29
|
-
export function isPackageManager(pm: string): boolean {
|
|
30
|
-
return PACKAGE_MANAGERS.includes(pm);
|
|
31
|
-
}
|
package/src/cli/version-check.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure helpers for the automatic toiljs update check that runs on every CLI invocation
|
|
3
|
-
* (and therefore on `npm run dev` / `npm run build`, which call the CLI). IO-free so it
|
|
4
|
-
* can be unit-tested; the registry fetch, cache file, and printing live in `notify.ts`.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/** How long a registry answer is trusted before we ask npm again. */
|
|
8
|
-
export const CHECK_TTL_MS = 60 * 60 * 1000;
|
|
9
|
-
|
|
10
|
-
/** What we persist between runs: the latest known version and when we asked. */
|
|
11
|
-
export interface CheckCache {
|
|
12
|
-
readonly latest: string | null;
|
|
13
|
-
readonly checkedAt: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Parses the cache file contents; returns null when malformed (forces a fresh check). */
|
|
17
|
-
export function parseCheckCache(raw: string): CheckCache | null {
|
|
18
|
-
try {
|
|
19
|
-
const parsed: unknown = JSON.parse(raw);
|
|
20
|
-
if (typeof parsed !== 'object' || parsed === null) return null;
|
|
21
|
-
const o = parsed as Record<string, unknown>;
|
|
22
|
-
const latest = typeof o.latest === 'string' ? o.latest : null;
|
|
23
|
-
const checkedAt = typeof o.checkedAt === 'number' ? o.checkedAt : NaN;
|
|
24
|
-
if (!Number.isFinite(checkedAt)) return null;
|
|
25
|
-
return { latest, checkedAt };
|
|
26
|
-
} catch {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** True when the cached answer is still trustworthy (also stale if the clock went backwards). */
|
|
32
|
-
export function isCacheFresh(
|
|
33
|
-
cache: CheckCache,
|
|
34
|
-
now: number,
|
|
35
|
-
ttlMs: number = CHECK_TTL_MS,
|
|
36
|
-
): boolean {
|
|
37
|
-
return cache.checkedAt <= now && now - cache.checkedAt < ttlMs;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface Parsed {
|
|
41
|
-
readonly nums: readonly [number, number, number];
|
|
42
|
-
readonly pre: string | null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function parseSemver(v: string): Parsed {
|
|
46
|
-
const m = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v.trim());
|
|
47
|
-
if (!m) return { nums: [0, 0, 0], pre: null };
|
|
48
|
-
return { nums: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ?? null };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Compares two semver strings: negative when `a < b`, 0 when equal, positive when `a > b`.
|
|
53
|
-
* A prerelease sorts below its release (`0.1.0-beta.1 < 0.1.0`); two prereleases compare
|
|
54
|
-
* lexicographically, which is enough for an update nudge.
|
|
55
|
-
*/
|
|
56
|
-
export function compareSemver(a: string, b: string): number {
|
|
57
|
-
const pa = parseSemver(a);
|
|
58
|
-
const pb = parseSemver(b);
|
|
59
|
-
for (let i = 0; i < 3; i++) {
|
|
60
|
-
if (pa.nums[i] !== pb.nums[i]) return pa.nums[i] < pb.nums[i] ? -1 : 1;
|
|
61
|
-
}
|
|
62
|
-
if (pa.pre === pb.pre) return 0;
|
|
63
|
-
if (pa.pre === null) return 1;
|
|
64
|
-
if (pb.pre === null) return -1;
|
|
65
|
-
return pa.pre < pb.pre ? -1 : 1;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** True when `installed` is behind `latest`. */
|
|
69
|
-
export function isOutdated(installed: string, latest: string): boolean {
|
|
70
|
-
return compareSemver(installed, latest) < 0;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** The command that updates toiljs for the project's package manager, or globally. */
|
|
74
|
-
export function installCommand(pm: string, scope: 'project' | 'global'): string {
|
|
75
|
-
if (scope === 'global') {
|
|
76
|
-
if (pm === 'pnpm') return 'pnpm add -g toiljs@latest';
|
|
77
|
-
if (pm === 'yarn') return 'yarn global add toiljs@latest';
|
|
78
|
-
if (pm === 'bun') return 'bun add -g toiljs@latest';
|
|
79
|
-
return 'npm install -g toiljs@latest';
|
|
80
|
-
}
|
|
81
|
-
if (pm === 'pnpm') return 'pnpm add toiljs@latest';
|
|
82
|
-
if (pm === 'yarn') return 'yarn add toiljs@latest';
|
|
83
|
-
if (pm === 'bun') return 'bun add toiljs@latest';
|
|
84
|
-
return 'npm install toiljs@latest';
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** One out-of-date install we want to nag about. */
|
|
88
|
-
export interface OutdatedRow {
|
|
89
|
-
/** Where the stale copy lives: the project's node_modules or the global CLI. */
|
|
90
|
-
readonly scope: 'project' | 'global';
|
|
91
|
-
readonly installed: string;
|
|
92
|
-
readonly command: string;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Decides what (if anything) to warn about. `projectVersion` is the toiljs resolved in the
|
|
97
|
-
* project's node_modules (null when not installed there), `cliVersion` is the copy of the CLI
|
|
98
|
-
* actually running, and `cliIsLocal` tells us whether those are the same install.
|
|
99
|
-
*/
|
|
100
|
-
export function findOutdated(
|
|
101
|
-
latest: string,
|
|
102
|
-
projectVersion: string | null,
|
|
103
|
-
cliVersion: string,
|
|
104
|
-
cliIsLocal: boolean,
|
|
105
|
-
pm: string,
|
|
106
|
-
): OutdatedRow[] {
|
|
107
|
-
const rows: OutdatedRow[] = [];
|
|
108
|
-
if (projectVersion !== null && isOutdated(projectVersion, latest)) {
|
|
109
|
-
rows.push({
|
|
110
|
-
scope: 'project',
|
|
111
|
-
installed: projectVersion,
|
|
112
|
-
command: installCommand(pm, 'project'),
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
// The running CLI only gets its own row when it is not the project install we already
|
|
116
|
-
// reported (global installs, or `npx toiljs` outside a project).
|
|
117
|
-
if (!cliIsLocal && isOutdated(cliVersion, latest)) {
|
|
118
|
-
rows.push({
|
|
119
|
-
scope: 'global',
|
|
120
|
-
installed: cliVersion,
|
|
121
|
-
command: installCommand('npm', 'global'),
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
return rows;
|
|
125
|
-
}
|