@svelterm/core 0.1.0 → 0.21.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 +425 -0
- package/README.md +42 -29
- package/dist/src/cli/build.d.ts +13 -0
- package/dist/src/cli/build.js +119 -0
- package/dist/src/cli/bundle.d.ts +25 -0
- package/dist/src/cli/bundle.js +61 -0
- package/dist/src/cli/dev.d.ts +10 -0
- package/dist/src/cli/dev.js +152 -0
- package/dist/src/cli/devtools.d.ts +9 -0
- package/dist/src/cli/devtools.js +47 -0
- package/dist/src/cli/init.d.ts +8 -0
- package/dist/src/cli/init.js +153 -0
- package/dist/src/cli/main.d.ts +9 -0
- package/dist/src/cli/main.js +52 -0
- package/dist/src/cli/svt-bin.d.ts +2 -0
- package/dist/src/cli/svt-bin.js +6 -0
- package/dist/src/cli/svt.d.ts +14 -0
- package/dist/src/cli/svt.js +76 -0
- package/dist/src/components/text-buffer.js +8 -5
- package/dist/src/css/animation-runner.d.ts +15 -6
- package/dist/src/css/animation-runner.js +80 -29
- package/dist/src/css/animation.d.ts +12 -0
- package/dist/src/css/animation.js +21 -0
- package/dist/src/css/calc.js +4 -3
- package/dist/src/css/color.d.ts +19 -0
- package/dist/src/css/color.js +371 -62
- package/dist/src/css/compute.d.ts +30 -3
- package/dist/src/css/compute.js +272 -33
- package/dist/src/css/defaults.d.ts +1 -1
- package/dist/src/css/defaults.js +9 -0
- package/dist/src/css/easing.d.ts +9 -0
- package/dist/src/css/easing.js +95 -0
- package/dist/src/css/incremental.d.ts +1 -1
- package/dist/src/css/incremental.js +2 -2
- package/dist/src/css/interpolate.d.ts +13 -0
- package/dist/src/css/interpolate.js +41 -0
- package/dist/src/css/parser.js +59 -3
- package/dist/src/css/pseudo-elements.d.ts +9 -0
- package/dist/src/css/pseudo-elements.js +97 -0
- package/dist/src/css/selector.d.ts +17 -2
- package/dist/src/css/selector.js +128 -13
- package/dist/src/css/specificity.js +17 -6
- package/dist/src/css/values.d.ts +6 -1
- package/dist/src/css/values.js +13 -6
- package/dist/src/debug/context.d.ts +13 -0
- package/dist/src/debug/context.js +11 -0
- package/dist/src/debug/css.d.ts +12 -0
- package/dist/src/debug/css.js +28 -0
- package/dist/src/debug/dom.d.ts +17 -0
- package/dist/src/debug/dom.js +92 -0
- package/dist/src/devtools/DevTools.compiled.js +327 -0
- package/dist/src/devtools/DevTools.css.js +1 -0
- package/dist/src/devtools/client.d.ts +36 -0
- package/dist/src/devtools/client.js +76 -0
- package/dist/src/framelog.d.ts +54 -0
- package/dist/src/framelog.js +99 -0
- package/dist/src/headless.js +12 -4
- package/dist/src/index.d.ts +65 -3
- package/dist/src/index.js +609 -81
- package/dist/src/input/checkable.d.ts +8 -0
- package/dist/src/input/checkable.js +66 -0
- package/dist/src/input/details.d.ts +6 -0
- package/dist/src/input/details.js +34 -0
- package/dist/src/input/focus.d.ts +6 -0
- package/dist/src/input/focus.js +27 -9
- package/dist/src/input/keyboard.d.ts +2 -2
- package/dist/src/input/keyboard.js +32 -5
- package/dist/src/input/label.d.ts +8 -0
- package/dist/src/input/label.js +53 -0
- package/dist/src/input/modal.d.ts +9 -0
- package/dist/src/input/modal.js +28 -0
- package/dist/src/input/mouse.d.ts +2 -2
- package/dist/src/input/mouse.js +15 -2
- package/dist/src/input/select.d.ts +12 -0
- package/dist/src/input/select.js +63 -0
- package/dist/src/input/selection.d.ts +48 -0
- package/dist/src/input/selection.js +150 -0
- package/dist/src/layout/engine.d.ts +2 -0
- package/dist/src/layout/engine.js +1084 -142
- package/dist/src/layout/flex.js +4 -4
- package/dist/src/layout/size.js +3 -2
- package/dist/src/layout/text.d.ts +3 -2
- package/dist/src/layout/text.js +96 -17
- package/dist/src/layout/unicode.d.ts +20 -0
- package/dist/src/layout/unicode.js +121 -0
- package/dist/src/render/animation-clock.d.ts +51 -0
- package/dist/src/render/animation-clock.js +213 -0
- package/dist/src/render/ansi-text.d.ts +26 -0
- package/dist/src/render/ansi-text.js +131 -0
- package/dist/src/render/ansi.d.ts +18 -0
- package/dist/src/render/ansi.js +64 -19
- package/dist/src/render/border.js +166 -17
- package/dist/src/render/buffer.d.ts +1 -0
- package/dist/src/render/buffer.js +5 -2
- package/dist/src/render/color-depth.d.ts +8 -0
- package/dist/src/render/color-depth.js +59 -0
- package/dist/src/render/context.d.ts +1 -0
- package/dist/src/render/context.js +17 -21
- package/dist/src/render/cursor-emit.d.ts +18 -0
- package/dist/src/render/cursor-emit.js +50 -0
- package/dist/src/render/diff.d.ts +12 -0
- package/dist/src/render/diff.js +120 -0
- package/dist/src/render/generation.d.ts +9 -0
- package/dist/src/render/generation.js +14 -0
- package/dist/src/render/graphics-layer.d.ts +27 -0
- package/dist/src/render/graphics-layer.js +86 -0
- package/dist/src/render/image.d.ts +27 -0
- package/dist/src/render/image.js +113 -0
- package/dist/src/render/incremental-paint.d.ts +7 -3
- package/dist/src/render/incremental-paint.js +52 -79
- package/dist/src/render/inline.d.ts +59 -0
- package/dist/src/render/inline.js +219 -0
- package/dist/src/render/kitty-graphics.d.ts +24 -0
- package/dist/src/render/kitty-graphics.js +58 -0
- package/dist/src/render/paint-text.js +68 -22
- package/dist/src/render/paint.d.ts +8 -1
- package/dist/src/render/paint.js +328 -30
- package/dist/src/render/png.d.ts +13 -0
- package/dist/src/render/png.js +145 -0
- package/dist/src/render/scrollbar.d.ts +8 -2
- package/dist/src/render/scrollbar.js +71 -14
- package/dist/src/render/snapshot.js +3 -1
- package/dist/src/renderer/default.d.ts +7 -0
- package/dist/src/renderer/default.js +11 -0
- package/dist/src/renderer/index.d.ts +8 -2
- package/dist/src/renderer/index.js +4 -2
- package/dist/src/renderer/node.d.ts +109 -0
- package/dist/src/renderer/node.js +165 -1
- package/dist/src/terminal/capabilities.d.ts +33 -0
- package/dist/src/terminal/capabilities.js +66 -0
- package/dist/src/terminal/clipboard.d.ts +9 -0
- package/dist/src/terminal/clipboard.js +39 -0
- package/dist/src/terminal/io.d.ts +82 -0
- package/dist/src/terminal/io.js +155 -0
- package/dist/src/terminal/screen.d.ts +3 -10
- package/dist/src/terminal/screen.js +5 -28
- package/dist/src/terminal/stdin-router.d.ts +8 -5
- package/dist/src/terminal/stdin-router.js +22 -11
- package/dist/src/utils/node-map.d.ts +24 -0
- package/dist/src/utils/node-map.js +75 -0
- package/dist/src/vite/config.d.ts +62 -0
- package/dist/src/vite/config.js +191 -0
- package/docs/compatibility.md +67 -0
- package/docs/debug/devtools.md +40 -0
- package/docs/debug/svt.md +50 -0
- package/docs/distribution.md +106 -0
- package/docs/elements.md +120 -0
- package/docs/getting-started.md +177 -0
- package/docs/guide/css.md +187 -0
- package/docs/guide/input.md +143 -0
- package/docs/guide/layout.md +171 -0
- package/docs/guide/theming.md +94 -0
- package/docs/how-it-works.md +115 -0
- package/docs/inline-mode.md +77 -0
- package/docs/layout.md +106 -0
- package/docs/motion.md +91 -0
- package/docs/reference/README.md +65 -0
- package/docs/reference/css/properties/border-corner.md +82 -0
- package/docs/reference/css/properties/border-style.md +168 -0
- package/docs/reference.md +226 -0
- package/docs/selectors.md +80 -0
- package/docs/terminal-css.md +149 -0
- package/docs/terminals.md +83 -0
- package/package.json +28 -7
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers behind `svelterm build` — the parts that don't need
|
|
3
|
+
* rolldown: code generation for the bundle and project-layout discovery.
|
|
4
|
+
*/
|
|
5
|
+
export declare const GLOBAL_CSS_MODULE = "\0svelterm:global-css";
|
|
6
|
+
export declare const BOOTSTRAP_MODULE = "\0svelterm:bootstrap";
|
|
7
|
+
export declare const WS_STUB_MODULE = "\0svelterm:ws-stub";
|
|
8
|
+
/** Debug-server dependency, never active in bundles. */
|
|
9
|
+
export declare const WS_STUB_SOURCE: string;
|
|
10
|
+
/**
|
|
11
|
+
* Append a registration call so the compiled component carries its
|
|
12
|
+
* extracted CSS into the bundle; run() collects it from the registry.
|
|
13
|
+
*/
|
|
14
|
+
export declare function withCssRegistration(compiledJs: string, css: string | null): string;
|
|
15
|
+
/**
|
|
16
|
+
* The bundle's entry module: global CSS registers first (so component
|
|
17
|
+
* styles land later in the cascade), then the app component mounts.
|
|
18
|
+
*/
|
|
19
|
+
export declare function bootstrapModule(entryPath: string, globalCss?: string): string;
|
|
20
|
+
/** The module registering the project's global stylesheet. */
|
|
21
|
+
export declare function globalCssModule(css: string): string;
|
|
22
|
+
/** The conventional entry component, or null if none exists. */
|
|
23
|
+
export declare function findEntry(projectDir: string): string | null;
|
|
24
|
+
/** The conventional global stylesheet, or null if none exists. */
|
|
25
|
+
export declare function findGlobalCss(projectDir: string): string | null;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers behind `svelterm build` — the parts that don't need
|
|
3
|
+
* rolldown: code generation for the bundle and project-layout discovery.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
export const GLOBAL_CSS_MODULE = '\0svelterm:global-css';
|
|
8
|
+
export const BOOTSTRAP_MODULE = '\0svelterm:bootstrap';
|
|
9
|
+
export const WS_STUB_MODULE = '\0svelterm:ws-stub';
|
|
10
|
+
/** Debug-server dependency, never active in bundles. */
|
|
11
|
+
export const WS_STUB_SOURCE = [
|
|
12
|
+
'export default undefined',
|
|
13
|
+
'export class WebSocketServer {}',
|
|
14
|
+
'export class WebSocket {}',
|
|
15
|
+
'',
|
|
16
|
+
].join('\n');
|
|
17
|
+
/**
|
|
18
|
+
* Append a registration call so the compiled component carries its
|
|
19
|
+
* extracted CSS into the bundle; run() collects it from the registry.
|
|
20
|
+
*/
|
|
21
|
+
export function withCssRegistration(compiledJs, css) {
|
|
22
|
+
if (!css)
|
|
23
|
+
return compiledJs;
|
|
24
|
+
return compiledJs
|
|
25
|
+
+ `\nimport { registerComponentCss as __svelterm_registerCss } from '@svelterm/core/app'`
|
|
26
|
+
+ `\n__svelterm_registerCss(${JSON.stringify(css)})\n`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The bundle's entry module: global CSS registers first (so component
|
|
30
|
+
* styles land later in the cascade), then the app component mounts.
|
|
31
|
+
*/
|
|
32
|
+
export function bootstrapModule(entryPath, globalCss) {
|
|
33
|
+
const lines = [`import { run } from '@svelterm/core/app'`];
|
|
34
|
+
if (globalCss)
|
|
35
|
+
lines.push(`import ${JSON.stringify('svelterm:global-css')}`);
|
|
36
|
+
lines.push(`import App from ${JSON.stringify(entryPath)}`, `run(App, {})`, '');
|
|
37
|
+
return lines.join('\n');
|
|
38
|
+
}
|
|
39
|
+
/** The module registering the project's global stylesheet. */
|
|
40
|
+
export function globalCssModule(css) {
|
|
41
|
+
return `import { registerComponentCss } from '@svelterm/core/app'\n`
|
|
42
|
+
+ `registerComponentCss(${JSON.stringify(css)})\n`;
|
|
43
|
+
}
|
|
44
|
+
const ENTRY_CANDIDATES = ['src/App.svelte', 'App.svelte'];
|
|
45
|
+
const GLOBAL_CSS_CANDIDATES = ['src/main.css', 'main.css'];
|
|
46
|
+
/** The conventional entry component, or null if none exists. */
|
|
47
|
+
export function findEntry(projectDir) {
|
|
48
|
+
return firstExisting(projectDir, ENTRY_CANDIDATES);
|
|
49
|
+
}
|
|
50
|
+
/** The conventional global stylesheet, or null if none exists. */
|
|
51
|
+
export function findGlobalCss(projectDir) {
|
|
52
|
+
return firstExisting(projectDir, GLOBAL_CSS_CANDIDATES);
|
|
53
|
+
}
|
|
54
|
+
function firstExisting(dir, candidates) {
|
|
55
|
+
for (const candidate of candidates) {
|
|
56
|
+
const full = path.join(dir, candidate);
|
|
57
|
+
if (existsSync(full))
|
|
58
|
+
return full;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* svelterm dev — connects to a Vite dev server and runs the
|
|
3
|
+
* terminal app. The server compiles modules, this process
|
|
4
|
+
* renders to the terminal.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx svelterm dev http://localhost:5173
|
|
8
|
+
* npx svelterm dev http://localhost:5173/src/App.svelte
|
|
9
|
+
*/
|
|
10
|
+
export declare function runDev(argv: string[]): Promise<void>;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* svelterm dev — connects to a Vite dev server and runs the
|
|
3
|
+
* terminal app. The server compiles modules, this process
|
|
4
|
+
* renders to the terminal.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx svelterm dev http://localhost:5173
|
|
8
|
+
* npx svelterm dev http://localhost:5173/src/App.svelte
|
|
9
|
+
*/
|
|
10
|
+
import { ModuleRunner, createWebSocketModuleRunnerTransport } from 'vite/module-runner';
|
|
11
|
+
import { WebSocket } from 'ws';
|
|
12
|
+
import http from 'http';
|
|
13
|
+
export async function runDev(argv) {
|
|
14
|
+
const url = argv[0];
|
|
15
|
+
if (!url) {
|
|
16
|
+
console.error('Usage: svelterm dev http://localhost:5173');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const parsedUrl = new URL(url);
|
|
20
|
+
const baseUrl = `${parsedUrl.protocol}//${parsedUrl.host}`;
|
|
21
|
+
const wsUrl = `ws://${parsedUrl.host}/__svelterm`;
|
|
22
|
+
const ws = new WebSocket(wsUrl);
|
|
23
|
+
await new Promise((resolve, reject) => {
|
|
24
|
+
ws.on('open', resolve);
|
|
25
|
+
ws.on('error', reject);
|
|
26
|
+
});
|
|
27
|
+
const transport = createWebSocketModuleRunnerTransport({
|
|
28
|
+
createConnection() {
|
|
29
|
+
return ws;
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
const runner = new ModuleRunner({ transport });
|
|
33
|
+
// Discover entry from the server config, or use the URL path
|
|
34
|
+
let entry;
|
|
35
|
+
if (parsedUrl.pathname !== '/') {
|
|
36
|
+
entry = parsedUrl.pathname;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const serverConfig = await fetchJson(`${baseUrl}/__svelterm/config`);
|
|
40
|
+
if (!serverConfig) {
|
|
41
|
+
console.error('svelterm not configured on this server.');
|
|
42
|
+
console.error('Add svelterm.terminalServer() to your vite plugins.');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
entry = '/' + serverConfig.entry.replace(/^\.\//, '');
|
|
46
|
+
}
|
|
47
|
+
let cleanup = null;
|
|
48
|
+
let restartTimer = null;
|
|
49
|
+
async function startApp() {
|
|
50
|
+
if (cleanup) {
|
|
51
|
+
try {
|
|
52
|
+
cleanup();
|
|
53
|
+
}
|
|
54
|
+
catch { }
|
|
55
|
+
cleanup = null;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
// Clear evaluated modules for fresh imports
|
|
59
|
+
const mods = runner.evaluatedModules;
|
|
60
|
+
if (mods?.idToModuleMap) {
|
|
61
|
+
mods.idToModuleMap.clear();
|
|
62
|
+
}
|
|
63
|
+
const mod = await runner.import(entry);
|
|
64
|
+
const Component = mod.default;
|
|
65
|
+
if (!Component) {
|
|
66
|
+
console.error(`${entry} must default-export a Svelte component`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const sveltermMod = await runner.import('@svelterm/core/app');
|
|
70
|
+
const css = await fetchText(`${baseUrl}/__svelterm/css`) ?? '';
|
|
71
|
+
cleanup = sveltermMod.run(Component, {
|
|
72
|
+
css,
|
|
73
|
+
fullscreen: true,
|
|
74
|
+
mouse: true,
|
|
75
|
+
// The terminal is the app's screen — route console output
|
|
76
|
+
// to the dev server pane instead, like a browser console.
|
|
77
|
+
onConsole(entry) {
|
|
78
|
+
if (ws.readyState !== ws.OPEN)
|
|
79
|
+
return;
|
|
80
|
+
ws.send(JSON.stringify({
|
|
81
|
+
type: 'custom',
|
|
82
|
+
event: 'svelterm:console',
|
|
83
|
+
data: { level: entry.level, text: entry.args.join(' ') },
|
|
84
|
+
}));
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
console.error('[svelterm] Error:', err);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function scheduleRestart() {
|
|
93
|
+
if (restartTimer)
|
|
94
|
+
clearTimeout(restartTimer);
|
|
95
|
+
restartTimer = setTimeout(() => {
|
|
96
|
+
restartTimer = null;
|
|
97
|
+
startApp();
|
|
98
|
+
}, 100);
|
|
99
|
+
}
|
|
100
|
+
// Listen for HMR updates from the server
|
|
101
|
+
ws.on('message', (data) => {
|
|
102
|
+
try {
|
|
103
|
+
const msg = JSON.parse(data.toString());
|
|
104
|
+
if (msg.type === 'update' || msg.type === 'full-reload') {
|
|
105
|
+
scheduleRestart();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch { }
|
|
109
|
+
});
|
|
110
|
+
await startApp();
|
|
111
|
+
process.on('SIGINT', () => {
|
|
112
|
+
if (cleanup)
|
|
113
|
+
cleanup();
|
|
114
|
+
runner.close();
|
|
115
|
+
ws.close();
|
|
116
|
+
process.exit(0);
|
|
117
|
+
});
|
|
118
|
+
process.on('SIGTERM', () => {
|
|
119
|
+
if (cleanup)
|
|
120
|
+
cleanup();
|
|
121
|
+
runner.close();
|
|
122
|
+
ws.close();
|
|
123
|
+
process.exit(0);
|
|
124
|
+
});
|
|
125
|
+
// Keep the process alive on the WebSocket + terminal input.
|
|
126
|
+
await new Promise((resolve) => ws.on('close', resolve));
|
|
127
|
+
}
|
|
128
|
+
function fetchJson(url) {
|
|
129
|
+
return new Promise((resolve) => {
|
|
130
|
+
http.get(url, (res) => {
|
|
131
|
+
let data = '';
|
|
132
|
+
res.on('data', (chunk) => data += chunk);
|
|
133
|
+
res.on('end', () => {
|
|
134
|
+
try {
|
|
135
|
+
resolve(JSON.parse(data));
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
resolve(null);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}).on('error', () => resolve(null));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function fetchText(url) {
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
http.get(url, (res) => {
|
|
147
|
+
let data = '';
|
|
148
|
+
res.on('data', (chunk) => data += chunk);
|
|
149
|
+
res.on('end', () => resolve(data));
|
|
150
|
+
}).on('error', () => resolve(null));
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* svelterm devtools — launch the DevTools TUI, a svelterm app that
|
|
3
|
+
* connects to a running app's debug server and inspects its live tree,
|
|
4
|
+
* styles, and layout.
|
|
5
|
+
*
|
|
6
|
+
* svelterm devtools # connect on 9444
|
|
7
|
+
* svelterm devtools --port 9500
|
|
8
|
+
*/
|
|
9
|
+
export declare function runDevtools(argv: string[]): Promise<void>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* svelterm devtools — launch the DevTools TUI, a svelterm app that
|
|
3
|
+
* connects to a running app's debug server and inspects its live tree,
|
|
4
|
+
* styles, and layout.
|
|
5
|
+
*
|
|
6
|
+
* svelterm devtools # connect on 9444
|
|
7
|
+
* svelterm devtools --port 9500
|
|
8
|
+
*/
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { pathToFileURL } from 'node:url';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
export async function runDevtools(argv) {
|
|
14
|
+
const port = extractPort(argv);
|
|
15
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const compiledPath = path.resolve(here, '../devtools/DevTools.compiled.js');
|
|
17
|
+
const cssPath = path.resolve(here, '../devtools/DevTools.css.js');
|
|
18
|
+
let DevTools;
|
|
19
|
+
let css = '';
|
|
20
|
+
try {
|
|
21
|
+
DevTools = (await import(pathToFileURL(compiledPath).href)).default;
|
|
22
|
+
css = (await import(pathToFileURL(cssPath).href)).css ?? '';
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
console.error('DevTools component is not compiled. Reinstall @svelterm/core.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
// Import run() the same way a consumer's app does, so the renderer
|
|
29
|
+
// instance matches the compiled component's.
|
|
30
|
+
const require = createRequire(path.join(process.cwd(), 'package.json'));
|
|
31
|
+
let run;
|
|
32
|
+
try {
|
|
33
|
+
run = require('@svelterm/core/app').run;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
const mod = await import('../index.js');
|
|
37
|
+
run = mod.run;
|
|
38
|
+
}
|
|
39
|
+
run(DevTools, { css, props: { port } });
|
|
40
|
+
}
|
|
41
|
+
function extractPort(argv) {
|
|
42
|
+
for (let i = 0; i < argv.length; i++) {
|
|
43
|
+
if (argv[i] === '--port')
|
|
44
|
+
return Number(argv[i + 1]) || 9444;
|
|
45
|
+
}
|
|
46
|
+
return 9444;
|
|
47
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* svelterm init — scaffold a terminal app project:
|
|
3
|
+
*
|
|
4
|
+
* npx svelterm init my-app
|
|
5
|
+
*/
|
|
6
|
+
export declare function runInit(argv: string[]): void;
|
|
7
|
+
/** Create the project files; throws if the directory has content. */
|
|
8
|
+
export declare function scaffoldProject(dir: string): void;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* svelterm init — scaffold a terminal app project:
|
|
3
|
+
*
|
|
4
|
+
* npx svelterm init my-app
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
export function runInit(argv) {
|
|
9
|
+
const target = argv[0];
|
|
10
|
+
if (!target) {
|
|
11
|
+
console.error('Usage: svelterm init <directory>');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const dir = path.resolve(target);
|
|
15
|
+
try {
|
|
16
|
+
scaffoldProject(dir);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
console.log(`Scaffolded ${path.basename(dir)}. Next:`);
|
|
23
|
+
console.log(` cd ${target}`);
|
|
24
|
+
console.log(' # see README.md — svelterm needs the custom-renderer Svelte fork');
|
|
25
|
+
console.log(' npm install && npm run dev');
|
|
26
|
+
}
|
|
27
|
+
/** Create the project files; throws if the directory has content. */
|
|
28
|
+
export function scaffoldProject(dir) {
|
|
29
|
+
if (existsSync(dir) && readdirSync(dir).length > 0) {
|
|
30
|
+
throw new Error(`${dir} is not empty — refusing to scaffold over existing files`);
|
|
31
|
+
}
|
|
32
|
+
mkdirSync(path.join(dir, 'src'), { recursive: true });
|
|
33
|
+
const name = path.basename(dir);
|
|
34
|
+
for (const [file, content] of Object.entries(templates(name))) {
|
|
35
|
+
writeFileSync(path.join(dir, file), content);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function templates(name) {
|
|
39
|
+
return {
|
|
40
|
+
'package.json': JSON.stringify({
|
|
41
|
+
name,
|
|
42
|
+
private: true,
|
|
43
|
+
version: '0.0.0',
|
|
44
|
+
type: 'module',
|
|
45
|
+
scripts: {
|
|
46
|
+
dev: 'vite dev',
|
|
47
|
+
app: 'svelterm dev http://localhost:5173',
|
|
48
|
+
build: 'svelterm build',
|
|
49
|
+
},
|
|
50
|
+
dependencies: {
|
|
51
|
+
'@svelterm/core': '^0.4.0',
|
|
52
|
+
// The custom-renderer Svelte branch — see the scaffolded README
|
|
53
|
+
svelte: 'file:../svelte-fork/packages/svelte',
|
|
54
|
+
},
|
|
55
|
+
devDependencies: {
|
|
56
|
+
rolldown: '^1.0.0',
|
|
57
|
+
vite: '^7.0.0',
|
|
58
|
+
},
|
|
59
|
+
}, null, 4) + '\n',
|
|
60
|
+
'vite.config.ts': `import { defineConfig } from 'vite'
|
|
61
|
+
import { svelterm } from '@svelterm/core/vite'
|
|
62
|
+
|
|
63
|
+
// Terminal-only app — svelterm's plugins compile .svelte for the
|
|
64
|
+
// terminal environment; no vite-plugin-svelte needed. (Dual-target
|
|
65
|
+
// browser + terminal setups do need it — see
|
|
66
|
+
// https://svelterm.dev/docs/getting-started.)
|
|
67
|
+
export default defineConfig({
|
|
68
|
+
plugins: [
|
|
69
|
+
...svelterm.terminalServer({ entry: './src/App.svelte' }),
|
|
70
|
+
],
|
|
71
|
+
environments: svelterm.environments(),
|
|
72
|
+
optimizeDeps: { exclude: ['svelte'] },
|
|
73
|
+
ssr: { noExternal: ['svelte'] },
|
|
74
|
+
})
|
|
75
|
+
`,
|
|
76
|
+
'src/App.svelte': `<script>
|
|
77
|
+
let count = $state(0)
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<div class="app">
|
|
81
|
+
<span class="title">${name}</span>
|
|
82
|
+
<span>Count: <span class="value">{count}</span></span>
|
|
83
|
+
<button onclick={() => count++}>Increment</button>
|
|
84
|
+
<button onclick={() => count--}>Decrement</button>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<style>
|
|
88
|
+
.app {
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-direction: column;
|
|
91
|
+
gap: 1cell;
|
|
92
|
+
border: rounded;
|
|
93
|
+
border-color: cyan;
|
|
94
|
+
padding: 1cell 2cell;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.title {
|
|
98
|
+
font-weight: bold;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.value {
|
|
102
|
+
color: yellow;
|
|
103
|
+
font-weight: bold;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
button {
|
|
107
|
+
width: 20cell;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
button:focus {
|
|
111
|
+
color: cyan;
|
|
112
|
+
font-weight: bold;
|
|
113
|
+
}
|
|
114
|
+
</style>
|
|
115
|
+
`,
|
|
116
|
+
'src/main.css': `/* Global styles — component <style> blocks layer on top. */
|
|
117
|
+
`,
|
|
118
|
+
'.gitignore': `node_modules/
|
|
119
|
+
dist/
|
|
120
|
+
`,
|
|
121
|
+
'README.md': `# ${name}
|
|
122
|
+
|
|
123
|
+
A terminal app built with [svelterm](https://svelterm.dev).
|
|
124
|
+
|
|
125
|
+
## Prerequisites
|
|
126
|
+
|
|
127
|
+
svelterm needs the Svelte custom-renderer branch (unmerged upstream).
|
|
128
|
+
Clone and build it as a sibling of this project:
|
|
129
|
+
|
|
130
|
+
\`\`\`bash
|
|
131
|
+
git clone -b svelte-custom-renderer https://github.com/tomyan/svelte.git ../svelte-fork
|
|
132
|
+
cd ../svelte-fork && pnpm install && pnpm -C packages/svelte build
|
|
133
|
+
\`\`\`
|
|
134
|
+
|
|
135
|
+
## Develop
|
|
136
|
+
|
|
137
|
+
\`\`\`bash
|
|
138
|
+
npm install
|
|
139
|
+
npm run dev # vite dev server (terminal 1)
|
|
140
|
+
npm run app # the app, in this terminal (terminal 2)
|
|
141
|
+
\`\`\`
|
|
142
|
+
|
|
143
|
+
Edits hot-reload; \`console.log\` output appears in the vite terminal.
|
|
144
|
+
|
|
145
|
+
## Ship
|
|
146
|
+
|
|
147
|
+
\`\`\`bash
|
|
148
|
+
npm run build # → dist/app.mjs, runnable with plain node
|
|
149
|
+
node dist/app.mjs
|
|
150
|
+
\`\`\`
|
|
151
|
+
`,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* svelterm — the CLI. Subcommands:
|
|
4
|
+
*
|
|
5
|
+
* svelterm init <dir> scaffold a terminal app project
|
|
6
|
+
* svelterm dev <url> run the app against a Vite dev server
|
|
7
|
+
* svelterm build [entry] bundle into one self-contained .mjs
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* svelterm — the CLI. Subcommands:
|
|
4
|
+
*
|
|
5
|
+
* svelterm init <dir> scaffold a terminal app project
|
|
6
|
+
* svelterm dev <url> run the app against a Vite dev server
|
|
7
|
+
* svelterm build [entry] bundle into one self-contained .mjs
|
|
8
|
+
*/
|
|
9
|
+
const USAGE = `Usage:
|
|
10
|
+
svelterm init <directory>
|
|
11
|
+
svelterm dev <dev-server-url>[/path/to/App.svelte]
|
|
12
|
+
svelterm build [entry.svelte] [-o out.mjs] [--css main.css]
|
|
13
|
+
svelterm inspect <tree|query|style|box|console|raw> [args] [--port n]
|
|
14
|
+
svelterm devtools [--port n]`;
|
|
15
|
+
async function main() {
|
|
16
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
17
|
+
switch (command) {
|
|
18
|
+
case 'init': {
|
|
19
|
+
const { runInit } = await import('./init.js');
|
|
20
|
+
runInit(rest);
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
case 'dev': {
|
|
24
|
+
const { runDev } = await import('./dev.js');
|
|
25
|
+
await runDev(rest);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case 'build': {
|
|
29
|
+
const { runBuild } = await import('./build.js');
|
|
30
|
+
await runBuild(rest);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case 'inspect': {
|
|
34
|
+
const { runSvt } = await import('./svt.js');
|
|
35
|
+
await runSvt(rest);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case 'devtools': {
|
|
39
|
+
const { runDevtools } = await import('./devtools.js');
|
|
40
|
+
await runDevtools(rest);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
default:
|
|
44
|
+
console.error(USAGE);
|
|
45
|
+
process.exit(command === undefined || command === '--help' ? 0 : 1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
main().catch((err) => {
|
|
49
|
+
console.error('Fatal:', err);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
});
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* svt — debug-protocol client. Connects to a `run(App, { debug: true })`
|
|
3
|
+
* app's WebSocket server, sends one request, prints the JSON result.
|
|
4
|
+
*
|
|
5
|
+
* svt tree # DOM.getDocument
|
|
6
|
+
* svt query '.card' # DOM.querySelector
|
|
7
|
+
* svt style <nodeId> # CSS.getComputedStyle
|
|
8
|
+
* svt box <nodeId> # DOM.getBoxModel
|
|
9
|
+
* svt console # Console.getEntries
|
|
10
|
+
* svt raw DOM.getDocument '{}' # any method + JSON params
|
|
11
|
+
*
|
|
12
|
+
* --port <n> debug server port (default 9444)
|
|
13
|
+
*/
|
|
14
|
+
export declare function runSvt(argv: string[]): Promise<void>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* svt — debug-protocol client. Connects to a `run(App, { debug: true })`
|
|
3
|
+
* app's WebSocket server, sends one request, prints the JSON result.
|
|
4
|
+
*
|
|
5
|
+
* svt tree # DOM.getDocument
|
|
6
|
+
* svt query '.card' # DOM.querySelector
|
|
7
|
+
* svt style <nodeId> # CSS.getComputedStyle
|
|
8
|
+
* svt box <nodeId> # DOM.getBoxModel
|
|
9
|
+
* svt console # Console.getEntries
|
|
10
|
+
* svt raw DOM.getDocument '{}' # any method + JSON params
|
|
11
|
+
*
|
|
12
|
+
* --port <n> debug server port (default 9444)
|
|
13
|
+
*/
|
|
14
|
+
import { WebSocket } from 'ws';
|
|
15
|
+
export async function runSvt(argv) {
|
|
16
|
+
const { rest, port } = extractPort(argv);
|
|
17
|
+
const command = buildCommand(rest);
|
|
18
|
+
if (!command) {
|
|
19
|
+
printUsage();
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const result = await request(port, command);
|
|
23
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
24
|
+
}
|
|
25
|
+
function buildCommand(argv) {
|
|
26
|
+
const [verb, ...args] = argv;
|
|
27
|
+
switch (verb) {
|
|
28
|
+
case 'tree': return { method: 'DOM.getDocument', params: {} };
|
|
29
|
+
case 'query': return { method: 'DOM.querySelector', params: { selector: args[0] } };
|
|
30
|
+
case 'style': return { method: 'CSS.getComputedStyle', params: { nodeId: Number(args[0]) } };
|
|
31
|
+
case 'box': return { method: 'DOM.getBoxModel', params: { nodeId: Number(args[0]) } };
|
|
32
|
+
case 'console': return { method: 'Console.getEntries', params: { count: Number(args[0]) || 100 } };
|
|
33
|
+
case 'raw': return { method: args[0], params: args[1] ? JSON.parse(args[1]) : {} };
|
|
34
|
+
default: return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function request(port, command) {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const ws = new WebSocket(`ws://127.0.0.1:${port}`);
|
|
40
|
+
const timer = setTimeout(() => { ws.close(); reject(new Error('timed out')); }, 3000);
|
|
41
|
+
ws.on('open', () => ws.send(JSON.stringify({ id: 1, ...command })));
|
|
42
|
+
ws.on('message', (data) => {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
const msg = JSON.parse(data.toString());
|
|
45
|
+
ws.close();
|
|
46
|
+
if (msg.error)
|
|
47
|
+
reject(new Error(msg.error.message));
|
|
48
|
+
else
|
|
49
|
+
resolve(msg.result);
|
|
50
|
+
});
|
|
51
|
+
ws.on('error', () => {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
reject(new Error(`cannot connect on port ${port} — run the target app with run(App, { debug: true })`));
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function extractPort(argv) {
|
|
58
|
+
const rest = [];
|
|
59
|
+
let port = 9444;
|
|
60
|
+
for (let i = 0; i < argv.length; i++) {
|
|
61
|
+
if (argv[i] === '--port')
|
|
62
|
+
port = Number(argv[++i]) || port;
|
|
63
|
+
else
|
|
64
|
+
rest.push(argv[i]);
|
|
65
|
+
}
|
|
66
|
+
return { rest, port };
|
|
67
|
+
}
|
|
68
|
+
function printUsage() {
|
|
69
|
+
console.error(`Usage: svt <command> [--port <n>]
|
|
70
|
+
tree print the node tree
|
|
71
|
+
query <selector> find a node id by CSS selector
|
|
72
|
+
style <nodeId> computed style for a node
|
|
73
|
+
box <nodeId> layout box for a node
|
|
74
|
+
console [count] recent console entries
|
|
75
|
+
raw <method> [json] any protocol method with JSON params`);
|
|
76
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nextGraphemeBoundary, prevGraphemeBoundary } from '../layout/unicode.js';
|
|
1
2
|
export class TextBuffer {
|
|
2
3
|
_text;
|
|
3
4
|
_cursor;
|
|
@@ -18,16 +19,18 @@ export class TextBuffer {
|
|
|
18
19
|
delete() {
|
|
19
20
|
if (this._cursor >= this._text.length)
|
|
20
21
|
return;
|
|
21
|
-
|
|
22
|
+
const end = nextGraphemeBoundary(this._text, this._cursor);
|
|
23
|
+
this._text = this._text.substring(0, this._cursor) + this._text.substring(end);
|
|
22
24
|
}
|
|
23
25
|
backspace() {
|
|
24
26
|
if (this._cursor <= 0)
|
|
25
27
|
return;
|
|
26
|
-
|
|
27
|
-
this._cursor
|
|
28
|
+
const start = prevGraphemeBoundary(this._text, this._cursor);
|
|
29
|
+
this._text = this._text.substring(0, start) + this._text.substring(this._cursor);
|
|
30
|
+
this._cursor = start;
|
|
28
31
|
}
|
|
29
|
-
moveLeft() { this.
|
|
30
|
-
moveRight() { this.
|
|
32
|
+
moveLeft() { this._cursor = prevGraphemeBoundary(this._text, this._cursor); }
|
|
33
|
+
moveRight() { this._cursor = nextGraphemeBoundary(this._text, this._cursor); }
|
|
31
34
|
home() { this._cursor = 0; }
|
|
32
35
|
end() { this._cursor = this._text.length; }
|
|
33
36
|
clearToStart() {
|