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/CHANGELOG.md +20 -0
- package/RSG.md +21 -8
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +2 -2
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/email-preview.js +38 -31
- package/build/compiler/emails.js +6 -3
- package/build/compiler/index.js +23 -0
- package/package.json +2 -2
- package/src/cli/create.ts +1 -1
- package/src/cli/diagnostics.ts +2 -2
- package/src/compiler/email-preview.ts +38 -31
- package/src/compiler/emails.ts +8 -3
- package/src/compiler/index.ts +32 -0
- package/test/email-preview.test.ts +5 -5
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toiljs",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
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.
|
|
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
package/src/cli/diagnostics.ts
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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:
|
|
128
|
-
#side { width:
|
|
129
|
-
.brand { padding:
|
|
130
|
-
|
|
131
|
-
#list
|
|
132
|
-
#list li
|
|
133
|
-
#list li
|
|
134
|
-
#list li.
|
|
135
|
-
.
|
|
136
|
-
.hint
|
|
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: #
|
|
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:
|
|
141
|
-
.subj { min-width: 0; flex: 1; display: flex; align-items: baseline; gap:
|
|
142
|
-
.subj .lbl { font-size:
|
|
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
|
|
146
|
-
.seg-btn { background:
|
|
147
|
-
.seg-btn
|
|
148
|
-
.btn { background:
|
|
149
|
-
.btn
|
|
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:
|
|
152
|
-
.field { display: flex; flex-direction: column; gap:
|
|
153
|
-
.fname { font-size: 12px; color:
|
|
154
|
-
.field input { background:
|
|
155
|
-
.field input:focus { outline: none; border-color:
|
|
156
|
-
.muted { color: #
|
|
157
|
-
.preview { flex: 1; min-width: 0; background:
|
|
158
|
-
#frame { width: 100%;
|
|
159
|
-
#text {
|
|
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"
|
|
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>
|
package/src/compiler/emails.ts
CHANGED
|
@@ -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
|
-
|
|
375
|
-
.
|
|
376
|
-
|
|
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
|
|
package/src/compiler/index.ts
CHANGED
|
@@ -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: #
|
|
37
|
-
expect(r.html).toMatch(/<h1[^>]*style="[^"]*color:\s*#
|
|
38
|
-
// .email-card
|
|
39
|
-
expect(r.html).toMatch(/<table[^>]*style="[^"]*background-color:\s*#
|
|
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.
|