@joinezco/codeblock 0.0.8 → 0.0.10
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/dist/editor.d.ts +30 -3
- package/dist/editor.js +416 -43
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/lsps/index.d.ts +5 -0
- package/dist/lsps/index.js +9 -2
- package/dist/lsps/typescript.d.ts +3 -1
- package/dist/lsps/typescript.js +8 -17
- package/dist/panels/settings.d.ts +22 -0
- package/dist/panels/settings.js +267 -0
- package/dist/panels/terminal.d.ts +3 -0
- package/dist/panels/terminal.js +76 -0
- package/dist/panels/toolbar.d.ts +53 -3
- package/dist/panels/toolbar.js +1336 -164
- package/dist/panels/toolbar.test.js +20 -14
- package/dist/rpc/transport.d.ts +2 -11
- package/dist/rpc/transport.js +19 -35
- package/dist/themes/index.js +226 -13
- package/dist/themes/vscode.js +3 -2
- package/dist/types.d.ts +5 -0
- package/dist/utils/fs.d.ts +22 -3
- package/dist/utils/fs.js +126 -21
- package/dist/utils/lsp.d.ts +26 -15
- package/dist/utils/lsp.js +79 -44
- package/dist/utils/search.d.ts +2 -0
- package/dist/utils/search.js +13 -4
- package/dist/utils/typescript-defaults.d.ts +57 -0
- package/dist/utils/typescript-defaults.js +208 -0
- package/dist/utils/typescript-defaults.test.d.ts +1 -0
- package/dist/utils/typescript-defaults.test.js +197 -0
- package/dist/workers/fs.worker.d.ts +4 -8
- package/dist/workers/fs.worker.js +30 -60
- package/dist/workers/javascript.worker.js +11 -9
- package/package.json +8 -4
- package/dist/assets/clike-C8IJ2oj_.js +0 -1
- package/dist/assets/cmake-BQqOBYOt.js +0 -1
- package/dist/assets/dockerfile-C_y-rIpk.js +0 -1
- package/dist/assets/fs.worker-BwEqZcql.ts +0 -109
- package/dist/assets/go-CTD25R5P.js +0 -1
- package/dist/assets/haskell-BWDZoCOh.js +0 -1
- package/dist/assets/index-9HdhmM_Y.js +0 -1
- package/dist/assets/index-C-QhPFHP.js +0 -3
- package/dist/assets/index-C3BnE2cG.js +0 -222
- package/dist/assets/index-CGx5MZO7.js +0 -6
- package/dist/assets/index-CIuq3uTk.js +0 -1
- package/dist/assets/index-CXFONXS8.js +0 -1
- package/dist/assets/index-D5Z27j1C.js +0 -1
- package/dist/assets/index-DWOBdRjn.js +0 -1
- package/dist/assets/index-Dvu-FFzd.js +0 -1
- package/dist/assets/index-Dx_VuNNd.js +0 -1
- package/dist/assets/index-I0dlv-r3.js +0 -1
- package/dist/assets/index-MGle_v2x.js +0 -1
- package/dist/assets/index-N-GE7HTU.js +0 -1
- package/dist/assets/index-aEsF5o-7.js +0 -2
- package/dist/assets/index-as7ELo0J.js +0 -1
- package/dist/assets/index-gUUzXNuP.js +0 -1
- package/dist/assets/index-pGm0qkrJ.js +0 -13
- package/dist/assets/javascript.worker-C1zGArKk.js +0 -527
- package/dist/assets/lua-BgMRiT3U.js +0 -1
- package/dist/assets/perl-CdXCOZ3F.js +0 -1
- package/dist/assets/process-Dw9K5EnD.js +0 -1357
- package/dist/assets/properties-C78fOPTZ.js +0 -1
- package/dist/assets/ruby-B2Rjki9n.js +0 -1
- package/dist/assets/shell-CjFT_Tl9.js +0 -1
- package/dist/assets/swift-BzpIVaGY.js +0 -1
- package/dist/assets/toml-BXUEaScT.js +0 -1
- package/dist/assets/vb-CmGdzxic.js +0 -1
- package/dist/e2e/example.spec.d.ts +0 -5
- package/dist/e2e/example.spec.js +0 -44
- package/dist/index.html +0 -16
- package/dist/resources/config.json +0 -13
- package/dist/snapshot.bin +0 -0
- package/dist/styles.css +0 -7
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
const LIB_DIR = '/node_modules/typescript/lib';
|
|
2
|
+
const TSCONFIG_PATH = '/tsconfig.json';
|
|
3
|
+
/**
|
|
4
|
+
* Maps ES target names to the list of TypeScript lib files they require.
|
|
5
|
+
* Each target includes all libs from previous targets plus its own additions.
|
|
6
|
+
* Derived from the `/// <reference lib="..." />` chains in TypeScript's lib files.
|
|
7
|
+
*/
|
|
8
|
+
const TARGET_LIBS = {
|
|
9
|
+
es5: [
|
|
10
|
+
'es5',
|
|
11
|
+
'decorators',
|
|
12
|
+
'decorators.legacy',
|
|
13
|
+
],
|
|
14
|
+
es2015: [
|
|
15
|
+
'es5', 'decorators', 'decorators.legacy',
|
|
16
|
+
'es2015',
|
|
17
|
+
'es2015.core', 'es2015.collection', 'es2015.iterable',
|
|
18
|
+
'es2015.generator', 'es2015.promise', 'es2015.proxy',
|
|
19
|
+
'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown',
|
|
20
|
+
],
|
|
21
|
+
es2016: [
|
|
22
|
+
'es5', 'decorators', 'decorators.legacy',
|
|
23
|
+
'es2015',
|
|
24
|
+
'es2015.core', 'es2015.collection', 'es2015.iterable',
|
|
25
|
+
'es2015.generator', 'es2015.promise', 'es2015.proxy',
|
|
26
|
+
'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown',
|
|
27
|
+
'es2016',
|
|
28
|
+
'es2016.array.include', 'es2016.intl',
|
|
29
|
+
],
|
|
30
|
+
es2017: [
|
|
31
|
+
'es5', 'decorators', 'decorators.legacy',
|
|
32
|
+
'es2015',
|
|
33
|
+
'es2015.core', 'es2015.collection', 'es2015.iterable',
|
|
34
|
+
'es2015.generator', 'es2015.promise', 'es2015.proxy',
|
|
35
|
+
'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown',
|
|
36
|
+
'es2016',
|
|
37
|
+
'es2016.array.include', 'es2016.intl',
|
|
38
|
+
'es2017',
|
|
39
|
+
'es2017.arraybuffer', 'es2017.date', 'es2017.intl',
|
|
40
|
+
'es2017.object', 'es2017.sharedmemory', 'es2017.string',
|
|
41
|
+
'es2017.typedarrays',
|
|
42
|
+
],
|
|
43
|
+
es2018: [
|
|
44
|
+
'es5', 'decorators', 'decorators.legacy',
|
|
45
|
+
'es2015',
|
|
46
|
+
'es2015.core', 'es2015.collection', 'es2015.iterable',
|
|
47
|
+
'es2015.generator', 'es2015.promise', 'es2015.proxy',
|
|
48
|
+
'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown',
|
|
49
|
+
'es2016',
|
|
50
|
+
'es2016.array.include', 'es2016.intl',
|
|
51
|
+
'es2017',
|
|
52
|
+
'es2017.arraybuffer', 'es2017.date', 'es2017.intl',
|
|
53
|
+
'es2017.object', 'es2017.sharedmemory', 'es2017.string',
|
|
54
|
+
'es2017.typedarrays',
|
|
55
|
+
'es2018',
|
|
56
|
+
'es2018.asynciterable', 'es2018.asyncgenerator',
|
|
57
|
+
'es2018.promise', 'es2018.regexp', 'es2018.intl',
|
|
58
|
+
],
|
|
59
|
+
es2019: [
|
|
60
|
+
'es5', 'decorators', 'decorators.legacy',
|
|
61
|
+
'es2015',
|
|
62
|
+
'es2015.core', 'es2015.collection', 'es2015.iterable',
|
|
63
|
+
'es2015.generator', 'es2015.promise', 'es2015.proxy',
|
|
64
|
+
'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown',
|
|
65
|
+
'es2016',
|
|
66
|
+
'es2016.array.include', 'es2016.intl',
|
|
67
|
+
'es2017',
|
|
68
|
+
'es2017.arraybuffer', 'es2017.date', 'es2017.intl',
|
|
69
|
+
'es2017.object', 'es2017.sharedmemory', 'es2017.string',
|
|
70
|
+
'es2017.typedarrays',
|
|
71
|
+
'es2018',
|
|
72
|
+
'es2018.asynciterable', 'es2018.asyncgenerator',
|
|
73
|
+
'es2018.promise', 'es2018.regexp', 'es2018.intl',
|
|
74
|
+
'es2019',
|
|
75
|
+
'es2019.array', 'es2019.object', 'es2019.string',
|
|
76
|
+
'es2019.symbol', 'es2019.intl',
|
|
77
|
+
],
|
|
78
|
+
es2020: [
|
|
79
|
+
'es5', 'decorators', 'decorators.legacy',
|
|
80
|
+
'es2015',
|
|
81
|
+
'es2015.core', 'es2015.collection', 'es2015.iterable',
|
|
82
|
+
'es2015.generator', 'es2015.promise', 'es2015.proxy',
|
|
83
|
+
'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown',
|
|
84
|
+
'es2016',
|
|
85
|
+
'es2016.array.include', 'es2016.intl',
|
|
86
|
+
'es2017',
|
|
87
|
+
'es2017.arraybuffer', 'es2017.date', 'es2017.intl',
|
|
88
|
+
'es2017.object', 'es2017.sharedmemory', 'es2017.string',
|
|
89
|
+
'es2017.typedarrays',
|
|
90
|
+
'es2018',
|
|
91
|
+
'es2018.asynciterable', 'es2018.asyncgenerator',
|
|
92
|
+
'es2018.promise', 'es2018.regexp', 'es2018.intl',
|
|
93
|
+
'es2019',
|
|
94
|
+
'es2019.array', 'es2019.object', 'es2019.string',
|
|
95
|
+
'es2019.symbol', 'es2019.intl',
|
|
96
|
+
'es2020',
|
|
97
|
+
'es2020.bigint', 'es2020.date', 'es2020.number',
|
|
98
|
+
'es2020.promise', 'es2020.sharedmemory', 'es2020.string',
|
|
99
|
+
'es2020.symbol.wellknown', 'es2020.intl',
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Environment lib files included by TypeScript's `.full` variants (e.g. lib.es2020.full.d.ts).
|
|
104
|
+
* TypeScript includes these by default when no explicit `lib` is specified in tsconfig.
|
|
105
|
+
* We list them explicitly to match VS Code's default behavior.
|
|
106
|
+
*/
|
|
107
|
+
const DEFAULT_ENV_LIBS = ['dom', 'dom.iterable', 'dom.asynciterable', 'webworker.importscripts', 'scripthost'];
|
|
108
|
+
/**
|
|
109
|
+
* Returns the list of TypeScript lib file names required for a given ES target,
|
|
110
|
+
* plus any additional libs (DOM by default).
|
|
111
|
+
* Names are without the `lib.` prefix and `.d.ts` suffix (e.g. "es5", "dom").
|
|
112
|
+
*/
|
|
113
|
+
export function getRequiredLibs(target = 'es2020', additionalLibs = DEFAULT_ENV_LIBS) {
|
|
114
|
+
const targetLibs = TARGET_LIBS[target.toLowerCase()] || TARGET_LIBS.es2020;
|
|
115
|
+
return [...targetLibs, ...additionalLibs];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Returns all individual lib names for the tsconfig `lib` field.
|
|
119
|
+
*
|
|
120
|
+
* Lists every individual lib file (e.g. "es5", "es2015.promise", "dom") instead of
|
|
121
|
+
* just the top-level entry (e.g. "ES2020"). This is critical for browser-based
|
|
122
|
+
* TypeScript via Volar: the virtual filesystem is async, so each
|
|
123
|
+
* `/// <reference lib="..." />` chain level requires a separate async round-trip.
|
|
124
|
+
* By listing all libs explicitly, TypeScript loads them all in a single pass.
|
|
125
|
+
*/
|
|
126
|
+
export function getLibFieldForTarget(target = 'es2020', additionalLibs = DEFAULT_ENV_LIBS) {
|
|
127
|
+
const t = target.toLowerCase();
|
|
128
|
+
const targetLibs = TARGET_LIBS[t] || TARGET_LIBS.es2020;
|
|
129
|
+
return [...targetLibs, ...additionalLibs];
|
|
130
|
+
}
|
|
131
|
+
let prefilled = false;
|
|
132
|
+
let cachedLibFiles;
|
|
133
|
+
/**
|
|
134
|
+
* Returns the cached lib file contents from the last prefill, if available.
|
|
135
|
+
* These are keyed by full path (e.g. "/node_modules/typescript/lib/lib.es5.d.ts").
|
|
136
|
+
* Includes the tsconfig.json content as well.
|
|
137
|
+
*/
|
|
138
|
+
export function getCachedLibFiles() {
|
|
139
|
+
return cachedLibFiles;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Pre-fills the virtual filesystem with TypeScript default lib definitions and tsconfig.
|
|
143
|
+
* Writes to `/node_modules/typescript/lib/` where Volar's TypeScript language server
|
|
144
|
+
* expects to find them in a browser environment.
|
|
145
|
+
*
|
|
146
|
+
* - Skips files that already exist on the filesystem
|
|
147
|
+
* - Only runs once per session (subsequent calls are no-ops)
|
|
148
|
+
* - Should be called lazily when a TypeScript file is first opened
|
|
149
|
+
*
|
|
150
|
+
* Returns a map of file paths to their contents for direct use by the LSP worker,
|
|
151
|
+
* bypassing the need for the worker to read through nested Comlink proxies.
|
|
152
|
+
*
|
|
153
|
+
* @param fs - Virtual filesystem to write to
|
|
154
|
+
* @param resolveLib - Function that resolves a lib name to its `.d.ts` content.
|
|
155
|
+
* Receives names like "es5", "es2015.collection".
|
|
156
|
+
* In Vite, use `import.meta.glob('typescript/lib/*.d.ts', { query: '?raw' })`.
|
|
157
|
+
* @param config - Optional target and tsconfig overrides
|
|
158
|
+
*/
|
|
159
|
+
export async function prefillTypescriptDefaults(fs, resolveLib, config = {}) {
|
|
160
|
+
if (prefilled) {
|
|
161
|
+
return cachedLibFiles || {};
|
|
162
|
+
}
|
|
163
|
+
prefilled = true;
|
|
164
|
+
const target = config.target || 'ES2020';
|
|
165
|
+
const additionalLibs = config.additionalLibs ?? DEFAULT_ENV_LIBS;
|
|
166
|
+
const fileContents = {};
|
|
167
|
+
// Write tsconfig.json if it doesn't exist
|
|
168
|
+
const tsconfigExists = await fs.exists(TSCONFIG_PATH);
|
|
169
|
+
const tsconfigContent = JSON.stringify({
|
|
170
|
+
compilerOptions: {
|
|
171
|
+
target,
|
|
172
|
+
lib: getLibFieldForTarget(target, additionalLibs),
|
|
173
|
+
module: "ESNext",
|
|
174
|
+
moduleResolution: "bundler",
|
|
175
|
+
strict: true,
|
|
176
|
+
skipLibCheck: true,
|
|
177
|
+
...config.compilerOptions,
|
|
178
|
+
}
|
|
179
|
+
}, null, 2);
|
|
180
|
+
fileContents[TSCONFIG_PATH] = tsconfigContent;
|
|
181
|
+
if (!tsconfigExists) {
|
|
182
|
+
await fs.writeFile(TSCONFIG_PATH, tsconfigContent);
|
|
183
|
+
}
|
|
184
|
+
// Write lib files to the path Volar expects in browser environments
|
|
185
|
+
const libs = getRequiredLibs(target, additionalLibs);
|
|
186
|
+
await fs.mkdir(LIB_DIR, { recursive: true });
|
|
187
|
+
await Promise.all(libs.map(async (name) => {
|
|
188
|
+
const path = `${LIB_DIR}/lib.${name}.d.ts`;
|
|
189
|
+
try {
|
|
190
|
+
const content = await resolveLib(name);
|
|
191
|
+
fileContents[path] = content;
|
|
192
|
+
if (!(await fs.exists(path))) {
|
|
193
|
+
await fs.writeFile(path, content);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
console.error(`Failed to load TypeScript lib: ${name}`, e);
|
|
198
|
+
}
|
|
199
|
+
}));
|
|
200
|
+
cachedLibFiles = fileContents;
|
|
201
|
+
return fileContents;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Resets the prefilled state. Primarily useful for testing.
|
|
205
|
+
*/
|
|
206
|
+
export function resetPrefillState() {
|
|
207
|
+
prefilled = false;
|
|
208
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { getRequiredLibs, getLibFieldForTarget, prefillTypescriptDefaults, resetPrefillState } from './typescript-defaults';
|
|
3
|
+
function createMockFs() {
|
|
4
|
+
const files = new Map();
|
|
5
|
+
return {
|
|
6
|
+
readFile: vi.fn(async (path) => {
|
|
7
|
+
if (!files.has(path))
|
|
8
|
+
throw new Error(`ENOENT: ${path}`);
|
|
9
|
+
return files.get(path);
|
|
10
|
+
}),
|
|
11
|
+
writeFile: vi.fn(async (path, data) => {
|
|
12
|
+
files.set(path, data);
|
|
13
|
+
}),
|
|
14
|
+
exists: vi.fn(async (path) => files.has(path)),
|
|
15
|
+
mkdir: vi.fn(async () => { }),
|
|
16
|
+
readDir: vi.fn(async () => []),
|
|
17
|
+
stat: vi.fn(async () => null),
|
|
18
|
+
watch: vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
describe('getRequiredLibs', () => {
|
|
22
|
+
it('returns ES5 libs for es5 target', () => {
|
|
23
|
+
const libs = getRequiredLibs('es5');
|
|
24
|
+
expect(libs).toContain('es5');
|
|
25
|
+
expect(libs).toContain('decorators');
|
|
26
|
+
expect(libs).toContain('decorators.legacy');
|
|
27
|
+
expect(libs).not.toContain('es2015');
|
|
28
|
+
});
|
|
29
|
+
it('returns ES2015 libs including ES5 for es2015 target', () => {
|
|
30
|
+
const libs = getRequiredLibs('es2015');
|
|
31
|
+
expect(libs).toContain('es5');
|
|
32
|
+
expect(libs).toContain('es2015');
|
|
33
|
+
expect(libs).toContain('es2015.promise');
|
|
34
|
+
expect(libs).toContain('es2015.collection');
|
|
35
|
+
expect(libs).not.toContain('es2016');
|
|
36
|
+
});
|
|
37
|
+
it('returns ES2020 libs for es2020 target', () => {
|
|
38
|
+
const libs = getRequiredLibs('es2020');
|
|
39
|
+
expect(libs).toContain('es5');
|
|
40
|
+
expect(libs).toContain('es2015');
|
|
41
|
+
expect(libs).toContain('es2020');
|
|
42
|
+
expect(libs).toContain('es2020.bigint');
|
|
43
|
+
expect(libs).toContain('es2020.promise');
|
|
44
|
+
});
|
|
45
|
+
it('is case-insensitive', () => {
|
|
46
|
+
expect(getRequiredLibs('ES2020')).toEqual(getRequiredLibs('es2020'));
|
|
47
|
+
});
|
|
48
|
+
it('defaults to ES2020 for unknown targets', () => {
|
|
49
|
+
expect(getRequiredLibs('unknown')).toEqual(getRequiredLibs('es2020'));
|
|
50
|
+
});
|
|
51
|
+
it('defaults to ES2020 when no target is provided', () => {
|
|
52
|
+
expect(getRequiredLibs()).toEqual(getRequiredLibs('es2020'));
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('getLibFieldForTarget', () => {
|
|
56
|
+
it('returns all individual lib names for a target', () => {
|
|
57
|
+
const libs = getLibFieldForTarget('es2020');
|
|
58
|
+
expect(libs).toContain('es5');
|
|
59
|
+
expect(libs).toContain('es2015');
|
|
60
|
+
expect(libs).toContain('es2015.promise');
|
|
61
|
+
expect(libs).toContain('es2020');
|
|
62
|
+
expect(libs).toContain('es2020.bigint');
|
|
63
|
+
expect(libs).toEqual(getRequiredLibs('es2020'));
|
|
64
|
+
});
|
|
65
|
+
it('returns es2015 libs for es2015 target', () => {
|
|
66
|
+
const libs = getLibFieldForTarget('ES2015');
|
|
67
|
+
expect(libs).toContain('es5');
|
|
68
|
+
expect(libs).toContain('es2015');
|
|
69
|
+
expect(libs).not.toContain('es2016');
|
|
70
|
+
});
|
|
71
|
+
it('defaults to es2020 libs for unknown targets', () => {
|
|
72
|
+
expect(getLibFieldForTarget('unknown')).toEqual(getRequiredLibs('es2020'));
|
|
73
|
+
});
|
|
74
|
+
it('defaults to es2020 libs when no target is provided', () => {
|
|
75
|
+
expect(getLibFieldForTarget()).toEqual(getRequiredLibs('es2020'));
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('prefillTypescriptDefaults', () => {
|
|
79
|
+
let mockFs;
|
|
80
|
+
const mockResolveLib = vi.fn(async (name) => `// lib.${name}.d.ts content`);
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
mockFs = createMockFs();
|
|
83
|
+
mockResolveLib.mockClear();
|
|
84
|
+
resetPrefillState();
|
|
85
|
+
});
|
|
86
|
+
it('writes tsconfig.json with default settings', async () => {
|
|
87
|
+
await prefillTypescriptDefaults(mockFs, mockResolveLib);
|
|
88
|
+
expect(mockFs.writeFile).toHaveBeenCalledWith('/tsconfig.json', expect.stringContaining('"target"'));
|
|
89
|
+
const tsconfigCall = vi.mocked(mockFs.writeFile).mock.calls.find(([path]) => path === '/tsconfig.json');
|
|
90
|
+
const tsconfig = JSON.parse(tsconfigCall[1]);
|
|
91
|
+
expect(tsconfig.compilerOptions.target).toBe('ES2020');
|
|
92
|
+
expect(tsconfig.compilerOptions.lib).toEqual(getRequiredLibs('es2020'));
|
|
93
|
+
expect(tsconfig.compilerOptions.module).toBe('ESNext');
|
|
94
|
+
expect(tsconfig.compilerOptions.strict).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
it('does not overwrite existing tsconfig.json', async () => {
|
|
97
|
+
// Pre-create tsconfig.json
|
|
98
|
+
await mockFs.writeFile('/tsconfig.json', '{"existing": true}');
|
|
99
|
+
vi.mocked(mockFs.writeFile).mockClear();
|
|
100
|
+
await prefillTypescriptDefaults(mockFs, mockResolveLib);
|
|
101
|
+
const tsconfigWrites = vi.mocked(mockFs.writeFile).mock.calls.filter(([path]) => path === '/tsconfig.json');
|
|
102
|
+
expect(tsconfigWrites).toHaveLength(0);
|
|
103
|
+
});
|
|
104
|
+
it('merges custom compilerOptions into tsconfig', async () => {
|
|
105
|
+
await prefillTypescriptDefaults(mockFs, mockResolveLib, {
|
|
106
|
+
compilerOptions: { jsx: 'react-jsx', strict: false },
|
|
107
|
+
});
|
|
108
|
+
const tsconfigCall = vi.mocked(mockFs.writeFile).mock.calls.find(([path]) => path === '/tsconfig.json');
|
|
109
|
+
const tsconfig = JSON.parse(tsconfigCall[1]);
|
|
110
|
+
expect(tsconfig.compilerOptions.jsx).toBe('react-jsx');
|
|
111
|
+
expect(tsconfig.compilerOptions.strict).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
it('writes TypeScript lib files to /node_modules/typescript/lib/', async () => {
|
|
114
|
+
await prefillTypescriptDefaults(mockFs, mockResolveLib, { target: 'es5' });
|
|
115
|
+
expect(mockFs.mkdir).toHaveBeenCalledWith('/node_modules/typescript/lib', { recursive: true });
|
|
116
|
+
// ES5 needs 3 target libs + 5 default env libs (dom, dom.iterable, etc.)
|
|
117
|
+
const libWrites = vi.mocked(mockFs.writeFile).mock.calls.filter(([path]) => path.startsWith('/node_modules/typescript/lib/'));
|
|
118
|
+
expect(libWrites).toHaveLength(8);
|
|
119
|
+
expect(libWrites.map(([path]) => path)).toEqual(expect.arrayContaining([
|
|
120
|
+
'/node_modules/typescript/lib/lib.es5.d.ts',
|
|
121
|
+
'/node_modules/typescript/lib/lib.decorators.d.ts',
|
|
122
|
+
'/node_modules/typescript/lib/lib.decorators.legacy.d.ts',
|
|
123
|
+
'/node_modules/typescript/lib/lib.dom.d.ts',
|
|
124
|
+
'/node_modules/typescript/lib/lib.dom.iterable.d.ts',
|
|
125
|
+
'/node_modules/typescript/lib/lib.dom.asynciterable.d.ts',
|
|
126
|
+
'/node_modules/typescript/lib/lib.webworker.importscripts.d.ts',
|
|
127
|
+
'/node_modules/typescript/lib/lib.scripthost.d.ts',
|
|
128
|
+
]));
|
|
129
|
+
});
|
|
130
|
+
it('calls resolveLib for each lib file', async () => {
|
|
131
|
+
await prefillTypescriptDefaults(mockFs, mockResolveLib, { target: 'es5' });
|
|
132
|
+
expect(mockResolveLib).toHaveBeenCalledWith('es5');
|
|
133
|
+
expect(mockResolveLib).toHaveBeenCalledWith('decorators');
|
|
134
|
+
expect(mockResolveLib).toHaveBeenCalledWith('decorators.legacy');
|
|
135
|
+
expect(mockResolveLib).toHaveBeenCalledWith('dom');
|
|
136
|
+
expect(mockResolveLib).toHaveBeenCalledWith('dom.iterable');
|
|
137
|
+
expect(mockResolveLib).toHaveBeenCalledWith('dom.asynciterable');
|
|
138
|
+
expect(mockResolveLib).toHaveBeenCalledWith('webworker.importscripts');
|
|
139
|
+
expect(mockResolveLib).toHaveBeenCalledWith('scripthost');
|
|
140
|
+
});
|
|
141
|
+
it('does not overwrite existing lib files', async () => {
|
|
142
|
+
// Pre-create one lib file
|
|
143
|
+
await mockFs.writeFile('/node_modules/typescript/lib/lib.es5.d.ts', '// existing');
|
|
144
|
+
vi.mocked(mockFs.writeFile).mockClear();
|
|
145
|
+
mockResolveLib.mockClear();
|
|
146
|
+
await prefillTypescriptDefaults(mockFs, mockResolveLib, { target: 'es5' });
|
|
147
|
+
// resolveLib is called for all libs (to populate the return cache)
|
|
148
|
+
expect(mockResolveLib).toHaveBeenCalledWith('es5');
|
|
149
|
+
// But the existing file should NOT be overwritten in the VFS
|
|
150
|
+
const es5Writes = vi.mocked(mockFs.writeFile).mock.calls.filter(([path]) => path === '/node_modules/typescript/lib/lib.es5.d.ts');
|
|
151
|
+
expect(es5Writes).toHaveLength(0);
|
|
152
|
+
// Other libs should still be written
|
|
153
|
+
expect(mockResolveLib).toHaveBeenCalledWith('decorators');
|
|
154
|
+
expect(mockResolveLib).toHaveBeenCalledWith('decorators.legacy');
|
|
155
|
+
});
|
|
156
|
+
it('only runs once per session', async () => {
|
|
157
|
+
const result1 = await prefillTypescriptDefaults(mockFs, mockResolveLib, { target: 'es5' });
|
|
158
|
+
const firstCallCount = mockResolveLib.mock.calls.length;
|
|
159
|
+
const result2 = await prefillTypescriptDefaults(mockFs, mockResolveLib, { target: 'es5' });
|
|
160
|
+
// Should not have been called again
|
|
161
|
+
expect(mockResolveLib).toHaveBeenCalledTimes(firstCallCount);
|
|
162
|
+
// Second call returns cached result
|
|
163
|
+
expect(result2).toEqual(result1);
|
|
164
|
+
});
|
|
165
|
+
it('returns lib file contents keyed by path', async () => {
|
|
166
|
+
const result = await prefillTypescriptDefaults(mockFs, mockResolveLib, { target: 'es5' });
|
|
167
|
+
expect(result['/tsconfig.json']).toBeDefined();
|
|
168
|
+
expect(result['/node_modules/typescript/lib/lib.es5.d.ts']).toBe('// lib.es5.d.ts content');
|
|
169
|
+
expect(result['/node_modules/typescript/lib/lib.decorators.d.ts']).toBe('// lib.decorators.d.ts content');
|
|
170
|
+
expect(result['/node_modules/typescript/lib/lib.decorators.legacy.d.ts']).toBe('// lib.decorators.legacy.d.ts content');
|
|
171
|
+
expect(result['/node_modules/typescript/lib/lib.dom.d.ts']).toBe('// lib.dom.d.ts content');
|
|
172
|
+
expect(result['/node_modules/typescript/lib/lib.dom.iterable.d.ts']).toBe('// lib.dom.iterable.d.ts content');
|
|
173
|
+
});
|
|
174
|
+
it('handles resolveLib errors gracefully', async () => {
|
|
175
|
+
const errorResolve = vi.fn(async (name) => {
|
|
176
|
+
if (name === 'decorators')
|
|
177
|
+
throw new Error('Network error');
|
|
178
|
+
return `// lib.${name}.d.ts`;
|
|
179
|
+
});
|
|
180
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
181
|
+
await prefillTypescriptDefaults(mockFs, errorResolve, { target: 'es5' });
|
|
182
|
+
// Should have logged the error
|
|
183
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('decorators'), expect.any(Error));
|
|
184
|
+
// Other libs should still have been written
|
|
185
|
+
const es5Write = vi.mocked(mockFs.writeFile).mock.calls.find(([path]) => path === '/node_modules/typescript/lib/lib.es5.d.ts');
|
|
186
|
+
expect(es5Write).toBeTruthy();
|
|
187
|
+
consoleSpy.mockRestore();
|
|
188
|
+
});
|
|
189
|
+
it('uses custom target when specified', async () => {
|
|
190
|
+
await prefillTypescriptDefaults(mockFs, mockResolveLib, { target: 'ES2015' });
|
|
191
|
+
// Should include ES2015-specific libs
|
|
192
|
+
expect(mockResolveLib).toHaveBeenCalledWith('es2015.promise');
|
|
193
|
+
expect(mockResolveLib).toHaveBeenCalledWith('es2015.collection');
|
|
194
|
+
// Should not include ES2016+ libs
|
|
195
|
+
expect(mockResolveLib).not.toHaveBeenCalledWith('es2016');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Optimized mount function that loads snapshots directly from URLs.
|
|
5
|
-
* This is much more efficient for large snapshots as it avoids transferring
|
|
6
|
-
* data through the main thread.
|
|
7
|
-
*/
|
|
1
|
+
import * as Comlink from "comlink";
|
|
2
|
+
import { MountArgs } from "../types";
|
|
3
|
+
export declare const mount: ({ buffer, mountPoint }: MountArgs) => Promise<import("..").VfsInterface & Comlink.ProxyMarked>;
|
|
8
4
|
export declare const mountFromUrl: ({ url, mountPoint }: {
|
|
9
5
|
url: string;
|
|
10
6
|
mountPoint?: string;
|
|
11
|
-
}) => Promise<
|
|
7
|
+
}) => Promise<import("..").VfsInterface & Comlink.ProxyMarked>;
|
|
@@ -1,93 +1,63 @@
|
|
|
1
1
|
import * as Comlink from "comlink";
|
|
2
2
|
import { watchOptionsTransferHandler, asyncGeneratorTransferHandler } from '../rpc/serde';
|
|
3
3
|
import { Snapshot } from "../utils";
|
|
4
|
+
import { fs } from '@joinezco/memfs';
|
|
5
|
+
import { Vfs } from "../utils/fs";
|
|
4
6
|
Comlink.transferHandlers.set('asyncGenerator', asyncGeneratorTransferHandler);
|
|
5
7
|
Comlink.transferHandlers.set('watchOptions', watchOptionsTransferHandler);
|
|
6
8
|
let filesystems = [];
|
|
9
|
+
// Create VfsInterface directly in the worker so all memfs calls are local (no nested Comlink proxies)
|
|
10
|
+
function createWorkerVfs() {
|
|
11
|
+
return Vfs.fromMemfs(fs);
|
|
12
|
+
}
|
|
7
13
|
export const mount = async ({ buffer, mountPoint = '/' }) => {
|
|
8
|
-
let filesystem;
|
|
9
14
|
try {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
: uint8.buffer.slice(uint8.byteOffset, uint8.byteOffset + uint8.byteLength);
|
|
21
|
-
console.log('Aligned ArrayBuffer:', aligned);
|
|
22
|
-
await Snapshot.mount(new Uint8Array(aligned), {
|
|
23
|
-
// @ts-ignore
|
|
24
|
-
fs,
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
console.log('Getting storage directory...');
|
|
29
|
-
// const handle = await navigator.storage.getDirectory();
|
|
30
|
-
console.log('Got storage directory');
|
|
31
|
-
console.log('Attempting to remove directory...');
|
|
32
|
-
try {
|
|
33
|
-
// TODO: clear storage button
|
|
34
|
-
// @ts-ignore
|
|
35
|
-
// await handle.remove({ recursive: true });
|
|
36
|
-
console.log('Successfully removed directory');
|
|
37
|
-
}
|
|
38
|
-
catch (removeErr) {
|
|
39
|
-
console.error('Error removing directory:', removeErr);
|
|
40
|
-
// Continue anyway, this might not be critical
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
console.log('Returning proxy from worker', fs);
|
|
44
|
-
filesystem = Comlink.proxy({ fs });
|
|
45
|
-
filesystems.push(filesystem);
|
|
46
|
-
}
|
|
47
|
-
catch (e) {
|
|
48
|
-
console.error('Worker initialization failed with error:', e);
|
|
49
|
-
throw e; // Make sure error propagates
|
|
15
|
+
if (buffer) {
|
|
16
|
+
console.debug(`Mounting filesystem snapshot at [${mountPoint}]...`, buffer);
|
|
17
|
+
const uint8 = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
18
|
+
const aligned = uint8.byteOffset === 0 && uint8.byteLength === uint8.buffer.byteLength
|
|
19
|
+
? uint8.buffer
|
|
20
|
+
: uint8.buffer.slice(uint8.byteOffset, uint8.byteOffset + uint8.byteLength);
|
|
21
|
+
await Snapshot.mount(new Uint8Array(aligned), {
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
fs,
|
|
24
|
+
});
|
|
50
25
|
}
|
|
51
26
|
}
|
|
52
27
|
catch (e) {
|
|
53
|
-
console.error('
|
|
28
|
+
console.error('Worker initialization failed:', e);
|
|
29
|
+
throw e;
|
|
54
30
|
}
|
|
55
|
-
|
|
56
|
-
|
|
31
|
+
const vfs = createWorkerVfs();
|
|
32
|
+
const proxy = Comlink.proxy(vfs);
|
|
33
|
+
filesystems.push(proxy);
|
|
34
|
+
return proxy;
|
|
57
35
|
};
|
|
58
|
-
/**
|
|
59
|
-
* Optimized mount function that loads snapshots directly from URLs.
|
|
60
|
-
* This is much more efficient for large snapshots as it avoids transferring
|
|
61
|
-
* data through the main thread.
|
|
62
|
-
*/
|
|
63
36
|
export const mountFromUrl = async ({ url, mountPoint = '/' }) => {
|
|
64
|
-
let filesystem;
|
|
65
37
|
try {
|
|
66
|
-
|
|
67
|
-
console.log(`Loading and mounting filesystem snapshot from URL: ${url} at [${mountPoint}]...`);
|
|
38
|
+
console.debug(`Loading snapshot from URL: ${url} at [${mountPoint}]...`);
|
|
68
39
|
const startTime = performance.now();
|
|
69
40
|
await Snapshot.loadAndMount(url, {
|
|
70
41
|
// @ts-ignore
|
|
71
42
|
fs,
|
|
72
43
|
path: mountPoint
|
|
73
44
|
});
|
|
74
|
-
|
|
75
|
-
console.log(`Snapshot loaded and mounted in ${Math.round(endTime - startTime)}ms`);
|
|
76
|
-
console.log('Returning proxy from worker', fs);
|
|
77
|
-
filesystem = Comlink.proxy({ fs });
|
|
78
|
-
filesystems.push(filesystem);
|
|
45
|
+
console.debug(`Snapshot mounted in ${Math.round(performance.now() - startTime)}ms`);
|
|
79
46
|
}
|
|
80
47
|
catch (e) {
|
|
81
48
|
console.error('Error loading snapshot from URL:', e);
|
|
82
49
|
throw e;
|
|
83
50
|
}
|
|
84
|
-
|
|
51
|
+
const vfs = createWorkerVfs();
|
|
52
|
+
const proxy = Comlink.proxy(vfs);
|
|
53
|
+
filesystems.push(proxy);
|
|
54
|
+
return proxy;
|
|
85
55
|
};
|
|
86
56
|
onconnect = async function (event) {
|
|
87
57
|
const [port] = event.ports;
|
|
88
|
-
console.
|
|
58
|
+
console.debug('workers/fs connected on port: ', port);
|
|
89
59
|
port.addEventListener('close', () => {
|
|
90
|
-
console.
|
|
60
|
+
console.debug('fs port closed');
|
|
91
61
|
});
|
|
92
62
|
Comlink.expose({ mount, mountFromUrl }, port);
|
|
93
63
|
};
|
|
@@ -2,19 +2,21 @@ import * as Comlink from 'comlink';
|
|
|
2
2
|
import { createLanguageServer } from '../lsps/typescript';
|
|
3
3
|
import { createConnection } from 'vscode-languageserver/browser';
|
|
4
4
|
import { BrowserMessageReader, BrowserMessageWriter } from '@volar/language-server/browser';
|
|
5
|
-
// TODO: get rid of this
|
|
6
|
-
// instead, create language specific workers (with a smarter client)
|
|
7
|
-
// i.e typescript.worker.ts / rust.worker.ts / ...
|
|
8
5
|
onconnect = async (event) => {
|
|
9
6
|
const [port] = event.ports;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
// Use a MessageChannel to separate Comlink RPC from LSP protocol.
|
|
8
|
+
// Both Comlink and BrowserMessageReader listen on the same port's
|
|
9
|
+
// 'message' event, so Comlink messages get misinterpreted as LSP messages.
|
|
10
|
+
const { port1: lspPort, port2: clientLspPort } = new MessageChannel();
|
|
11
|
+
lspPort.start();
|
|
12
|
+
const reader = new BrowserMessageReader(lspPort);
|
|
13
|
+
const writer = new BrowserMessageWriter(lspPort);
|
|
13
14
|
const connection = createConnection(reader, writer);
|
|
14
15
|
connection.listen();
|
|
15
|
-
const proxy = async (
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const proxy = async (fsProxy, libFiles) => {
|
|
17
|
+
await createLanguageServer({ fs: fsProxy, connection, libFiles });
|
|
18
|
+
// Return the LSP port for the client to use (separate from Comlink port)
|
|
19
|
+
return Comlink.transfer(clientLspPort, [clientLspPort]);
|
|
18
20
|
};
|
|
19
21
|
Comlink.expose({ createLanguageServer: proxy }, port);
|
|
20
22
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joinezco/codeblock",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"@types/sharedworker": "^0.0.181",
|
|
27
27
|
"multimatch": "^7.0.0",
|
|
28
28
|
"process": "^0.11.10",
|
|
29
|
+
"puppeteer-core": "^24.37.5",
|
|
29
30
|
"tsx": "^4.19.4",
|
|
30
31
|
"vite": "^7.0.6",
|
|
31
32
|
"vitest": "^3.2.4"
|
|
@@ -58,7 +59,7 @@
|
|
|
58
59
|
"@jsonjoy.com/json-pack": "^1.2.0",
|
|
59
60
|
"@jsonjoy.com/util": "^1.6.0",
|
|
60
61
|
"@lezer/highlight": "^1.2.1",
|
|
61
|
-
"@
|
|
62
|
+
"@m234/nerd-fonts": "^0.5.0",
|
|
62
63
|
"@typescript/vfs": "^1.6.1",
|
|
63
64
|
"@uiw/codemirror-theme-vscode": "^4.23.12",
|
|
64
65
|
"@volar/language-server": "2.4.13",
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
"@volar/typescript": "2.4.13",
|
|
67
68
|
"comlink": "^4.4.2",
|
|
68
69
|
"events": "^3.3.0",
|
|
70
|
+
"ghostty-web": "^0.4.0",
|
|
69
71
|
"ignore": "^7.0.4",
|
|
70
72
|
"lodash": "^4.17.21",
|
|
71
73
|
"lz-string": "^1.5.0",
|
|
@@ -82,7 +84,7 @@
|
|
|
82
84
|
"vscode-languageserver-protocol": "^3.17.5",
|
|
83
85
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
84
86
|
"vscode-uri": "^3.1.0",
|
|
85
|
-
"@
|
|
87
|
+
"@codemirror/lsp-client": "6.2.2",
|
|
86
88
|
"@joinezco/jswasi": "0.0.1",
|
|
87
89
|
"@joinezco/memfs": "4.23.0"
|
|
88
90
|
},
|
|
@@ -90,6 +92,8 @@
|
|
|
90
92
|
"dev": "vite",
|
|
91
93
|
"build": "tsc",
|
|
92
94
|
"build:preview": "vite build",
|
|
93
|
-
"preview": "vite preview"
|
|
95
|
+
"preview": "vite preview",
|
|
96
|
+
"test": "vitest run src/e2e/",
|
|
97
|
+
"test:e2e": "vitest run src/e2e/"
|
|
94
98
|
}
|
|
95
99
|
}
|