thebird 1.2.50 → 1.2.51
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/docs/shell.js +58 -24
- package/docs/terminal.js +17 -1
- package/package.json +1 -1
package/docs/shell.js
CHANGED
|
@@ -53,18 +53,23 @@ function makeBuiltins(ctx) {
|
|
|
53
53
|
export: ([kv]) => { const [k, ...v] = (kv || '').split('='); ctx.env[k] = v.join('='); },
|
|
54
54
|
clear: () => ctx.term.clear(),
|
|
55
55
|
help: () => wl(Object.keys(makeBuiltins(ctx)).join(' ')),
|
|
56
|
-
exit: () => {
|
|
57
|
-
|
|
58
|
-
if (
|
|
56
|
+
exit: (actor) => {
|
|
57
|
+
const st = actor.getSnapshot().value;
|
|
58
|
+
if (st === 'node-repl') { actor.send({ type: 'EXIT_REPL' }); wl('[shell]'); }
|
|
59
|
+
},
|
|
60
|
+
node: async ([file], actor) => {
|
|
61
|
+
if (!file) { actor.send({ type: 'ENTER_REPL' }); wl('[node repl — type exit to return]'); return; }
|
|
59
62
|
const path = resolvePath(ctx.cwd, file);
|
|
60
63
|
const code = snap()[path];
|
|
61
64
|
if (code == null) throw new Error('no such file: ' + path);
|
|
65
|
+
actor.send({ type: 'NODE_START' });
|
|
62
66
|
await ctx.nodeEval(code, path);
|
|
63
67
|
},
|
|
64
|
-
npm: async args => {
|
|
68
|
+
npm: async (args, actor) => {
|
|
65
69
|
if (args[0] !== 'install') throw new Error('only npm install supported');
|
|
66
70
|
const pkg = args[1];
|
|
67
71
|
if (!pkg) throw new Error('npm install <pkg>');
|
|
72
|
+
actor.send({ type: 'NPM_START' });
|
|
68
73
|
w('fetching ' + pkg + '...\r\n');
|
|
69
74
|
const r = await fetch('https://esm.sh/' + pkg);
|
|
70
75
|
if (!r.ok) throw new Error('fetch failed: ' + r.status);
|
|
@@ -77,18 +82,29 @@ function makeBuiltins(ctx) {
|
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
const machine = createMachine({ id: 'shell', initial: 'idle', states: {
|
|
80
|
-
idle: { on: { RUN: 'executing' } },
|
|
85
|
+
idle: { on: { RUN: 'executing', ENTER_REPL: 'node-repl', NPM_START: 'npm-installing', NODE_START: 'node-running' } },
|
|
81
86
|
executing: { on: { DONE: 'idle', ERROR: 'idle' } },
|
|
87
|
+
'npm-installing': { on: { DONE: 'idle', ERROR: 'idle' } },
|
|
88
|
+
'node-running': { on: { DONE: 'idle', ERROR: 'idle' } },
|
|
89
|
+
'node-repl': { on: { EXIT_REPL: 'idle', RUN: 'node-repl' } },
|
|
82
90
|
}});
|
|
83
91
|
|
|
84
92
|
export function createShell({ term, onPreviewWrite }) {
|
|
85
|
-
const ctx = { term, cwd: '/', env: {},
|
|
93
|
+
const ctx = { term, cwd: '/', env: {}, history: [], httpHandlers: {} };
|
|
86
94
|
const BUILTINS = makeBuiltins(ctx);
|
|
87
95
|
ctx.nodeEval = createNodeEnv({ ctx, term });
|
|
88
96
|
|
|
89
97
|
const actor = createActor(machine);
|
|
90
98
|
actor.start();
|
|
91
99
|
|
|
100
|
+
let inputQueue = [];
|
|
101
|
+
|
|
102
|
+
function drainQueue(onData) {
|
|
103
|
+
const items = inputQueue.slice();
|
|
104
|
+
inputQueue = [];
|
|
105
|
+
for (const d of items) onData(d);
|
|
106
|
+
}
|
|
107
|
+
|
|
92
108
|
window.__debug = window.__debug || {};
|
|
93
109
|
window.__debug.shell = {
|
|
94
110
|
get state() { return actor.getSnapshot().value; },
|
|
@@ -96,69 +112,87 @@ export function createShell({ term, onPreviewWrite }) {
|
|
|
96
112
|
get env() { return ctx.env; },
|
|
97
113
|
get history() { return ctx.history; },
|
|
98
114
|
httpHandlers: ctx.httpHandlers,
|
|
99
|
-
get
|
|
115
|
+
get inputQueue() { return inputQueue.slice(); },
|
|
100
116
|
};
|
|
101
117
|
|
|
102
|
-
async function runCmd(line, capture) {
|
|
118
|
+
async function runCmd(line, capture, actor) {
|
|
103
119
|
if (!line.trim()) return '';
|
|
104
120
|
const [cmd, ...args] = line.trim().split(/\s+/);
|
|
105
121
|
const fn = BUILTINS[cmd];
|
|
106
122
|
if (!capture) {
|
|
107
|
-
if (fn) await fn(args); else term.write('command not found: ' + cmd + '\r\n');
|
|
123
|
+
if (fn) await fn(args, actor); else term.write('command not found: ' + cmd + '\r\n');
|
|
108
124
|
return '';
|
|
109
125
|
}
|
|
110
126
|
let out = '';
|
|
111
127
|
const orig = term.write.bind(term);
|
|
112
128
|
term.write = s => { out += s; };
|
|
113
|
-
try { if (fn) await fn(args); else out += 'command not found: ' + cmd + '\r\n'; }
|
|
129
|
+
try { if (fn) await fn(args, actor); else out += 'command not found: ' + cmd + '\r\n'; }
|
|
114
130
|
finally { term.write = orig; }
|
|
115
131
|
return out;
|
|
116
132
|
}
|
|
117
133
|
|
|
118
|
-
async function run(line) {
|
|
134
|
+
async function run(line, onData) {
|
|
119
135
|
if (!line.trim()) return;
|
|
120
136
|
ctx.history.push(line);
|
|
121
|
-
|
|
122
|
-
if (line.trim()
|
|
123
|
-
|
|
137
|
+
const st = actor.getSnapshot().value;
|
|
138
|
+
if (st === 'node-repl' && line.trim() !== 'exit') { await ctx.nodeEval(line); return; }
|
|
139
|
+
if (line.trim() === 'exit') { BUILTINS.exit(actor); return; }
|
|
140
|
+
const [cmd] = line.trim().split(/\s+/);
|
|
141
|
+
if (cmd !== 'npm' && cmd !== 'node') actor.send({ type: 'RUN' });
|
|
124
142
|
try {
|
|
125
143
|
const parts = line.split(' | ');
|
|
126
144
|
if (parts.length > 1) {
|
|
127
|
-
let buf = await runCmd(parts[0], true);
|
|
145
|
+
let buf = await runCmd(parts[0], true, actor);
|
|
128
146
|
for (const p of parts.slice(1)) {
|
|
129
|
-
const [
|
|
130
|
-
const fn = BUILTINS[
|
|
131
|
-
if (fn) await fn([buf, ...
|
|
147
|
+
const [c, ...a] = p.trim().split(/\s+/);
|
|
148
|
+
const fn = BUILTINS[c];
|
|
149
|
+
if (fn) await fn([buf, ...a], actor); else term.write('command not found: ' + c + '\r\n');
|
|
132
150
|
buf = '';
|
|
133
151
|
}
|
|
134
152
|
} else {
|
|
135
|
-
await runCmd(line, false);
|
|
153
|
+
await runCmd(line, false, actor);
|
|
136
154
|
}
|
|
137
155
|
actor.send({ type: 'DONE' });
|
|
156
|
+
drainQueue(onData);
|
|
138
157
|
} catch (e) {
|
|
139
158
|
term.write('\x1b[31m' + e.message + '\x1b[0m\r\n');
|
|
140
159
|
actor.send({ type: 'ERROR' });
|
|
160
|
+
drainQueue(onData);
|
|
141
161
|
}
|
|
142
162
|
}
|
|
143
163
|
|
|
144
164
|
const prompt = () => term.write('\r\n\x1b[32m' + ctx.cwd + ' $ \x1b[0m');
|
|
145
165
|
let buf = '';
|
|
146
|
-
|
|
166
|
+
|
|
167
|
+
function onData(data) {
|
|
168
|
+
if (data === '\x03') {
|
|
169
|
+
actor.send({ type: 'ERROR' });
|
|
170
|
+
inputQueue = [];
|
|
171
|
+
buf = '';
|
|
172
|
+
term.write('^C');
|
|
173
|
+
prompt();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const st = actor.getSnapshot().value;
|
|
177
|
+
if (st !== 'idle' && st !== 'node-repl') {
|
|
178
|
+
inputQueue.push(data);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
147
181
|
if (data === '\r') {
|
|
148
182
|
term.write('\r\n');
|
|
149
183
|
const line = buf;
|
|
150
184
|
buf = '';
|
|
151
|
-
|
|
152
|
-
prompt();
|
|
185
|
+
run(line, onData).then(() => prompt());
|
|
153
186
|
} else if (data === '\x7f') {
|
|
154
187
|
if (buf.length) { buf = buf.slice(0, -1); term.write('\x08 \x08'); }
|
|
155
188
|
} else {
|
|
156
189
|
buf += data;
|
|
157
190
|
term.write(data);
|
|
158
191
|
}
|
|
159
|
-
}
|
|
192
|
+
}
|
|
160
193
|
|
|
194
|
+
term.onData(onData);
|
|
161
195
|
onPreviewWrite && (window.__debug.shell.onPreviewWrite = onPreviewWrite);
|
|
162
196
|
prompt();
|
|
163
|
-
return { run };
|
|
197
|
+
return { run: line => run(line, onData) };
|
|
164
198
|
}
|
package/docs/terminal.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Terminal, FitAddon } from './vendor/xterm-bundle.js';
|
|
2
|
+
import { createMachine, createActor } from './vendor/xstate.js';
|
|
2
3
|
import { createShell } from './shell.js';
|
|
3
4
|
import { registerPreviewSW } from './preview-sw-client.js';
|
|
4
5
|
|
|
@@ -31,6 +32,13 @@ async function idbSave(data) {
|
|
|
31
32
|
});
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
const bootMachine = createMachine({ id: 'terminal', initial: 'loading-idb', states: {
|
|
36
|
+
'loading-idb': { on: { IDB_READY: 'registering-sw' } },
|
|
37
|
+
'registering-sw': { on: { SW_READY: 'ready', SW_ERROR: 'ready' } },
|
|
38
|
+
'ready': {},
|
|
39
|
+
'error': {},
|
|
40
|
+
}});
|
|
41
|
+
|
|
34
42
|
let reloadTimer = null;
|
|
35
43
|
function scheduleReload() {
|
|
36
44
|
clearTimeout(reloadTimer);
|
|
@@ -44,6 +52,12 @@ async function boot() {
|
|
|
44
52
|
const el = document.getElementById('term-container');
|
|
45
53
|
if (!el) return;
|
|
46
54
|
|
|
55
|
+
const bootActor = createActor(bootMachine);
|
|
56
|
+
bootActor.start();
|
|
57
|
+
|
|
58
|
+
window.__debug = window.__debug || {};
|
|
59
|
+
window.__debug.terminal = { get state() { return bootActor.getSnapshot().value; } };
|
|
60
|
+
|
|
47
61
|
const term = new Terminal({ theme: { background: '#000000' }, convertEol: true });
|
|
48
62
|
const fit = new FitAddon();
|
|
49
63
|
term.loadAddon(fit);
|
|
@@ -60,15 +74,17 @@ async function boot() {
|
|
|
60
74
|
files = await r.json();
|
|
61
75
|
}
|
|
62
76
|
|
|
63
|
-
window.__debug = window.__debug || {};
|
|
64
77
|
window.__debug.idbSnapshot = files;
|
|
65
78
|
window.__debug.idbPersist = () => idbSave(JSON.stringify(window.__debug.idbSnapshot));
|
|
66
79
|
window.__debug.term = term;
|
|
80
|
+
bootActor.send({ type: 'IDB_READY' });
|
|
67
81
|
|
|
68
82
|
try {
|
|
69
83
|
await registerPreviewSW();
|
|
84
|
+
bootActor.send({ type: 'SW_READY' });
|
|
70
85
|
} catch (e) {
|
|
71
86
|
term.write('\x1b[33mSW: ' + e.message + '\x1b[0m\r\n');
|
|
87
|
+
bootActor.send({ type: 'SW_ERROR' });
|
|
72
88
|
}
|
|
73
89
|
|
|
74
90
|
createShell({ term, onPreviewWrite: scheduleReload });
|
package/package.json
CHANGED