almostnode 0.1.0 → 0.2.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.
@@ -5,13 +5,74 @@
5
5
  * to provide browser-enforced isolation from the main application.
6
6
  */
7
7
 
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
13
+ // @ts-ignore - import.meta.url is available in ESM
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+
16
+ /**
17
+ * Get the contents of the service worker file.
18
+ * Returns null if the file is not found (e.g., running in browser).
19
+ */
20
+ function getServiceWorkerContent(): string | null {
21
+ try {
22
+ // Try dist directory first (when running from built package)
23
+ let swPath = path.join(__dirname, '__sw__.js');
24
+ if (fs.existsSync(swPath)) {
25
+ return fs.readFileSync(swPath, 'utf-8');
26
+ }
27
+ // Try relative to src (when running from source)
28
+ swPath = path.join(__dirname, '../dist/__sw__.js');
29
+ if (fs.existsSync(swPath)) {
30
+ return fs.readFileSync(swPath, 'utf-8');
31
+ }
32
+ return null;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ export interface SandboxHtmlOptions {
39
+ /**
40
+ * URL to load almostnode from (e.g., unpkg, jsdelivr, or your CDN)
41
+ * @default 'https://unpkg.com/almostnode/dist/index.js'
42
+ */
43
+ almostnodeUrl?: string;
44
+ /**
45
+ * Whether to include service worker registration for dev server support.
46
+ * When true, the sandbox can run ViteDevServer/NextDevServer with URL access.
47
+ * @default true
48
+ */
49
+ includeServiceWorker?: boolean;
50
+ }
51
+
8
52
  /**
9
53
  * HTML template for the sandbox page.
10
54
  * This loads almostnode and handles postMessage communication with the parent.
11
55
  *
12
- * @param justNodeUrl - URL to load almostnode from (e.g., unpkg, jsdelivr, or your CDN)
56
+ * @param options - Configuration options or legacy URL string
13
57
  */
14
- export function getSandboxHtml(justNodeUrl = 'https://unpkg.com/almostnode/dist/index.js'): string {
58
+ export function getSandboxHtml(options: SandboxHtmlOptions | string = {}): string {
59
+ // Support legacy string argument
60
+ const opts: SandboxHtmlOptions = typeof options === 'string'
61
+ ? { almostnodeUrl: options }
62
+ : options;
63
+
64
+ const almostnodeUrl = opts.almostnodeUrl ?? 'https://unpkg.com/almostnode/dist/index.js';
65
+ const includeServiceWorker = opts.includeServiceWorker ?? true;
66
+
67
+ const serviceWorkerScript = includeServiceWorker ? `
68
+ // Register service worker for dev server support
69
+ if ('serviceWorker' in navigator) {
70
+ navigator.serviceWorker.register('/__sw__.js', { scope: '/' })
71
+ .then(reg => console.log('[Sandbox] Service worker registered'))
72
+ .catch(err => console.warn('[Sandbox] Service worker registration failed:', err));
73
+ }
74
+ ` : '';
75
+
15
76
  return `<!DOCTYPE html>
16
77
  <html>
17
78
  <head>
@@ -20,7 +81,8 @@ export function getSandboxHtml(justNodeUrl = 'https://unpkg.com/almostnode/dist/
20
81
  </head>
21
82
  <body>
22
83
  <script type="module">
23
- import { VirtualFS, Runtime } from '${justNodeUrl}';
84
+ import { VirtualFS, Runtime } from '${almostnodeUrl}';
85
+ ${serviceWorkerScript}
24
86
 
25
87
  let vfs = null;
26
88
  let runtime = null;
@@ -122,29 +184,59 @@ export function getSandboxVercelConfig(): object {
122
184
  };
123
185
  }
124
186
 
187
+ export interface GenerateSandboxFilesOptions extends SandboxHtmlOptions {
188
+ // Inherits almostnodeUrl and includeServiceWorker from SandboxHtmlOptions
189
+ }
190
+
125
191
  /**
126
- * Generate all files needed for deploying a sandbox to Vercel.
192
+ * Generate all files needed for deploying a sandbox to Vercel or other platforms.
127
193
  *
128
- * @param justNodeUrl - URL to load almostnode from
194
+ * @param options - Configuration options or legacy URL string
129
195
  * @returns Object with file names as keys and content as values
130
196
  *
131
197
  * @example
132
198
  * ```typescript
133
- * import { generateSandboxFiles } from 'almostnode/sandbox-helpers';
199
+ * import { generateSandboxFiles } from 'almostnode';
200
+ * import fs from 'fs';
134
201
  *
135
202
  * const files = generateSandboxFiles();
203
+ *
136
204
  * // Write files to sandbox/ directory
205
+ * fs.mkdirSync('sandbox', { recursive: true });
206
+ * for (const [filename, content] of Object.entries(files)) {
207
+ * fs.writeFileSync(`sandbox/${filename}`, content);
208
+ * }
209
+ *
137
210
  * // Deploy to Vercel: cd sandbox && vercel --prod
138
211
  * ```
139
212
  */
140
- export function generateSandboxFiles(justNodeUrl?: string): {
213
+ export function generateSandboxFiles(options: GenerateSandboxFilesOptions | string = {}): {
141
214
  'index.html': string;
142
215
  'vercel.json': string;
216
+ '__sw__.js'?: string;
143
217
  } {
144
- return {
145
- 'index.html': getSandboxHtml(justNodeUrl),
218
+ // Support legacy string argument
219
+ const opts: GenerateSandboxFilesOptions = typeof options === 'string'
220
+ ? { almostnodeUrl: options }
221
+ : options;
222
+
223
+ const includeServiceWorker = opts.includeServiceWorker ?? true;
224
+ const swContent = includeServiceWorker ? getServiceWorkerContent() : null;
225
+
226
+ const files: {
227
+ 'index.html': string;
228
+ 'vercel.json': string;
229
+ '__sw__.js'?: string;
230
+ } = {
231
+ 'index.html': getSandboxHtml(opts),
146
232
  'vercel.json': JSON.stringify(getSandboxVercelConfig(), null, 2),
147
233
  };
234
+
235
+ if (swContent) {
236
+ files['__sw__.js'] = swContent;
237
+ }
238
+
239
+ return files;
148
240
  }
149
241
 
150
242
  /**
@@ -38,6 +38,14 @@ export interface BridgeOptions {
38
38
  onServerReady?: (port: number, url: string) => void;
39
39
  }
40
40
 
41
+ export interface InitServiceWorkerOptions {
42
+ /**
43
+ * The URL path to the service worker file
44
+ * @default '/__sw__.js'
45
+ */
46
+ swUrl?: string;
47
+ }
48
+
41
49
  /**
42
50
  * Server Bridge manages virtual HTTP servers and routes requests
43
51
  */
@@ -146,14 +154,18 @@ export class ServerBridge extends EventEmitter {
146
154
 
147
155
  /**
148
156
  * Initialize Service Worker communication
157
+ * @param options - Configuration options for the service worker
158
+ * @param options.swUrl - Custom URL path to the service worker file (default: '/__sw__.js')
149
159
  */
150
- async initServiceWorker(): Promise<void> {
160
+ async initServiceWorker(options?: InitServiceWorkerOptions): Promise<void> {
151
161
  if (!('serviceWorker' in navigator)) {
152
162
  throw new Error('Service Workers not supported');
153
163
  }
154
164
 
165
+ const swUrl = options?.swUrl ?? '/__sw__.js';
166
+
155
167
  // Register service worker
156
- const registration = await navigator.serviceWorker.register('/__sw__.js', {
168
+ const registration = await navigator.serviceWorker.register(swUrl, {
157
169
  scope: '/',
158
170
  });
159
171
 
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Vite Plugin for almostnode
3
+ *
4
+ * Serves the service worker file from the package's dist directory,
5
+ * enabling seamless integration when almostnode is installed as an npm package.
6
+ */
7
+
8
+ import type { Plugin, ViteDevServer } from 'vite';
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
14
+ // @ts-ignore - import.meta.url is available in ESM
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+
17
+ export interface AlmostnodePluginOptions {
18
+ /**
19
+ * The URL path where the service worker will be served
20
+ * @default '/__sw__.js'
21
+ */
22
+ swPath?: string;
23
+ }
24
+
25
+ /**
26
+ * Vite plugin that serves the almostnode service worker file.
27
+ *
28
+ * When almostnode is installed as an npm package, the service worker file
29
+ * is located at node_modules/almostnode/dist/__sw__.js but the browser
30
+ * tries to load it from the root URL (/__sw__.js). This plugin intercepts
31
+ * requests to the service worker path and serves the file from the correct location.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * // vite.config.ts
36
+ * import { defineConfig } from 'vite';
37
+ * import { almostnodePlugin } from 'almostnode/vite';
38
+ *
39
+ * export default defineConfig({
40
+ * plugins: [almostnodePlugin()]
41
+ * });
42
+ * ```
43
+ */
44
+ export function almostnodePlugin(options: AlmostnodePluginOptions = {}): Plugin {
45
+ const swPath = options.swPath || '/__sw__.js';
46
+
47
+ return {
48
+ name: 'almostnode',
49
+
50
+ configureServer(server: ViteDevServer) {
51
+ server.middlewares.use(swPath, (_req, res) => {
52
+ // The service worker file is in the dist directory relative to this file
53
+ // In src: ../dist/__sw__.js
54
+ // In dist: ./__sw__.js
55
+ let swFilePath = path.join(__dirname, '__sw__.js');
56
+
57
+ // If running from src directory during development, look in dist
58
+ if (!fs.existsSync(swFilePath)) {
59
+ swFilePath = path.join(__dirname, '../dist/__sw__.js');
60
+ }
61
+
62
+ if (!fs.existsSync(swFilePath)) {
63
+ res.statusCode = 404;
64
+ res.end('Service worker file not found. Make sure almostnode is built.');
65
+ return;
66
+ }
67
+
68
+ res.setHeader('Content-Type', 'application/javascript');
69
+ res.setHeader('Cache-Control', 'no-cache');
70
+ res.end(fs.readFileSync(swFilePath));
71
+ });
72
+ },
73
+ };
74
+ }
75
+
76
+ export default almostnodePlugin;