@stainless-api/playgrounds 0.0.1-beta.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 (45) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +23 -0
  3. package/eslint.config.js +2 -0
  4. package/package.json +69 -0
  5. package/src/Logs.tsx +216 -0
  6. package/src/Panel.tsx +21 -0
  7. package/src/PlaygroundPanelWrapper.tsx +5 -0
  8. package/src/build-py-types.ts +152 -0
  9. package/src/build-ts-types.ts +70 -0
  10. package/src/build.ts +97 -0
  11. package/src/codemirror/comlink.ts +698 -0
  12. package/src/codemirror/curl/curlconverter.vendor.js +7959 -0
  13. package/src/codemirror/curl.ts +108 -0
  14. package/src/codemirror/deps.ts +12 -0
  15. package/src/codemirror/fix-lsp-markdown.ts +50 -0
  16. package/src/codemirror/lsp.ts +87 -0
  17. package/src/codemirror/python/anser.ts +398 -0
  18. package/src/codemirror/python/pyodide.ts +180 -0
  19. package/src/codemirror/python.ts +160 -0
  20. package/src/codemirror/react.tsx +615 -0
  21. package/src/codemirror/sanitize-html.ts +12 -0
  22. package/src/codemirror/shiki.ts +65 -0
  23. package/src/codemirror/typescript/cdn-typescript.d.ts +1 -0
  24. package/src/codemirror/typescript/cdn-typescript.js +1 -0
  25. package/src/codemirror/typescript/console.ts +590 -0
  26. package/src/codemirror/typescript/get-signature.ts +94 -0
  27. package/src/codemirror/typescript/prettier-plugin-external-typescript.vendor.js +4968 -0
  28. package/src/codemirror/typescript/runner.ts +396 -0
  29. package/src/codemirror/typescript/special-info.ts +171 -0
  30. package/src/codemirror/typescript/worker.ts +292 -0
  31. package/src/codemirror/typescript.tsx +198 -0
  32. package/src/create.tsx +44 -0
  33. package/src/icon.tsx +21 -0
  34. package/src/index.ts +6 -0
  35. package/src/logs-context.ts +5 -0
  36. package/src/playground.css +359 -0
  37. package/src/sandbox-worker/in-frame.js +179 -0
  38. package/src/sandbox-worker/index.ts +202 -0
  39. package/src/use-storage.ts +54 -0
  40. package/src/util.ts +29 -0
  41. package/src/virtual-module.d.ts +45 -0
  42. package/src/vite-env.d.ts +1 -0
  43. package/test/get-signature.test.ts +73 -0
  44. package/test/use-storage.test.ts +60 -0
  45. package/tsconfig.json +11 -0
@@ -0,0 +1,180 @@
1
+ import { expose } from '../comlink';
2
+ import type { Logger } from '../../Logs.tsx';
3
+ import Anser from './anser.ts';
4
+
5
+ const indexURL = 'https://cdn.jsdelivr.net/pyodide/v0.28.1/full';
6
+ const pyodidePromise = (
7
+ import(
8
+ /* @vite-ignore */
9
+ (indexURL + '/pyodide.mjs') as string
10
+ ) as Promise<typeof import('pyodide')>
11
+ ).then((mod) =>
12
+ mod.loadPyodide({
13
+ indexURL,
14
+ }),
15
+ );
16
+ function withResolvers<T>(): { promise: Promise<T>; resolve(value: T): void; reject(err: unknown): void } {
17
+ let resolve: (value: T) => void, reject: (err: unknown) => void;
18
+ const promise = new Promise<T>((res, rej) => {
19
+ resolve = res;
20
+ reject = rej;
21
+ });
22
+ return { promise, resolve: resolve!, reject: reject! };
23
+ }
24
+ const wheelPromise = withResolvers<{ blob: Blob; name: string }>();
25
+ const micropipPromise = pyodidePromise.then(async (pyodide) => {
26
+ await pyodide.loadPackage('micropip');
27
+ });
28
+ const sdkPromise = Promise.all([wheelPromise.promise, pyodidePromise, micropipPromise]).then(
29
+ async ([wheel, pyodide]) => {
30
+ pyodide.FS.writeFile('/' + wheel.name, new Uint8Array(await wheel.blob.arrayBuffer()));
31
+ const micropip = pyodide.pyimport('micropip');
32
+ await Promise.all([
33
+ micropip.install('emfs:/' + wheel.name),
34
+ micropip.install(
35
+ 'https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl',
36
+ ),
37
+ ]);
38
+ },
39
+ );
40
+ const blackPromise = Promise.all([pyodidePromise, micropipPromise]).then(async ([pyodide]) => {
41
+ const micropip = pyodide.pyimport('micropip');
42
+ await micropip.install(
43
+ 'https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl',
44
+ );
45
+ const black = pyodide.pyimport('black');
46
+ const mode = black.Mode();
47
+ return (code: string) => black.format_file_contents.callKwargs(code, { fast: false, mode });
48
+ });
49
+ const setEnvFromJSONPromise = sdkPromise.then(async () => {
50
+ const pyodide = await pyodidePromise;
51
+ pyodide.runPython(`
52
+ def __mk_stl_setenv__():
53
+ async def setenv(vars):
54
+ __import__("os").environ.update(__import__("json").loads(vars))
55
+ return setenv
56
+ `);
57
+ const setEnvFromJSON = pyodide.globals.__mk_stl_setenv__();
58
+ delete pyodide.globals.__mk_stl_setenv__;
59
+ return setEnvFromJSON;
60
+ });
61
+ const wrapperPromise = sdkPromise.then(async () => {
62
+ const pyodide = await pyodidePromise;
63
+ pyodide.runPython(`
64
+ __import__("os").environ["COLORTERM"] = "truecolor"
65
+ __import__("os").environ["FORCE_COLOR"] = "3"
66
+ def __mk_stl_run__():
67
+ async def run(code):
68
+ try:
69
+ compiled = compile(
70
+ code,
71
+ "<playground>",
72
+ "exec",
73
+ flags=__import__("ast").PyCF_ALLOW_TOP_LEVEL_AWAIT,
74
+ )
75
+ coroutine = eval(compiled, {})
76
+ if coroutine is not None:
77
+ await coroutine
78
+ except:
79
+ re = __import__("re")
80
+ print(re.sub(r"^ *File \\"<exec>\\",.+, in run\\n", "", __import__("traceback").format_exc(), flags=re.MULTILINE), file=__import__("sys").stderr)
81
+ return run
82
+ `);
83
+ const run = pyodide.globals.__mk_stl_run__();
84
+ delete pyodide.globals.__mk_stl_run__;
85
+ return run;
86
+ });
87
+
88
+ function anserToCSS(jsonChunk: Anser.AnserJsonEntry) {
89
+ const rules: string[] = [];
90
+ const textDecorations: string[] = [];
91
+
92
+ if (jsonChunk.fg) {
93
+ rules.push('color:' + jsonChunk.fg);
94
+ }
95
+
96
+ if (jsonChunk.bg) {
97
+ rules.push('background-color:' + jsonChunk.bg);
98
+ }
99
+
100
+ jsonChunk.decorations.forEach((decoration) => {
101
+ // use styles
102
+ if (decoration === 'bold') {
103
+ rules.push('font-weight:bold');
104
+ } else if (decoration === 'dim') {
105
+ rules.push('opacity:0.5');
106
+ } else if (decoration === 'italic') {
107
+ rules.push('font-style:italic');
108
+ } else if (decoration === 'hidden') {
109
+ rules.push('visibility:hidden');
110
+ } else if (decoration === 'strikethrough') {
111
+ textDecorations.push('line-through');
112
+ } else {
113
+ // underline and blink are treated here
114
+ textDecorations.push(decoration);
115
+ }
116
+ });
117
+
118
+ if (textDecorations.length) {
119
+ rules.push('text-decoration:' + textDecorations.join(' '));
120
+ }
121
+
122
+ return rules.join(';');
123
+ }
124
+
125
+ let packagesInstalling = true;
126
+ wrapperPromise.finally(() => {
127
+ packagesInstalling = false;
128
+ });
129
+
130
+ let anser = new Anser();
131
+ const api = {
132
+ async setLogger(callback: Logger) {
133
+ const pyodide = await pyodidePromise;
134
+ const ansi = (level: 'stdout' | 'stderr') => (str: string) => {
135
+ console.log({ str });
136
+ if (packagesInstalling && /^Loaded|^Loading/.test(str)) return;
137
+ callback({
138
+ type: level,
139
+ parts: anser
140
+ .ansiToJson(str)
141
+ .filter((e) => e.content !== '')
142
+ .map((e) => ({
143
+ css: anserToCSS(e),
144
+ value: e.content,
145
+ type: 'string',
146
+ })),
147
+ });
148
+ };
149
+ pyodide.setStdout({
150
+ batched: ansi('stdout'),
151
+ });
152
+ pyodide.setStderr({
153
+ batched: ansi('stderr'),
154
+ });
155
+ },
156
+ async run(s: string) {
157
+ try {
158
+ const wrapper = await wrapperPromise;
159
+ await wrapper(s);
160
+ } finally {
161
+ anser = new Anser();
162
+ }
163
+ },
164
+ async setEnv(vars: Record<string, string>) {
165
+ const setEnvFromJSON = await setEnvFromJSONPromise;
166
+ setEnvFromJSON(JSON.stringify(vars));
167
+ },
168
+ async format(s: string): Promise<string | undefined> {
169
+ try {
170
+ return (await blackPromise)(s).replace(/\n+$/, '');
171
+ } catch {
172
+ return undefined;
173
+ }
174
+ },
175
+ init(o: { blob: Blob; name: string }) {
176
+ wheelPromise.resolve(o);
177
+ },
178
+ };
179
+ export type API = typeof api;
180
+ expose(api);
@@ -0,0 +1,160 @@
1
+ import { workerTransport } from './lsp';
2
+ import { pythonLanguage } from '@codemirror/lang-python';
3
+ import pyodideWorkerURL from './python/pyodide?worker&url';
4
+ import type { Language } from './react';
5
+ import { proxy, releaseProxy, type Remote, wrap } from './comlink';
6
+ import type { API } from './python/pyodide';
7
+ import type { Logger } from '../Logs';
8
+ import wheelUrl from 'virtual:stl-playground/python/wheel.whl?url';
9
+ import pyTypes from 'virtual:stl-playground/python.json';
10
+
11
+ if (!pyTypes) {
12
+ throw new Error('Python playgrounds failed to build.');
13
+ }
14
+
15
+ const pyrightWorkerURL = 'https://cdn.jsdelivr.net/npm/browser-basedpyright@1.33.0/dist/pyright.worker.js';
16
+
17
+ const wheelBlobPromise = fetch(wheelUrl).then((e) => e.blob());
18
+ export async function createPyright(signal: AbortSignal, doc: string): Promise<Language> {
19
+ const { SandboxWorker } = await import('../sandbox-worker/index.js');
20
+ let pyodideWorker: Worker;
21
+ let pyodideAPI: Remote<API>;
22
+ const startPyodide = async () => {
23
+ if (pyodideWorker) {
24
+ pyodideWorker.terminate();
25
+ pyodideAPI[releaseProxy]();
26
+ }
27
+ pyodideWorker = new SandboxWorker(pyodideWorkerURL);
28
+ pyodideWorker.addEventListener('error', (e) => {
29
+ e.preventDefault();
30
+ logger({ type: 'error', parts: ['failed to load pyodide.'] });
31
+ pyodideWorker.terminate();
32
+ pyodideAPI[releaseProxy]();
33
+ });
34
+ pyodideAPI = wrap<API>(pyodideWorker);
35
+ await pyodideAPI.init({ blob: await wheelBlobPromise, name: pyTypes!.wheel });
36
+ };
37
+ await startPyodide();
38
+ const pyrightWorker: Worker = new SandboxWorker(pyrightWorkerURL, {
39
+ name: 'pyright-foreground',
40
+ });
41
+
42
+ pyrightWorker.postMessage({
43
+ type: 'browser/boot',
44
+ mode: 'foreground',
45
+ });
46
+
47
+ signal.addEventListener('abort', () => {
48
+ pyrightWorker.terminate();
49
+ pyodideWorker.terminate();
50
+ });
51
+
52
+ const workers: Worker[] = [pyrightWorker];
53
+ const realTerminate = pyrightWorker.terminate;
54
+ pyrightWorker.terminate = () => {
55
+ pyrightWorker.terminate = realTerminate;
56
+ workers.forEach((w) => w.terminate());
57
+ };
58
+
59
+ let backgroundWorkerCount = 0;
60
+
61
+ const initializationOptions = {
62
+ files: {
63
+ ...pyTypes?.files,
64
+ ['/play/playground.py']: doc,
65
+ ['/play/pyrightconfig.json']: JSON.stringify({
66
+ typeshedPath: '/typeshed',
67
+ typeCheckingMode: 'all',
68
+ venvPath: '.',
69
+ venv: '.venv',
70
+ }),
71
+ },
72
+ };
73
+
74
+ if (signal.aborted) throw new Error('unmounted');
75
+
76
+ const transport = workerTransport({
77
+ worker: pyrightWorker,
78
+ hooks: {
79
+ onSend: (message_) => {
80
+ const message = message_ as {
81
+ method: string;
82
+ params: {
83
+ capabilities?: { publishDiagnostics?: object };
84
+ rootPath: string;
85
+ rootUri: string;
86
+ initializationOptions?: object;
87
+ };
88
+ };
89
+ if (message.method === 'initialize') {
90
+ message.params.capabilities ??= {};
91
+ message.params.capabilities.publishDiagnostics = {
92
+ tagSupport: {
93
+ valueSet: [1, 2],
94
+ },
95
+ versionSupport: true,
96
+ };
97
+ message.params.rootPath = message.params.rootUri.replace(/^file:\/\//, '');
98
+ message.params.initializationOptions = initializationOptions;
99
+ }
100
+ return message;
101
+ },
102
+ onMessage: (data_) => {
103
+ // Handle Pyright-specific messages
104
+ const data = data_ as { type?: string; initialData: unknown; port: MessagePort };
105
+ if (data && data.type === 'browser/newWorker') {
106
+ const { initialData, port } = data;
107
+ const background: Worker = new SandboxWorker(pyrightWorkerURL, {
108
+ name: `pyright-background-${++backgroundWorkerCount}`,
109
+ });
110
+ workers.push(background);
111
+ background.postMessage(
112
+ {
113
+ type: 'browser/boot',
114
+ mode: 'background',
115
+ initialData,
116
+ port,
117
+ },
118
+ [port],
119
+ );
120
+ }
121
+ },
122
+ },
123
+ });
124
+
125
+ let logger: Logger;
126
+ const lang: Language = {
127
+ transport,
128
+ fileName: 'playground.py',
129
+ extensions: [pythonLanguage],
130
+ doc,
131
+ setLogger(fn) {
132
+ pyodideAPI.setLogger(proxy((logger = fn)));
133
+ },
134
+ async setEnv(vars) {
135
+ await pyodideAPI.setEnv(vars);
136
+ },
137
+ async run(code, signal) {
138
+ signal.addEventListener('abort', startPyodide);
139
+ try {
140
+ await pyodideAPI.run(code);
141
+ } catch (e) {
142
+ if (signal.aborted) {
143
+ logger({ type: 'error', parts: ['Aborted.'] });
144
+ return;
145
+ }
146
+ throw e;
147
+ } finally {
148
+ signal.removeEventListener('abort', startPyodide);
149
+ }
150
+ },
151
+ async format(code) {
152
+ return pyodideAPI.format(code);
153
+ },
154
+ async expandLogHandle() {
155
+ return [];
156
+ },
157
+ async freeLogHandle() {},
158
+ };
159
+ return lang;
160
+ }