@tutorialkit-rb/runtime 1.5.2-rb.0.1.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.
Files changed (46) hide show
  1. package/README.md +18 -0
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.js +2 -0
  4. package/dist/lesson-files.d.ts +22 -0
  5. package/dist/lesson-files.js +126 -0
  6. package/dist/store/editor.d.ts +29 -0
  7. package/dist/store/editor.js +127 -0
  8. package/dist/store/index.d.ts +113 -0
  9. package/dist/store/index.js +316 -0
  10. package/dist/store/previews.d.ts +19 -0
  11. package/dist/store/previews.js +80 -0
  12. package/dist/store/terminal.d.ts +24 -0
  13. package/dist/store/terminal.js +128 -0
  14. package/dist/store/tutorial-runner.d.ts +147 -0
  15. package/dist/store/tutorial-runner.js +564 -0
  16. package/dist/tasks.d.ts +22 -0
  17. package/dist/tasks.js +26 -0
  18. package/dist/utils/multi-counter.d.ts +5 -0
  19. package/dist/utils/multi-counter.js +19 -0
  20. package/dist/utils/promises.d.ts +8 -0
  21. package/dist/utils/promises.js +29 -0
  22. package/dist/utils/support.d.ts +1 -0
  23. package/dist/utils/support.js +23 -0
  24. package/dist/utils/terminal.d.ts +17 -0
  25. package/dist/utils/terminal.js +13 -0
  26. package/dist/webcontainer/command.d.ts +28 -0
  27. package/dist/webcontainer/command.js +67 -0
  28. package/dist/webcontainer/editor-config.d.ts +12 -0
  29. package/dist/webcontainer/editor-config.js +60 -0
  30. package/dist/webcontainer/index.d.ts +4 -0
  31. package/dist/webcontainer/index.js +4 -0
  32. package/dist/webcontainer/on-demand-boot.d.ts +15 -0
  33. package/dist/webcontainer/on-demand-boot.js +39 -0
  34. package/dist/webcontainer/port-info.d.ts +6 -0
  35. package/dist/webcontainer/port-info.js +10 -0
  36. package/dist/webcontainer/preview-info.d.ts +21 -0
  37. package/dist/webcontainer/preview-info.js +56 -0
  38. package/dist/webcontainer/shell.d.ts +14 -0
  39. package/dist/webcontainer/shell.js +46 -0
  40. package/dist/webcontainer/steps.d.ts +15 -0
  41. package/dist/webcontainer/steps.js +38 -0
  42. package/dist/webcontainer/terminal-config.d.ts +59 -0
  43. package/dist/webcontainer/terminal-config.js +230 -0
  44. package/dist/webcontainer/utils/files.d.ts +10 -0
  45. package/dist/webcontainer/utils/files.js +76 -0
  46. package/package.json +53 -0
@@ -0,0 +1,230 @@
1
+ export class TerminalConfig {
2
+ _config;
3
+ constructor(config) {
4
+ const normalized = normalizeTerminalConfig(config);
5
+ this._config = normalized;
6
+ }
7
+ get panels() {
8
+ return this._config.panels;
9
+ }
10
+ get activePanel() {
11
+ return this._config.activePanel;
12
+ }
13
+ get defaultOpen() {
14
+ return this._config.defaultOpen;
15
+ }
16
+ }
17
+ const TERMINAL_PANEL_TITLES = {
18
+ output: 'Output',
19
+ terminal: 'Terminal',
20
+ };
21
+ let globalId = 0;
22
+ /**
23
+ * This class contains the state for a terminal panel. This is a panel which is attached to a process and renders
24
+ * the process output to a screen.
25
+ */
26
+ export class TerminalPanel {
27
+ type;
28
+ _options;
29
+ static panelCount = {
30
+ output: 0,
31
+ terminal: 0,
32
+ };
33
+ static resetCount() {
34
+ this.panelCount = {
35
+ output: 0,
36
+ terminal: 0,
37
+ };
38
+ }
39
+ id;
40
+ title;
41
+ _terminal;
42
+ _process;
43
+ _data = [];
44
+ _onData;
45
+ constructor(type, _options) {
46
+ this.type = type;
47
+ this._options = _options;
48
+ let title = _options?.title;
49
+ // automatically infer a title if no title is provided
50
+ if (!title) {
51
+ title = TERMINAL_PANEL_TITLES[type];
52
+ // we keep track of all untitled panel and add an index to the title
53
+ const count = TerminalPanel.panelCount[type];
54
+ if (count > 0) {
55
+ title += ` ${count}`;
56
+ }
57
+ TerminalPanel.panelCount[type]++;
58
+ }
59
+ this.title = title;
60
+ this.id = _options?.id ?? (type === 'output' ? 'output' : `${type}-${globalId++}`);
61
+ }
62
+ get terminal() {
63
+ return this._terminal;
64
+ }
65
+ get process() {
66
+ return this._process;
67
+ }
68
+ get processOptions() {
69
+ if (this.type === 'output') {
70
+ return undefined;
71
+ }
72
+ return {
73
+ allowRedirects: this._options?.allowRedirects ?? false,
74
+ allowCommands: this._options?.allowCommands,
75
+ };
76
+ }
77
+ // #region ITerminal methods
78
+ get cols() {
79
+ // we fallback to a default
80
+ return this._terminal?.cols;
81
+ }
82
+ get rows() {
83
+ return this._terminal?.rows;
84
+ }
85
+ reset() {
86
+ if (this._terminal) {
87
+ this._terminal.reset();
88
+ }
89
+ else {
90
+ this._data = [];
91
+ }
92
+ }
93
+ /** @internal*/
94
+ write(data) {
95
+ if (this._terminal) {
96
+ this._terminal.write(data);
97
+ }
98
+ else {
99
+ this._data.push({ data, type: 'echo' });
100
+ }
101
+ }
102
+ input(data) {
103
+ if (this.type !== 'terminal') {
104
+ throw new Error('Cannot write data to output-only terminal');
105
+ }
106
+ if (this._terminal) {
107
+ this._terminal.input(data);
108
+ }
109
+ else {
110
+ this._data.push({ data, type: 'input' });
111
+ }
112
+ }
113
+ onData(callback) {
114
+ if (this._terminal) {
115
+ this._terminal.onData(callback);
116
+ }
117
+ else {
118
+ this._onData = callback;
119
+ }
120
+ }
121
+ // #endregion
122
+ /**
123
+ * Attach a WebContainer process to this panel.
124
+ *
125
+ * @param process The WebContainer process
126
+ */
127
+ attachProcess(process) {
128
+ this._process = process;
129
+ if (this.cols != null && this.rows != null) {
130
+ this._process.resize({ cols: this.cols, rows: this.rows });
131
+ }
132
+ }
133
+ /**
134
+ * Attach a terminal to this panel.
135
+ *
136
+ * @param terminal The terminal.
137
+ */
138
+ attachTerminal(terminal) {
139
+ for (const { type, data } of this._data) {
140
+ if (type === 'echo') {
141
+ terminal.write(data);
142
+ }
143
+ else {
144
+ terminal.input(data);
145
+ }
146
+ }
147
+ this._data = [];
148
+ this._terminal = terminal;
149
+ if (this._onData) {
150
+ terminal.onData(this._onData);
151
+ }
152
+ if (this.cols != null && this.rows != null) {
153
+ this._process?.resize({ cols: this.cols, rows: this.rows });
154
+ }
155
+ }
156
+ }
157
+ /**
158
+ * Normalize the provided configuration to a configuration which is easier to parse.
159
+ *
160
+ * @param config The terminal configuration.
161
+ * @returns A normalized terminal configuration.
162
+ */
163
+ function normalizeTerminalConfig(config) {
164
+ let activePanel = 0;
165
+ if (config === false) {
166
+ // if the value is `false`, we don't render anything
167
+ return {
168
+ panels: [],
169
+ activePanel,
170
+ defaultOpen: false,
171
+ };
172
+ }
173
+ // reset the count so that the auto-infered titles are indexed properly
174
+ TerminalPanel.resetCount();
175
+ // if no config is set, or the value is `true`, we just render the output panel
176
+ if (config === undefined || config === true) {
177
+ return {
178
+ panels: [new TerminalPanel('output')],
179
+ activePanel,
180
+ defaultOpen: false,
181
+ };
182
+ }
183
+ const panels = [];
184
+ const options = {
185
+ allowRedirects: config.allowRedirects,
186
+ allowCommands: config.allowCommands,
187
+ };
188
+ if (config.panels) {
189
+ if (config.panels === 'output') {
190
+ panels.push(new TerminalPanel('output'));
191
+ }
192
+ else if (config.panels === 'terminal') {
193
+ panels.push(new TerminalPanel('terminal', options));
194
+ }
195
+ else if (Array.isArray(config.panels)) {
196
+ for (const panel of config.panels) {
197
+ let terminalPanel;
198
+ if (typeof panel === 'string') {
199
+ terminalPanel = new TerminalPanel(panel, options);
200
+ }
201
+ else if (Array.isArray(panel)) {
202
+ terminalPanel = new TerminalPanel(panel[0], {
203
+ title: panel[1],
204
+ ...options,
205
+ });
206
+ }
207
+ else {
208
+ terminalPanel = new TerminalPanel(panel.type, {
209
+ id: panel.id,
210
+ title: panel.title,
211
+ allowRedirects: panel.allowRedirects ?? config.allowRedirects,
212
+ allowCommands: panel.allowCommands ?? config.allowCommands,
213
+ });
214
+ }
215
+ panels.push(terminalPanel);
216
+ }
217
+ }
218
+ }
219
+ if (typeof config.activePanel === 'number') {
220
+ activePanel = config.activePanel;
221
+ if (activePanel >= panels.length) {
222
+ activePanel = 0;
223
+ }
224
+ }
225
+ return {
226
+ activePanel,
227
+ panels,
228
+ defaultOpen: config.open || false,
229
+ };
230
+ }
@@ -0,0 +1,10 @@
1
+ import type { Files } from '@tutorialkit-rb/types';
2
+ import type { FileSystemTree } from '@webcontainer/api';
3
+ export declare function areFilesEqual(a: Files, b: Files): boolean;
4
+ interface FilesDiff {
5
+ addedOrModified: Files;
6
+ removed: string[];
7
+ }
8
+ export declare function diffFiles(before: Files, after: Files): FilesDiff;
9
+ export declare function toFileTree(files: Files): FileSystemTree;
10
+ export {};
@@ -0,0 +1,76 @@
1
+ export function areFilesEqual(a, b) {
2
+ const aFilePaths = Object.keys(a);
3
+ const bFilePaths = Object.keys(b);
4
+ if (aFilePaths.length !== bFilePaths.length) {
5
+ return false;
6
+ }
7
+ aFilePaths.sort();
8
+ bFilePaths.sort();
9
+ for (let i = 0; i < aFilePaths.length; ++i) {
10
+ const filePath = aFilePaths[i];
11
+ if (bFilePaths[i] !== filePath) {
12
+ return false;
13
+ }
14
+ if (a[filePath] !== b[filePath]) {
15
+ return false;
16
+ }
17
+ }
18
+ return true;
19
+ }
20
+ export function diffFiles(before, after) {
21
+ const addedOrModified = {};
22
+ const removed = [];
23
+ for (const filePath in before) {
24
+ const beforeFile = before[filePath];
25
+ const afterFile = after[filePath];
26
+ if (typeof afterFile == 'undefined') {
27
+ removed.push(filePath);
28
+ }
29
+ else if (beforeFile !== afterFile) {
30
+ addedOrModified[filePath] = afterFile;
31
+ }
32
+ }
33
+ for (const filePath in after) {
34
+ if (!(filePath in before)) {
35
+ addedOrModified[filePath] = after[filePath];
36
+ }
37
+ }
38
+ return {
39
+ addedOrModified,
40
+ removed,
41
+ };
42
+ }
43
+ export function toFileTree(files) {
44
+ const root = {};
45
+ for (const filePath in files) {
46
+ const segments = filePath.split('/').filter((segment) => segment);
47
+ let currentTree = root;
48
+ for (let i = 0; i < segments.length; ++i) {
49
+ const name = segments[i];
50
+ if (i === segments.length - 1) {
51
+ currentTree[name] = {
52
+ file: {
53
+ contents: files[filePath],
54
+ },
55
+ };
56
+ }
57
+ else {
58
+ let folder = currentTree[name];
59
+ assertDirectoryNode(folder);
60
+ if (!folder) {
61
+ folder = {
62
+ directory: {},
63
+ };
64
+ currentTree[name] = folder;
65
+ }
66
+ currentTree = folder.directory;
67
+ }
68
+ }
69
+ }
70
+ return root;
71
+ }
72
+ function assertDirectoryNode(node) {
73
+ if (node && !('directory' in node)) {
74
+ throw new Error('Expected directory node');
75
+ }
76
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@tutorialkit-rb/runtime",
3
+ "version": "1.5.2-rb.0.1.0",
4
+ "description": "TutorialKit runtime",
5
+ "author": "StackBlitz Inc.",
6
+ "type": "module",
7
+ "bugs": "https://github.com/stackblitz/tutorialkit/issues",
8
+ "homepage": "https://github.com/stackblitz/tutorialkit",
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/Bakaface/tutorialkit.rb",
13
+ "directory": "packages/runtime"
14
+ },
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": "./dist/index.js",
18
+ "./tasks": "./dist/tasks.js"
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "typesVersions": {
24
+ "*": {
25
+ "tasks": [
26
+ "dist/tasks.d.ts"
27
+ ]
28
+ }
29
+ },
30
+ "scripts": {
31
+ "build": "tsc -b tsconfig.build.json",
32
+ "dev": "pnpm run build --watch --preserveWatchOutput",
33
+ "test": "vitest"
34
+ },
35
+ "dependencies": {
36
+ "@tutorialkit-rb/types": "1.5.2",
37
+ "@webcontainer/api": "1.5.1",
38
+ "nanostores": "^0.10.3",
39
+ "picomatch": "^4.0.2"
40
+ },
41
+ "devDependencies": {
42
+ "@types/picomatch": "^3.0.1",
43
+ "typescript": "^5.4.5",
44
+ "vite": "^5.3.1",
45
+ "vite-tsconfig-paths": "^4.3.2",
46
+ "vitest": "^3.0.5"
47
+ },
48
+ "keywords": [
49
+ "ruby",
50
+ "rails",
51
+ "wasm"
52
+ ]
53
+ }