@mhingston5/conduit 1.0.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 (87) hide show
  1. package/.env.example +13 -0
  2. package/.github/workflows/ci.yml +88 -0
  3. package/.github/workflows/pr-checks.yml +90 -0
  4. package/.tool-versions +2 -0
  5. package/README.md +177 -0
  6. package/conduit.yaml.test +3 -0
  7. package/docs/ARCHITECTURE.md +35 -0
  8. package/docs/CODE_MODE.md +33 -0
  9. package/docs/SECURITY.md +52 -0
  10. package/logo.png +0 -0
  11. package/package.json +74 -0
  12. package/src/assets/deno-shim.ts +93 -0
  13. package/src/assets/python-shim.py +21 -0
  14. package/src/core/asset.utils.ts +42 -0
  15. package/src/core/concurrency.service.ts +70 -0
  16. package/src/core/config.service.ts +147 -0
  17. package/src/core/execution.context.ts +37 -0
  18. package/src/core/execution.service.ts +209 -0
  19. package/src/core/interfaces/app.config.ts +17 -0
  20. package/src/core/interfaces/executor.interface.ts +31 -0
  21. package/src/core/interfaces/middleware.interface.ts +12 -0
  22. package/src/core/interfaces/url.validator.interface.ts +3 -0
  23. package/src/core/logger.ts +64 -0
  24. package/src/core/metrics.service.ts +112 -0
  25. package/src/core/middleware/auth.middleware.ts +56 -0
  26. package/src/core/middleware/error.middleware.ts +21 -0
  27. package/src/core/middleware/logging.middleware.ts +25 -0
  28. package/src/core/middleware/middleware.builder.ts +24 -0
  29. package/src/core/middleware/ratelimit.middleware.ts +31 -0
  30. package/src/core/network.policy.service.ts +106 -0
  31. package/src/core/ops.server.ts +74 -0
  32. package/src/core/otel.service.ts +41 -0
  33. package/src/core/policy.service.ts +77 -0
  34. package/src/core/registries/executor.registry.ts +26 -0
  35. package/src/core/request.controller.ts +297 -0
  36. package/src/core/security.service.ts +68 -0
  37. package/src/core/session.manager.ts +44 -0
  38. package/src/core/types.ts +47 -0
  39. package/src/executors/deno.executor.ts +342 -0
  40. package/src/executors/isolate.executor.ts +281 -0
  41. package/src/executors/pyodide.executor.ts +327 -0
  42. package/src/executors/pyodide.worker.ts +195 -0
  43. package/src/gateway/auth.service.ts +104 -0
  44. package/src/gateway/gateway.service.ts +345 -0
  45. package/src/gateway/schema.cache.ts +46 -0
  46. package/src/gateway/upstream.client.ts +244 -0
  47. package/src/index.ts +92 -0
  48. package/src/sdk/index.ts +2 -0
  49. package/src/sdk/sdk-generator.ts +245 -0
  50. package/src/sdk/tool-binding.ts +86 -0
  51. package/src/transport/socket.transport.ts +203 -0
  52. package/tests/__snapshots__/assets.test.ts.snap +97 -0
  53. package/tests/assets.test.ts +50 -0
  54. package/tests/auth.service.test.ts +78 -0
  55. package/tests/code-mode-lite-execution.test.ts +84 -0
  56. package/tests/code-mode-lite-gateway.test.ts +150 -0
  57. package/tests/concurrency.service.test.ts +50 -0
  58. package/tests/concurrency.test.ts +41 -0
  59. package/tests/config.service.test.ts +70 -0
  60. package/tests/contract.test.ts +43 -0
  61. package/tests/deno.executor.test.ts +68 -0
  62. package/tests/deno_hardening.test.ts +45 -0
  63. package/tests/dynamic.tool.test.ts +237 -0
  64. package/tests/e2e_stdio_upstream.test.ts +197 -0
  65. package/tests/fixtures/stdio-server.ts +42 -0
  66. package/tests/gateway.manifest.test.ts +82 -0
  67. package/tests/gateway.service.test.ts +58 -0
  68. package/tests/gateway.strict.unit.test.ts +74 -0
  69. package/tests/gateway.validation.unit.test.ts +89 -0
  70. package/tests/gateway_validation.test.ts +86 -0
  71. package/tests/hardening.test.ts +139 -0
  72. package/tests/hardening_v1.test.ts +72 -0
  73. package/tests/isolate.executor.test.ts +100 -0
  74. package/tests/log-limit.test.ts +55 -0
  75. package/tests/middleware.test.ts +106 -0
  76. package/tests/ops.server.test.ts +65 -0
  77. package/tests/policy.service.test.ts +90 -0
  78. package/tests/pyodide.executor.test.ts +101 -0
  79. package/tests/reference_mcp.ts +40 -0
  80. package/tests/remediation.test.ts +119 -0
  81. package/tests/routing.test.ts +148 -0
  82. package/tests/schema.cache.test.ts +27 -0
  83. package/tests/sdk/sdk-generator.test.ts +205 -0
  84. package/tests/socket.transport.test.ts +182 -0
  85. package/tests/stdio_upstream.test.ts +54 -0
  86. package/tsconfig.json +25 -0
  87. package/tsup.config.ts +22 -0
@@ -0,0 +1,27 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { SchemaCache } from '../src/gateway/schema.cache.js';
3
+ import pino from 'pino';
4
+
5
+ const logger = pino({ level: 'silent' });
6
+
7
+ describe('SchemaCache', () => {
8
+ it('should cache and retrieve schemas', () => {
9
+ const cache = new SchemaCache(logger);
10
+ const tools = [{ name: 'test', inputSchema: {} }];
11
+
12
+ cache.set('upstream1', tools);
13
+ expect(cache.get('upstream1')).toEqual(tools);
14
+ });
15
+
16
+ it('should return undefined for missing entries', () => {
17
+ const cache = new SchemaCache(logger);
18
+ expect(cache.get('missing')).toBeUndefined();
19
+ });
20
+
21
+ it('should invalidate entries', () => {
22
+ const cache = new SchemaCache(logger);
23
+ cache.set('upstream1', []);
24
+ cache.invalidate('upstream1');
25
+ expect(cache.get('upstream1')).toBeUndefined();
26
+ });
27
+ });
@@ -0,0 +1,205 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { SDKGenerator } from '../../src/sdk/sdk-generator.js';
3
+ import { ToolBinding, toToolBinding, groupByNamespace } from '../../src/sdk/tool-binding.js';
4
+
5
+ describe('SDKGenerator', () => {
6
+ const generator = new SDKGenerator();
7
+
8
+ describe('generateTypeScript', () => {
9
+ it('should generate SDK with nested namespace structure', () => {
10
+ const bindings: ToolBinding[] = [
11
+ { name: 'github__createIssue', namespace: 'github', methodName: 'createIssue' },
12
+ { name: 'github__listRepos', namespace: 'github', methodName: 'listRepos' },
13
+ { name: 'slack__sendMessage', namespace: 'slack', methodName: 'sendMessage' },
14
+ ];
15
+
16
+ const code = generator.generateTypeScript(bindings);
17
+
18
+ expect(code).toContain('const tools = {');
19
+ expect(code).toContain('github: {');
20
+ expect(code).toContain('async createIssue(args)');
21
+ expect(code).toContain('await __internalCallTool("github__createIssue", args)');
22
+ expect(code).toContain('slack: {');
23
+ expect(code).toContain('async sendMessage(args)');
24
+ expect(code).toContain('(globalThis as any).tools = tools');
25
+ });
26
+
27
+ it('should include $raw escape hatch by default', () => {
28
+ const bindings: ToolBinding[] = [
29
+ { name: 'test__method', namespace: 'test', methodName: 'method' },
30
+ ];
31
+
32
+ const code = generator.generateTypeScript(bindings);
33
+
34
+ expect(code).toContain('async $raw(name, args)');
35
+ expect(code).toContain('await __internalCallTool(normalized, args)');
36
+ });
37
+
38
+ it('should omit $raw when disabled', () => {
39
+ const bindings: ToolBinding[] = [
40
+ { name: 'test__method', namespace: 'test', methodName: 'method' },
41
+ ];
42
+
43
+ const code = generator.generateTypeScript(bindings, undefined, false);
44
+
45
+ expect(code).not.toContain('$raw');
46
+ });
47
+
48
+ it('should include JSDoc comments from descriptions', () => {
49
+ const bindings: ToolBinding[] = [
50
+ { name: 'github__createIssue', namespace: 'github', methodName: 'createIssue', description: 'Create a new GitHub issue' },
51
+ ];
52
+
53
+ const code = generator.generateTypeScript(bindings);
54
+
55
+ expect(code).toContain('/** Create a new GitHub issue */');
56
+ });
57
+
58
+ it('should handle empty bindings', () => {
59
+ const code = generator.generateTypeScript([]);
60
+
61
+ expect(code).toContain('const tools = {');
62
+ expect(code).toContain('async $raw(name, args)');
63
+ });
64
+ });
65
+
66
+ describe('generatePython', () => {
67
+ it('should generate SDK with nested namespace structure', () => {
68
+ const bindings: ToolBinding[] = [
69
+ { name: 'github__createIssue', namespace: 'github', methodName: 'createIssue' },
70
+ { name: 'slack__sendMessage', namespace: 'slack', methodName: 'sendMessage' },
71
+ ];
72
+
73
+ const code = generator.generatePython(bindings);
74
+
75
+ expect(code).toContain('class _Tools:');
76
+ expect(code).toContain('self.github = _ToolNamespace');
77
+ expect(code).toContain('"create_issue"'); // snake_case conversion
78
+ expect(code).toContain('self.slack = _ToolNamespace');
79
+ expect(code).toContain('"send_message"'); // snake_case conversion
80
+ expect(code).toContain('tools = _Tools()');
81
+ });
82
+
83
+ it('should include raw escape hatch', () => {
84
+ const bindings: ToolBinding[] = [
85
+ { name: 'test__method', namespace: 'test', methodName: 'method' },
86
+ ];
87
+
88
+ const code = generator.generatePython(bindings);
89
+
90
+ expect(code).toContain('async def raw(self, name, args)');
91
+ expect(code).toContain('await _internal_call_tool(normalized, args)');
92
+ });
93
+
94
+ it('should inject allowlist when provided', () => {
95
+ const bindings: ToolBinding[] = [
96
+ { name: 'test__method', namespace: 'test', methodName: 'method' },
97
+ ];
98
+
99
+ const code = generator.generatePython(bindings, ['test.method', 'other.*']);
100
+
101
+ expect(code).toContain('_allowed_tools = ["test__method","other__*"]');
102
+ expect(code).toContain('if _allowed_tools is not None');
103
+ });
104
+ });
105
+ });
106
+
107
+ describe('SDK Allowlist Enforcement', () => {
108
+ const generator = new SDKGenerator();
109
+
110
+ describe('TypeScript', () => {
111
+ it('should inject allowlist when provided', () => {
112
+ const bindings: ToolBinding[] = [
113
+ { name: 'github__createIssue', namespace: 'github', methodName: 'createIssue' },
114
+ ];
115
+
116
+ const code = generator.generateTypeScript(bindings, ['github.createIssue', 'slack.*']);
117
+
118
+ expect(code).toContain('const __allowedTools = ["github__createIssue","slack__*"]');
119
+ expect(code).toContain('if (__allowedTools)');
120
+ });
121
+
122
+ it('should set __allowedTools to null when no allowlist', () => {
123
+ const bindings: ToolBinding[] = [
124
+ { name: 'test__method', namespace: 'test', methodName: 'method' },
125
+ ];
126
+
127
+ const code = generator.generateTypeScript(bindings);
128
+
129
+ expect(code).toContain('const __allowedTools = null');
130
+ });
131
+
132
+ it('should include wildcard pattern matching', () => {
133
+ const bindings: ToolBinding[] = [];
134
+ const code = generator.generateTypeScript(bindings, ['github.*']);
135
+
136
+ expect(code).toContain("if (p.endsWith('__*'))");
137
+ expect(code).toContain('normalized.startsWith(p.slice(0, -1))');
138
+ });
139
+ });
140
+
141
+ describe('Python', () => {
142
+ it('should inject allowlist when provided', () => {
143
+ const bindings: ToolBinding[] = [];
144
+ const code = generator.generatePython(bindings, ['github.createIssue']);
145
+
146
+ expect(code).toContain('_allowed_tools = ["github__createIssue"]');
147
+ expect(code).toContain('if _allowed_tools is not None');
148
+ });
149
+
150
+ it('should set _allowed_tools to None when no allowlist', () => {
151
+ const bindings: ToolBinding[] = [];
152
+ const code = generator.generatePython(bindings);
153
+
154
+ expect(code).toContain('_allowed_tools = None');
155
+ });
156
+
157
+ it('should include wildcard pattern matching', () => {
158
+ const bindings: ToolBinding[] = [];
159
+ const code = generator.generatePython(bindings, ['github.*']);
160
+
161
+ expect(code).toContain('p.endswith("__*")');
162
+ expect(code).toContain('raise PermissionError');
163
+ });
164
+ });
165
+ });
166
+
167
+ describe('toToolBinding', () => {
168
+ it('should parse fully qualified tool name', () => {
169
+ const binding = toToolBinding('github__createIssue');
170
+
171
+ expect(binding.name).toBe('github__createIssue');
172
+ expect(binding.namespace).toBe('github');
173
+ expect(binding.methodName).toBe('createIssue');
174
+ });
175
+
176
+ it('should handle multi-part method names', () => {
177
+ const binding = toToolBinding('mcp__filesystem__read_file');
178
+
179
+ expect(binding.namespace).toBe('mcp');
180
+ expect(binding.methodName).toBe('filesystem__read_file');
181
+ });
182
+
183
+ it('should include optional fields', () => {
184
+ const schema = { type: 'object' };
185
+ const binding = toToolBinding('test__method', schema, 'Test description');
186
+
187
+ expect(binding.inputSchema).toEqual(schema);
188
+ expect(binding.description).toBe('Test description');
189
+ });
190
+ });
191
+
192
+ describe('groupByNamespace', () => {
193
+ it('should group bindings by namespace', () => {
194
+ const bindings: ToolBinding[] = [
195
+ { name: 'a__one', namespace: 'a', methodName: 'one' },
196
+ { name: 'a__two', namespace: 'a', methodName: 'two' },
197
+ { name: 'b__one', namespace: 'b', methodName: 'one' },
198
+ ];
199
+
200
+ const groups = groupByNamespace(bindings);
201
+
202
+ expect(groups.get('a')?.length).toBe(2);
203
+ expect(groups.get('b')?.length).toBe(1);
204
+ });
205
+ });
@@ -0,0 +1,182 @@
1
+ import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
2
+ import { SocketTransport } from '../src/transport/socket.transport.js';
3
+ import { RequestController } from '../src/core/request.controller.js';
4
+ import pino from 'pino';
5
+ import net from 'node:net';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { SecurityService } from '../src/core/security.service.js';
9
+ import { ExecutionService } from '../src/core/execution.service.js';
10
+ import { ExecutorRegistry } from '../src/core/registries/executor.registry.js';
11
+ import { buildDefaultMiddleware } from '../src/core/middleware/middleware.builder.js';
12
+
13
+ import fs from 'node:fs';
14
+
15
+ const logger = pino({ level: 'silent' });
16
+ const defaultLimits = {
17
+ timeoutMs: 5000,
18
+ memoryLimitMb: 128,
19
+ maxOutputBytes: 1024,
20
+ maxLogEntries: 100,
21
+ };
22
+
23
+ describe('SocketTransport', () => {
24
+ let transport: SocketTransport;
25
+ let requestController: RequestController;
26
+ let securityService: any;
27
+ let concurrencyService: any;
28
+ let gatewayService: any;
29
+ const testToken = 'test-token';
30
+
31
+ beforeEach(() => {
32
+ gatewayService = {
33
+ discoverTools: vi.fn().mockResolvedValue([]), // Return empty array for SDK generation
34
+ callTool: vi.fn(),
35
+ listToolPackages: vi.fn().mockResolvedValue([]),
36
+ listToolStubs: vi.fn().mockResolvedValue([])
37
+ } as any;
38
+ securityService = new SecurityService(logger, testToken);
39
+ concurrencyService = {
40
+ run: vi.fn().mockImplementation((fn) => fn()),
41
+ } as any;
42
+
43
+ const executorRegistry = new ExecutorRegistry();
44
+ // Use real DenoExecutor for this E2E-like test
45
+ // But DenoExecutor initialization might be heavy? Whatever, it creates temp dir.
46
+ // Or we can mock it to return expected stdout.
47
+ // Let's use real one to match previous behavior.
48
+
49
+ // Since we are mocking Deno call in test via 'executeTypeScript', maybe we should mock DenoExecutor execution?
50
+ // But the test sends 'console.log...'.
51
+ // If we mock DenoExecutor, we can make it return 'hello E2E'.
52
+ // That's faster and safer.
53
+ const mockDenoExecutor = {
54
+ execute: vi.fn().mockImplementation(async (code) => {
55
+ // Simple mock that echoes input logic if needed, or just returns static because test sends specific string
56
+ if (code.includes('hello E2E')) return { stdout: 'hello E2E', stderr: '', exitCode: 0 };
57
+ if (code.includes('hello')) return { stdout: 'hello', stderr: '', exitCode: 0 }; // for server busy test
58
+ return { stdout: '', stderr: '', exitCode: 0 };
59
+ }),
60
+ shutdown: vi.fn(),
61
+ healthCheck: vi.fn(),
62
+ warmup: vi.fn()
63
+ };
64
+ executorRegistry.register('deno', mockDenoExecutor as any);
65
+
66
+ const executionService = new ExecutionService(
67
+ logger,
68
+ defaultLimits,
69
+ gatewayService,
70
+ securityService,
71
+ executorRegistry
72
+ );
73
+ executionService.ipcAddress = '127.0.0.1:0'; // Dummy address for tests
74
+
75
+ requestController = new RequestController(logger, executionService, gatewayService, buildDefaultMiddleware(securityService));
76
+ });
77
+
78
+ afterEach(async () => {
79
+ if (transport) {
80
+ await transport.close();
81
+ }
82
+ });
83
+
84
+ it('should listen on a TCP port in development mode', async () => {
85
+ transport = new SocketTransport(logger, requestController, concurrencyService);
86
+ const address = await transport.listen({ port: 0 }); // Random port
87
+ expect(address).toMatch(/127\.0\.0\.1:\d+|:::\d+|0\.0\.0\.0:\d+/);
88
+ });
89
+
90
+ if (os.platform() !== 'win32') {
91
+ it('should listen on a Unix socket', async () => {
92
+ const socketPath = path.join(os.tmpdir(), `conduit-test-${Date.now()}.sock`);
93
+ transport = new SocketTransport(logger, requestController, concurrencyService);
94
+ const address = await transport.listen({ path: socketPath });
95
+ expect(address).toBe(socketPath);
96
+ expect(fs.existsSync(socketPath)).toBe(true);
97
+
98
+ // Cleanup
99
+ if (fs.existsSync(socketPath)) fs.unlinkSync(socketPath);
100
+ });
101
+ }
102
+
103
+ it('should handle mcp.executeTypeScript request', async () => {
104
+ transport = new SocketTransport(logger, requestController, concurrencyService);
105
+ const address = await transport.listen({ port: 0 });
106
+ const portMatch = address.match(/:(\d+)$/);
107
+ const port = portMatch ? parseInt(portMatch[1]) : 0;
108
+ const host = address.replace(/:\d+$/, '').replace(/^\[|\]$/g, '');
109
+
110
+ return new Promise<void>((resolve, reject) => {
111
+ const client = net.createConnection({ host: host === '::' ? '::1' : host, port }, () => {
112
+ const request = {
113
+ jsonrpc: '2.0',
114
+ id: 1,
115
+ method: 'mcp.executeTypeScript',
116
+ params: { code: 'console.log("hello E2E")' },
117
+ auth: { bearerToken: testToken }
118
+ };
119
+ client.write(JSON.stringify(request) + '\n');
120
+ });
121
+
122
+ client.on('data', (data) => {
123
+ const responseString = data.toString();
124
+ try {
125
+ const response = JSON.parse(responseString);
126
+ expect(response.id).toBe(1);
127
+ expect(response.result.stdout).toContain('hello E2E');
128
+ resolve();
129
+ } catch (err) {
130
+ reject(err);
131
+ } finally {
132
+ client.end();
133
+ }
134
+ });
135
+
136
+ client.on('error', reject);
137
+ });
138
+ });
139
+
140
+ it('should return server busy error with correct ID when queue is full', async () => {
141
+ const queueFullError = new Error('Queue full');
142
+ queueFullError.name = 'QueueFullError';
143
+ concurrencyService.run.mockRejectedValue(queueFullError);
144
+
145
+ transport = new SocketTransport(logger, requestController, concurrencyService);
146
+ const address = await transport.listen({ port: 0 });
147
+ const portMatch = address.match(/:(\d+)$/);
148
+ const port = portMatch ? parseInt(portMatch[1]) : 0;
149
+ const host = address.replace(/:\d+$/, '').replace(/^\[|\]$/g, '');
150
+
151
+ return new Promise<void>((resolve, reject) => {
152
+ const client = net.createConnection({ host: host === '::' ? '::1' : host, port }, () => {
153
+ const request = {
154
+ jsonrpc: '2.0',
155
+ id: 12345,
156
+ method: 'mcp.executeTypeScript',
157
+ params: { code: 'console.log("hello")' },
158
+ auth: { bearerToken: testToken }
159
+ };
160
+ client.write(JSON.stringify(request) + '\n');
161
+ });
162
+
163
+ client.on('data', (data) => {
164
+ const responseString = data.toString();
165
+ try {
166
+ const response = JSON.parse(responseString);
167
+ expect(response.id).toBe(12345);
168
+ expect(response.error).toBeDefined();
169
+ expect(response.error.code).toBe(-32000); // ConduitError.ServerBusy
170
+ expect(response.error.message).toBe('Server busy');
171
+ resolve();
172
+ } catch (err) {
173
+ reject(err);
174
+ } finally {
175
+ client.end();
176
+ }
177
+ });
178
+
179
+ client.on('error', reject);
180
+ });
181
+ });
182
+ });
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { UpstreamClient } from '../src/gateway/upstream.client.js';
3
+ import { Logger } from 'pino';
4
+ import { mock } from 'vitest-mock-extended';
5
+ import { AuthService } from '../src/gateway/auth.service.js';
6
+ import { IUrlValidator } from '../src/core/interfaces/url.validator.interface.js';
7
+ import path from 'path';
8
+
9
+ describe('UpstreamClient (Stdio)', () => {
10
+ const logger = mock<Logger>();
11
+ const authService = mock<AuthService>();
12
+ const urlValidator = mock<IUrlValidator>();
13
+ logger.child.mockReturnThis();
14
+
15
+ it('should connect to a local stdio server and call a tool', async () => {
16
+ const serverPath = path.resolve(__dirname, 'fixtures/stdio-server.ts');
17
+
18
+ // We use ts-node or just node with loader to run the ts file,
19
+ // or we compile it. For simplicity in this env, we assume we can run it via node loader
20
+ // OR we use a simple JS script if TS execution is complex in subprocess.
21
+ // Let's rely on tsx or similar if available, or just compile it?
22
+ // Actually, the project uses `vite-node` or `ts-node`.
23
+ // Let's try running it with `npx tsx`.
24
+
25
+ const client = new UpstreamClient(
26
+ logger,
27
+ {
28
+ id: 'stdio-test',
29
+ type: 'stdio',
30
+ command: 'npx',
31
+ args: ['tsx', serverPath],
32
+ },
33
+ authService,
34
+ urlValidator
35
+ );
36
+
37
+ // We simulate a JSON-RPC request like Gateway would send
38
+ const request = {
39
+ jsonrpc: '2.0',
40
+ id: '1',
41
+ method: 'tools/call', // SDK uses 'tools/call'
42
+ params: {
43
+ name: 'echo',
44
+ arguments: { message: 'Hello Stdio' },
45
+ },
46
+ };
47
+
48
+ const response = await client.call(request as any, { correlationId: 'test-corr' } as any);
49
+
50
+ expect(response.error).toBeUndefined();
51
+ expect(response.result).toBeDefined();
52
+ expect((response.result as any).content[0].text).toBe('Echo: Hello Stdio');
53
+ }, 10000);
54
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": [
7
+ "ESNext"
8
+ ],
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "outDir": "dist",
14
+ "rootDir": "src",
15
+ "declaration": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": [
19
+ "src/**/*"
20
+ ],
21
+ "exclude": [
22
+ "node_modules",
23
+ "**/*.test.ts"
24
+ ]
25
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: {
5
+ index: 'src/index.ts',
6
+ 'executors/pyodide.worker': 'src/executors/pyodide.worker.ts'
7
+ },
8
+ format: ['esm'],
9
+ dts: true,
10
+ splitting: false,
11
+ sourcemap: true,
12
+ clean: true,
13
+ loader: {
14
+ '.py': 'text',
15
+ '.ts': 'text', // We want the shim source as text
16
+ },
17
+ // Ensure assets are included
18
+ // We can use the 'onSuccess' hook to copy them or just include them in the bundle
19
+ // But the spec says 'into dist/assets', which implies they should be separate files.
20
+ // tsup doesn't have a direct 'copy directory' but we can use a custom plugin or hook.
21
+ onSuccess: 'mkdir -p dist/assets && cp src/assets/* dist/assets/',
22
+ });