toiljs 0.0.46 → 0.0.48

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "toiljs",
3
3
  "type": "module",
4
- "version": "0.0.46",
4
+ "version": "0.0.48",
5
5
  "author": "Dacely",
6
6
  "description": "The modern React framework: a file-based React frontend and a ToilScript-compiled WebAssembly backend.",
7
7
  "repository": {
@@ -130,7 +130,7 @@
130
130
  "nodemailer": "^9.0.0",
131
131
  "picocolors": "^1.1.1",
132
132
  "sharp": "^0.35.0",
133
- "toilscript": "^0.1.25",
133
+ "toilscript": "^0.1.26",
134
134
  "typescript-eslint": "^8.60.0",
135
135
  "vite": "^8.0.14",
136
136
  "vite-imagetools": "^10.0.0",
package/src/cli/create.ts CHANGED
@@ -114,7 +114,7 @@ function scaffold(
114
114
  '@types/react-dom': '^19.2.3',
115
115
  eslint: '^10.2.0',
116
116
  prettier: '^3.8.1',
117
- toilscript: '^0.1.18',
117
+ toilscript: '^0.1.26',
118
118
  typescript: '^6.0.3',
119
119
  };
120
120
  for (const dep of requiredPackages(features).sort()) {
@@ -402,8 +402,8 @@ export function checkWasmBuilt(exists: boolean): Check {
402
402
 
403
403
  // --- Typed RPC (@data / @remote / @service) -------------------------------------------------------
404
404
 
405
- /** Minimum toilscript: @rest/@route HTTP layer + RPC codegen + hardened decoders + @data editor decls. */
406
- export const RPC_TOILSCRIPT_MIN = '0.1.15';
405
+ /** Minimum toilscript: @rest/@route HTTP layer + RPC codegen + hardened decoders + correct @data editor decls (TS2395 fix). */
406
+ export const RPC_TOILSCRIPT_MIN = '0.1.26';
407
407
 
408
408
  /** Whether each piece of the typed-RPC wiring is in place (computed in `doctor.ts`). */
409
409
  export interface RpcFacts {
@@ -122,46 +122,53 @@ export function previewShellHtml(): string {
122
122
  <meta name="viewport" content="width=device-width, initial-scale=1" />
123
123
  <title>Email preview · toiljs</title>
124
124
  <style>
125
- :root { color-scheme: dark; }
125
+ /* Matches the toiljs demo brand (examples/basic/client/styles/main.css). */
126
+ :root {
127
+ color-scheme: dark;
128
+ --bg: #080d11; --surface: #0e1520; --surface2: #131d2e; --border: #1b2330;
129
+ --text: #f5f6fa; --muted: #8b9ab4; --accent: #2563ff; --accent3: #22e3ab;
130
+ }
126
131
  * { box-sizing: border-box; }
127
- body { margin: 0; height: 100vh; display: flex; font: 14px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background: #0c0c11; color: #e7e9f0; }
128
- #side { width: 240px; flex: 0 0 auto; border-right: 1px solid #23232e; display: flex; flex-direction: column; background: #101016; }
129
- .brand { padding: 14px 16px; font-weight: 600; border-bottom: 1px solid #23232e; }
130
- #list { list-style: none; margin: 0; padding: 6px; overflow: auto; flex: 1; }
131
- #list li { padding: 8px 10px; border-radius: 8px; cursor: pointer; color: #c8cee0; }
132
- #list li:hover { background: #181820; }
133
- #list li.on { background: #1d1d6b33; color: #fff; }
134
- #list li.muted { color: #6b7080; cursor: default; }
135
- .hint { padding: 10px 16px; font-size: 12px; color: #6b7080; border-top: 1px solid #23232e; }
136
- .hint code { color: #9aa1b8; }
132
+ body { margin: 0; height: 100vh; display: flex; font: 14px/1.5 system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--text); }
133
+ #side { width: 248px; flex: 0 0 auto; border-right: 1px solid var(--border); display: flex; flex-direction: column; background: var(--surface); }
134
+ .brand { display: flex; align-items: center; gap: 10px; padding: 15px 16px; font-family: 'Montserrat', system-ui, sans-serif; font-weight: 800; font-size: 15px; letter-spacing: -0.01em; border-bottom: 1px solid var(--border); }
135
+ .brand .mark { width: 26px; height: 26px; flex: 0 0 auto; border-radius: 7px; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 13px; background: linear-gradient(135deg, var(--accent), #7c3aed 55%, var(--accent3)); }
136
+ #list { list-style: none; margin: 0; padding: 8px; overflow: auto; flex: 1; }
137
+ #list li { padding: 8px 11px; border-radius: 8px; cursor: pointer; color: var(--muted); transition: background 150ms, color 150ms; }
138
+ #list li:hover { background: rgba(255,255,255,0.04); color: var(--text); }
139
+ #list li.on { background: rgba(37,99,255,0.14); color: #fff; box-shadow: inset 2px 0 0 var(--accent); }
140
+ #list li.muted, #list li.muted:hover { color: #5d6a82; cursor: default; background: none; }
141
+ .hint { padding: 12px 16px; font-size: 12px; color: #5d6a82; border-top: 1px solid var(--border); }
142
+ .hint code { color: var(--muted); }
137
143
  #main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
138
- .empty { margin: auto; color: #6b7080; }
144
+ .empty { margin: auto; color: #5d6a82; }
139
145
  #view { display: flex; flex-direction: column; height: 100%; }
140
- .bar { display: flex; align-items: center; gap: 14px; padding: 12px 16px; border-bottom: 1px solid #23232e; }
141
- .subj { min-width: 0; flex: 1; display: flex; align-items: baseline; gap: 8px; }
142
- .subj .lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: #6b7080; }
143
- #subject { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
146
+ .bar { display: flex; align-items: center; gap: 14px; padding: 14px 18px; border-bottom: 1px solid var(--border); }
147
+ .subj { min-width: 0; flex: 1; display: flex; align-items: baseline; gap: 9px; }
148
+ .subj .lbl { font-size: 10px; text-transform: uppercase; letter-spacing: .08em; color: #5d6a82; }
149
+ #subject { font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
144
150
  .actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
145
- .seg { display: flex; border: 1px solid #2c2c38; border-radius: 8px; overflow: hidden; }
146
- .seg-btn { background: #15151c; border: 0; color: #8b90a4; font: inherit; padding: 6px 12px; cursor: pointer; }
147
- .seg-btn.on { background: #2563ff; color: #fff; }
148
- .btn { background: #15151c; border: 1px solid #2c2c38; color: #c8cee0; font: inherit; padding: 6px 12px; border-radius: 8px; cursor: pointer; }
149
- .btn:hover { color: #fff; border-color: #3a3a48; }
151
+ .seg { display: flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
152
+ .seg-btn { background: var(--surface); border: 0; color: var(--muted); font: inherit; padding: 6px 14px; cursor: pointer; transition: color 150ms; }
153
+ .seg-btn:hover { color: var(--text); }
154
+ .seg-btn.on { background: var(--accent); color: #fff; }
155
+ .btn { background: var(--surface); border: 1px solid var(--border); color: var(--text); font: inherit; padding: 6px 13px; border-radius: 8px; cursor: pointer; transition: border-color 150ms, background 150ms; }
156
+ .btn:hover { border-color: var(--accent); background: var(--surface2); }
150
157
  .body { display: flex; flex: 1; min-height: 0; }
151
- .tokens { width: 240px; flex: 0 0 auto; border-right: 1px solid #23232e; padding: 12px; overflow: auto; }
152
- .field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px; }
153
- .fname { font-size: 12px; color: #9aa1b8; }
154
- .field input { background: #0c0c11; border: 1px solid #2c2c38; border-radius: 6px; color: #e7e9f0; font: inherit; padding: 6px 8px; }
155
- .field input:focus { outline: none; border-color: #2563ff; }
156
- .muted { color: #6b7080; font-size: 12px; }
157
- .preview { flex: 1; min-width: 0; background: #f6f7f9; }
158
- #frame { width: 100%; height: 100%; border: 0; background: #fff; }
159
- #text { width: 100%; height: 100%; margin: 0; padding: 16px; overflow: auto; background: #0c0c11; color: #c8cee0; white-space: pre-wrap; }
158
+ .tokens { width: 248px; flex: 0 0 auto; border-right: 1px solid var(--border); padding: 14px; overflow: auto; }
159
+ .field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 13px; }
160
+ .fname { font-size: 12px; color: var(--muted); }
161
+ .field input { background: var(--bg); border: 1px solid var(--border); border-radius: 7px; color: var(--text); font: inherit; padding: 7px 9px; }
162
+ .field input:focus { outline: none; border-color: var(--accent); }
163
+ .muted { color: #5d6a82; font-size: 12px; }
164
+ .preview { flex: 1; min-width: 0; display: flex; background: var(--bg); padding: 18px; }
165
+ #frame { flex: 1; width: 100%; border: 1px solid var(--border); border-radius: 12px; background: var(--bg); }
166
+ #text { flex: 1; width: 100%; margin: 0; padding: 18px; overflow: auto; background: var(--surface); color: var(--muted); white-space: pre-wrap; border: 1px solid var(--border); border-radius: 12px; font: 13px/1.6 'SFMono-Regular', Consolas, monospace; }
160
167
  </style>
161
168
  </head>
162
169
  <body>
163
170
  <aside id="side">
164
- <div class="brand">✉ Emails</div>
171
+ <div class="brand"><span class="mark">✦</span>Emails</div>
165
172
  <ul id="list"></ul>
166
173
  <div class="hint">Author in <code>emails/*.tsx</code>; this updates live on save.</div>
167
174
  </aside>
@@ -21,6 +21,7 @@
21
21
  import fs from 'node:fs';
22
22
  import path from 'node:path';
23
23
 
24
+ import pc from 'picocolors';
24
25
  import { createServer, type ViteDevServer } from 'vite';
25
26
 
26
27
  import type { ResolvedToilConfig } from './config.js';
@@ -371,9 +372,13 @@ export async function renderEmails(cfg: ResolvedToilConfig): Promise<void> {
371
372
  fs.mkdirSync(path.dirname(generatedPath), { recursive: true });
372
373
  fs.writeFileSync(generatedPath, next);
373
374
  process.stdout.write(
374
- `emails: generated ${String(rendered.length)} template${rendered.length === 1 ? '' : 's'} (${rendered
375
- .map((r) => r.name)
376
- .join(', ')})\n`,
375
+ pc.green('') +
376
+ pc.dim(
377
+ `emails: generated ${String(rendered.length)} template${rendered.length === 1 ? '' : 's'} (${rendered
378
+ .map((r) => r.name)
379
+ .join(', ')})`,
380
+ ) +
381
+ '\n',
377
382
  );
378
383
  }
379
384
 
@@ -228,6 +228,33 @@ function watchServer(cfg: ResolvedToilConfig, watcher: ViteDevServer['watcher'])
228
228
  });
229
229
  }
230
230
 
231
+ /**
232
+ * Make `Ctrl+C` actually kill the dev server. Without this the process can hang
233
+ * on shutdown (the native uWebSockets listener / Vite's watcher don't always
234
+ * close promptly), so an old `toiljs dev` is left ORPHANED — still watching and
235
+ * rebuilding — and the next run races it (parallel double rebuilds), while the
236
+ * console is left with a hidden cursor. On SIGINT/SIGTERM we restore the cursor,
237
+ * close the servers, and force-exit after a short grace period no matter what.
238
+ */
239
+ function installDevShutdown(close: () => Promise<void> | void): void {
240
+ let closing = false;
241
+ const shutdown = (): void => {
242
+ if (closing) return;
243
+ closing = true;
244
+ // Restore the cursor (anything that hid it leaves the terminal odd on exit).
245
+ process.stdout.write('\x1b[?25h');
246
+ process.stdout.write(pc.dim('\n shutting down dev server…') + '\n');
247
+ // Force-exit even if a server hangs on close (the orphan-prevention).
248
+ const hard = setTimeout(() => process.exit(0), 1500);
249
+ hard.unref();
250
+ Promise.resolve()
251
+ .then(close)
252
+ .catch(() => {})
253
+ .finally(() => process.exit(0));
254
+ };
255
+ for (const sig of ['SIGINT', 'SIGTERM'] as const) process.once(sig, shutdown);
256
+ }
257
+
231
258
  /** The server wasm artifact path from the toilconfig `release` target (toilscript's output). */
232
259
  function serverWasmFile(root: string): string {
233
260
  let outFile = 'build/server/release.wasm';
@@ -311,6 +338,7 @@ export async function dev(opts: ToilCommandOptions = {}): Promise<ViteDevServer>
311
338
  await server.listen();
312
339
  server.printUrls();
313
340
  printEmailsUrl(cfg, server.resolvedUrls?.local?.[0]);
341
+ installDevShutdown(() => server.close());
314
342
  return server;
315
343
  }
316
344
 
@@ -348,6 +376,10 @@ export async function dev(opts: ToilCommandOptions = {}): Promise<ViteDevServer>
348
376
  // Rebuild the server on server-file changes; Vite HMRs the regenerated shared/server.ts
349
377
  // and the dev server hot-swaps the recompiled wasm module.
350
378
  watchServer(cfg, server.watcher);
379
+ installDevShutdown(async () => {
380
+ await front.close();
381
+ await server.close();
382
+ });
351
383
  return server;
352
384
  }
353
385
 
@@ -32,11 +32,11 @@ describe('email preview end-to-end (examples/basic)', () => {
32
32
  // tokens discovered from props
33
33
  expect(r.tokens).toEqual(['code', 'name']);
34
34
  // subject token template
35
- expect(r.subject).toBe('Welcome, {{name}}!');
36
- // .email-title { color: #111827 } from emails/styles/email.css inlined onto the <h1>
37
- expect(r.html).toMatch(/<h1[^>]*style="[^"]*color:\s*#111827/i);
38
- // .email-card backgroundColor inlined onto the <table>
39
- expect(r.html).toMatch(/<table[^>]*style="[^"]*background-color:\s*#f6f7f9/i);
35
+ expect(r.subject).toBe('Welcome to toiljs, {{name}}');
36
+ // .email-title { color: #f5f6fa } from emails/styles/email.css inlined onto the <h1>
37
+ expect(r.html).toMatch(/<h1[^>]*style="[^"]*color:\s*#f5f6fa/i);
38
+ // .email-card { background-color: #0e1520 } inlined onto the card <table>
39
+ expect(r.html).toMatch(/<table[^>]*style="[^"]*background-color:\s*#0e1520/i);
40
40
 
41
41
  // The `client/*` reuse alias still resolves project CSS (the documented
42
42
  // `import 'client/styles/…'` path), independent of where the demo keeps its styles.