toiljs 0.0.48 → 0.0.49
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 +9 -0
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +15 -6
- package/build/client/.tsbuildinfo +1 -1
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/docs.js +2 -2
- package/build/compiler/email-preview.js +4 -2
- package/build/compiler/index.js +13 -1
- package/build/compiler/vite.js +14 -1
- package/examples/basic/client/layout.tsx +1 -1
- package/examples/basic/client/routes/pq.tsx +2 -2
- package/examples/basic/client/routes/rpc.tsx +1 -1
- package/examples/basic/client/routes/search.tsx +1 -1
- package/package.json +1 -1
- package/src/cli/proc.ts +9 -3
- package/src/cli/ui.ts +0 -1
- package/src/cli/update.ts +16 -3
- package/src/client/head/head.ts +1 -1
- package/src/client/head/metadata.ts +1 -1
- package/src/compiler/docs.ts +2 -2
- package/src/compiler/email-preview.ts +4 -2
- package/src/compiler/index.ts +20 -2
- package/src/compiler/vite.ts +21 -1
package/src/cli/proc.ts
CHANGED
|
@@ -6,12 +6,18 @@ import { spawn } from 'node:child_process';
|
|
|
6
6
|
* `shell: true` is deprecated (DEP0190), so the whole command is passed as one string there
|
|
7
7
|
* (args are fixed/allowlisted, never raw user input). POSIX spawns directly.
|
|
8
8
|
*/
|
|
9
|
-
export function run(
|
|
9
|
+
export function run(
|
|
10
|
+
cmd: string,
|
|
11
|
+
args: string[],
|
|
12
|
+
cwd: string,
|
|
13
|
+
opts: { stdio?: 'ignore' | 'inherit' } = {},
|
|
14
|
+
): Promise<void> {
|
|
10
15
|
return new Promise((resolve, reject) => {
|
|
11
16
|
const onWindows = process.platform === 'win32';
|
|
17
|
+
const stdio = opts.stdio ?? 'ignore';
|
|
12
18
|
const child = onWindows
|
|
13
|
-
? spawn([cmd, ...args].join(' '), { cwd, stdio
|
|
14
|
-
: spawn(cmd, args, { cwd, stdio
|
|
19
|
+
? spawn([cmd, ...args].join(' '), { cwd, stdio, shell: true })
|
|
20
|
+
: spawn(cmd, args, { cwd, stdio });
|
|
15
21
|
child.on('error', reject);
|
|
16
22
|
child.on('close', (code) =>
|
|
17
23
|
code === 0 ? resolve() : reject(new Error(`${cmd} exited with code ${String(code)}`)),
|
package/src/cli/ui.ts
CHANGED
|
@@ -134,7 +134,6 @@ export function box(lines: readonly string[], paint: (s: string) => string = (s)
|
|
|
134
134
|
* first full-stack framework for a globally distributed application delivery network.
|
|
135
135
|
*/
|
|
136
136
|
const TAGLINES: ReadonlyArray<(a: (s: string) => string) => string> = [
|
|
137
|
-
(a) => `the most performant ${a('react')} framework`,
|
|
138
137
|
(a) => `bringing ${a('hyper scale')} to anyone`,
|
|
139
138
|
(a) => `the first full-stack ${a('application delivery network')}`,
|
|
140
139
|
(a) => `your app, ${a('globally distributed')} by default`,
|
package/src/cli/update.ts
CHANGED
|
@@ -140,9 +140,22 @@ export async function runUpdate(opts: UpdateOptions): Promise<void> {
|
|
|
140
140
|
await run('npx', ncuArgs(applyAll ? ['-u'] : ['-u', '--filter', selected.join(' ')]), root);
|
|
141
141
|
s.stop('package.json updated');
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
// Run the install with VISIBLE output (so a failure is diagnosable — npm
|
|
144
|
+
// prints the real error) and handle a non-zero exit gracefully, instead of
|
|
145
|
+
// leaving the spinner spinning forever on a failed install.
|
|
146
|
+
note(dim(`Running ${pm.name} install…`), 'Install');
|
|
147
|
+
try {
|
|
148
|
+
await run(pm.name, ['install'], root, { stdio: 'inherit' });
|
|
149
|
+
} catch {
|
|
150
|
+
outro(
|
|
151
|
+
danger(
|
|
152
|
+
`${pm.name} install failed — package.json was updated to the new versions, but the ` +
|
|
153
|
+
`install did not finish. Fix the error printed above, then run \`${pm.name} install\`.`,
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
process.exitCode = 1;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
146
159
|
|
|
147
160
|
outro(
|
|
148
161
|
success(`Updated ${String(selected.length)} package${selected.length === 1 ? '' : 's'}.`),
|
package/src/client/head/head.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface LinkTag {
|
|
|
25
25
|
export interface HeadSpec {
|
|
26
26
|
/** Document title. */
|
|
27
27
|
readonly title?: string;
|
|
28
|
-
/** Template applied to a child's title, `%s` = the title (e.g. `'%s
|
|
28
|
+
/** Template applied to a child's title, `%s` = the title (e.g. `'%s, toiljs'`). */
|
|
29
29
|
readonly titleTemplate?: string;
|
|
30
30
|
readonly meta?: readonly MetaTag[];
|
|
31
31
|
readonly link?: readonly LinkTag[];
|
|
@@ -21,7 +21,7 @@ export interface OpenGraph {
|
|
|
21
21
|
export interface Metadata {
|
|
22
22
|
/** Document title. */
|
|
23
23
|
readonly title?: string;
|
|
24
|
-
/** Template applied to the title (`%s` = the title), e.g. `'%s
|
|
24
|
+
/** Template applied to the title (`%s` = the title), e.g. `'%s, toiljs'`. */
|
|
25
25
|
readonly titleTemplate?: string;
|
|
26
26
|
/** `<meta name="description">`. */
|
|
27
27
|
readonly description?: string;
|
package/src/compiler/docs.ts
CHANGED
|
@@ -8,7 +8,7 @@ import fs from 'node:fs';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
|
|
10
10
|
/** Shared body for the per-tool pointer files. */
|
|
11
|
-
const POINTER_BODY = `# toiljs
|
|
11
|
+
const POINTER_BODY = `# toiljs, AI assistant guide
|
|
12
12
|
|
|
13
13
|
This is a **toiljs** project, a full-stack React framework (React + Vite client, file-based
|
|
14
14
|
routing, and a toilscript→WebAssembly server).
|
|
@@ -183,7 +183,7 @@ export const TOIL_DOCS: Record<string, string> = {
|
|
|
183
183
|
'',
|
|
184
184
|
' Toil.useHead({',
|
|
185
185
|
' title: "Blog",',
|
|
186
|
-
' titleTemplate: "%s
|
|
186
|
+
' titleTemplate: "%s, MyApp",',
|
|
187
187
|
' meta: [{ name: "description", content: "..." }],',
|
|
188
188
|
' });',
|
|
189
189
|
]),
|
|
@@ -120,7 +120,7 @@ export function previewShellHtml(): string {
|
|
|
120
120
|
<head>
|
|
121
121
|
<meta charset="utf-8" />
|
|
122
122
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
123
|
-
<title>Email preview
|
|
123
|
+
<title>Email preview, toiljs</title>
|
|
124
124
|
<style>
|
|
125
125
|
/* Matches the toiljs demo brand (examples/basic/client/styles/main.css). */
|
|
126
126
|
:root {
|
|
@@ -213,7 +213,9 @@ export function previewShellHtml(): string {
|
|
|
213
213
|
subjectEl.textContent = fill(rendered.subject);
|
|
214
214
|
if (format === 'html') {
|
|
215
215
|
frame.hidden = false; textEl.hidden = true;
|
|
216
|
-
|
|
216
|
+
// Wrap the email FRAGMENT in a minimal dark document so the iframe doesn't
|
|
217
|
+
// show the browser-default white body/margin around and below the email.
|
|
218
|
+
frame.srcdoc = '<!doctype html><meta charset="utf-8"><style>html,body{margin:0;padding:0;background:#080d11}</style>' + fill(rendered.html);
|
|
217
219
|
} else {
|
|
218
220
|
frame.hidden = true; textEl.hidden = false;
|
|
219
221
|
textEl.textContent = fill(rendered.text);
|
package/src/compiler/index.ts
CHANGED
|
@@ -220,6 +220,14 @@ function watchServer(cfg: ResolvedToilConfig, watcher: ViteDevServer['watcher'])
|
|
|
220
220
|
dirs.some((dir) => file === dir || file.startsWith(dir + path.sep));
|
|
221
221
|
const isEmailSource = (file: string): boolean =>
|
|
222
222
|
/\.(tsx|jsx)$/.test(file) && (file === emailsDir || file.startsWith(emailsDir + path.sep));
|
|
223
|
+
// A transient watch error must NOT crash the dev server: an unhandled 'error'
|
|
224
|
+
// on the chokidar watcher takes down the whole process. Windows throws EBUSY /
|
|
225
|
+
// EPERM when a file is momentarily locked (an editor save, a formatter, our own
|
|
226
|
+
// rebuild, a just-written file). Swallow it — the next change still fires.
|
|
227
|
+
watcher.on('error', (err: unknown) => {
|
|
228
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
229
|
+
process.stdout.write(pc.yellow(' ! ') + pc.dim(`file watcher: ${msg}`) + '\n');
|
|
230
|
+
});
|
|
223
231
|
watcher.add([...dirs, emailsDir]);
|
|
224
232
|
watcher.on('all', (_event, file) => {
|
|
225
233
|
if (!isServerSource(file) && !isEmailSource(file)) return;
|
|
@@ -237,12 +245,22 @@ function watchServer(cfg: ResolvedToilConfig, watcher: ViteDevServer['watcher'])
|
|
|
237
245
|
* close the servers, and force-exit after a short grace period no matter what.
|
|
238
246
|
*/
|
|
239
247
|
function installDevShutdown(close: () => Promise<void> | void): void {
|
|
248
|
+
// Final, SYNCHRONOUS cursor + style restore — `exit` runs no matter how we go
|
|
249
|
+
// (signal, throw, normal), so the terminal can't be left without a cursor.
|
|
250
|
+
const restoreTerminal = (): void => {
|
|
251
|
+
try {
|
|
252
|
+
process.stdout.write('\x1b[0m\x1b[?25h');
|
|
253
|
+
} catch {
|
|
254
|
+
/* stream already closed */
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
process.on('exit', restoreTerminal);
|
|
258
|
+
|
|
240
259
|
let closing = false;
|
|
241
260
|
const shutdown = (): void => {
|
|
242
261
|
if (closing) return;
|
|
243
262
|
closing = true;
|
|
244
|
-
|
|
245
|
-
process.stdout.write('\x1b[?25h');
|
|
263
|
+
restoreTerminal();
|
|
246
264
|
process.stdout.write(pc.dim('\n shutting down dev server…') + '\n');
|
|
247
265
|
// Force-exit even if a server hangs on close (the orphan-prevention).
|
|
248
266
|
const hard = setTimeout(() => process.exit(0), 1500);
|
package/src/compiler/vite.ts
CHANGED
|
@@ -3,10 +3,11 @@ import { createRequire } from 'node:module';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
5
|
|
|
6
|
+
import pc from 'picocolors';
|
|
6
7
|
import react from '@vitejs/plugin-react';
|
|
7
8
|
import { imagetools } from 'vite-imagetools';
|
|
8
9
|
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
|
9
|
-
import { mergeConfig, type InlineConfig, type PluginOption } from 'vite';
|
|
10
|
+
import { createLogger, mergeConfig, type InlineConfig, type Logger, type PluginOption } from 'vite';
|
|
10
11
|
|
|
11
12
|
import { type ResolvedToilConfig } from './config.js';
|
|
12
13
|
import { fontPreloadPlugin } from './fonts.js';
|
|
@@ -120,6 +121,24 @@ function manualChunks(id: string): string | undefined {
|
|
|
120
121
|
* splitting and tuned build options, is applied here; `toiljs/client` is aliased to the
|
|
121
122
|
* runtime, and the user's `client.vite` overrides deep-merge on top.
|
|
122
123
|
*/
|
|
124
|
+
/**
|
|
125
|
+
* Vite's `(ssr)` environment label is dark blue — nearly invisible on a black
|
|
126
|
+
* terminal. Wrap the default logger to recolor `(ssr)` magenta (purple) and
|
|
127
|
+
* `(client)` cyan, so the dev reload logs are readable.
|
|
128
|
+
*/
|
|
129
|
+
function brandedLogger(): Logger {
|
|
130
|
+
const logger = createLogger();
|
|
131
|
+
const recolor = (msg: string): string =>
|
|
132
|
+
msg
|
|
133
|
+
.replace(/(?:\[[0-9;]*m)*\(ssr\)(?:\[[0-9;]*m)*/g, pc.magenta('(ssr)'))
|
|
134
|
+
.replace(/(?:\[[0-9;]*m)*\(client\)(?:\[[0-9;]*m)*/g, pc.cyan('(client)'));
|
|
135
|
+
const info = logger.info.bind(logger);
|
|
136
|
+
const warn = logger.warn.bind(logger);
|
|
137
|
+
logger.info = (msg, opts) => info(recolor(msg), opts);
|
|
138
|
+
logger.warn = (msg, opts) => warn(recolor(msg), opts);
|
|
139
|
+
return logger;
|
|
140
|
+
}
|
|
141
|
+
|
|
123
142
|
export async function createViteConfig(cfg: ResolvedToilConfig): Promise<InlineConfig> {
|
|
124
143
|
const frameworkRoot = path.resolve(path.dirname(cfg.runtimePath), '..', '..');
|
|
125
144
|
const tailwind = await tailwindPlugin(cfg.root);
|
|
@@ -128,6 +147,7 @@ export async function createViteConfig(cfg: ResolvedToilConfig): Promise<InlineC
|
|
|
128
147
|
root: cfg.toilDir,
|
|
129
148
|
base: cfg.base,
|
|
130
149
|
configFile: false,
|
|
150
|
+
customLogger: brandedLogger(),
|
|
131
151
|
plugins: [
|
|
132
152
|
tailwind,
|
|
133
153
|
// Build-time image resize/optimization. Every *imported* raster image is compressed to
|