@jxsuite/import 0.41.1 → 0.42.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/import",
3
- "version": "0.41.1",
3
+ "version": "0.42.0",
4
4
  "description": "Clone a live website into a Jx project",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,9 +35,9 @@
35
35
  "import": "bun run src/cli.ts"
36
36
  },
37
37
  "dependencies": {
38
- "@jxsuite/markup": "^1.0.1",
39
- "@jxsuite/protocol": "^5.0.0",
40
- "@jxsuite/schema": "^4.1.0",
38
+ "@jxsuite/markup": "^1.0.2",
39
+ "@jxsuite/protocol": "^6.0.0",
40
+ "@jxsuite/schema": "^5.0.0",
41
41
  "pixelmatch": "^8.0.0",
42
42
  "pngjs": "^7.0.0",
43
43
  "puppeteer-core": "^25.12.0"
@@ -49,6 +49,6 @@
49
49
  "happy-dom": "^21.0.3"
50
50
  },
51
51
  "optionalDependencies": {
52
- "@jxsuite/compiler": "^7.0.0"
52
+ "@jxsuite/compiler": "^8.0.0"
53
53
  }
54
54
  }
@@ -10,6 +10,7 @@
10
10
  * the same pipeline against a `@cloudflare/puppeteer` session.
11
11
  */
12
12
 
13
+ import { which } from "@jxsuite/schema/which";
13
14
  import { launch } from "puppeteer-core";
14
15
  import type { Browser } from "puppeteer-core";
15
16
 
@@ -30,7 +31,7 @@ function findChrome(executablePath?: string): string {
30
31
  }
31
32
 
32
33
  for (const name of DEFAULT_CHROME_PATHS) {
33
- const path = Bun.which(name);
34
+ const path = which(name);
34
35
  if (path) {
35
36
  return path;
36
37
  }
package/src/io.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  * `node:fs`, and a Worker supplies its own without the pipeline knowing the difference.
9
9
  */
10
10
 
11
- import { mkdir } from "node:fs/promises";
11
+ import { mkdir, writeFile } from "node:fs/promises";
12
12
  import { dirname, join } from "node:path";
13
13
 
14
14
  export interface ImportIo {
@@ -43,7 +43,7 @@ export function createLocalIo(outDir: string): ImportIo {
43
43
  async write(relPath, data) {
44
44
  const path = resolve(relPath);
45
45
  await mkdir(dirname(path), { recursive: true });
46
- await Bun.write(path, data);
46
+ await writeFile(path, data);
47
47
  },
48
48
  async mkdir(relPath) {
49
49
  await mkdir(resolve(relPath), { recursive: true });
package/src/run.ts CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { existsSync, readdirSync } from "node:fs";
12
12
  import { isAbsolute, join } from "node:path";
13
- import { mkdir } from "node:fs/promises";
13
+ import { mkdir, writeFile } from "node:fs/promises";
14
14
  import { closeBrowser, launchBrowser } from "./browser-local.ts";
15
15
  import { createLocalIo } from "./io.ts";
16
16
  import { runImportPipeline } from "./pipeline.ts";
@@ -234,7 +234,7 @@ export async function importSite(
234
234
  async function seedProject(): Promise<void> {
235
235
  await mkdir(outDir, { recursive: true });
236
236
  const seed = { name: seedName(url), imports: {}, images: { optimize: false } };
237
- await Bun.write(join(outDir, PROJECT_FILE), `${JSON.stringify(seed, null, 2)}\n`);
237
+ await writeFile(join(outDir, PROJECT_FILE), `${JSON.stringify(seed, null, 2)}\n`);
238
238
  onProgress?.({
239
239
  message: `Created ${outDir}`,
240
240
  phase: "seed",
package/src/verify.ts CHANGED
@@ -7,8 +7,10 @@
7
7
  * during import.
8
8
  */
9
9
 
10
+ import { createServer } from "node:http";
11
+ import type { Server } from "node:http";
10
12
  import { extname, join } from "node:path";
11
- import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
13
+ import { createReadStream, readFileSync, statSync, writeFileSync, mkdirSync } from "node:fs";
12
14
  import { isFunction } from "@jxsuite/schema/guards";
13
15
  import type { Browser, Page } from "puppeteer-core";
14
16
  import { launchBrowser, closeBrowser } from "./browser-local.ts";
@@ -240,37 +242,57 @@ const CONTENT_TYPES = new Map<string, string>(
240
242
 
241
243
  /** A running static server and the origin it answers on. */
242
244
  export interface ServedDirectory {
243
- server: ReturnType<typeof Bun.serve>;
245
+ server: { stop: () => Promise<void> };
244
246
  baseUrl: string;
245
247
  }
246
248
 
249
+ /** True when `path` names a regular file. */
250
+ function isFile(path: string): boolean {
251
+ try {
252
+ return statSync(path).isFile();
253
+ } catch {
254
+ return false;
255
+ }
256
+ }
257
+
247
258
  /**
248
- * Serve a directory over HTTP using Bun's built-in server. Returns the server instance and the base
249
- * URL.
259
+ * Serve a directory over loopback HTTP. Resolves with a stop handle and the base URL once the
260
+ * server is listening.
261
+ *
262
+ * `node:http` rather than `Bun.serve`: verification runs inside the desktop backend, which is
263
+ * Electron's Node (specs/desktop.md §7.1), and a static server needs nothing either runtime lacks.
250
264
  */
251
- export function serveDirectory(dir: string): ServedDirectory {
252
- const server = Bun.serve({
253
- port: 0,
254
- async fetch(req) {
255
- let { pathname } = new URL(req.url);
256
- if (pathname === "/") {
257
- pathname = "/index.html";
258
- } else if (!pathname.includes(".")) {
259
- pathname = `${pathname}/index.html`;
260
- }
261
-
262
- const filePath = join(dir, pathname);
263
- const file = Bun.file(filePath);
264
- if (await file.exists()) {
265
- const contentType =
266
- CONTENT_TYPES.get(extname(filePath).toLowerCase()) ?? "application/octet-stream";
267
- return new Response(file, { headers: { "Content-Type": contentType } });
268
- }
269
- return new Response("Not Found", { status: 404 });
270
- },
265
+ export async function serveDirectory(dir: string): Promise<ServedDirectory> {
266
+ const http: Server = createServer((req, res) => {
267
+ let { pathname } = new URL(req.url ?? "/", "http://localhost");
268
+ if (pathname === "/") {
269
+ pathname = "/index.html";
270
+ } else if (!pathname.includes(".")) {
271
+ pathname = `${pathname}/index.html`;
272
+ }
273
+ const filePath = join(dir, pathname);
274
+ if (!isFile(filePath)) {
275
+ res.writeHead(404).end("Not Found");
276
+ return;
277
+ }
278
+ const contentType =
279
+ CONTENT_TYPES.get(extname(filePath).toLowerCase()) ?? "application/octet-stream";
280
+ res.writeHead(200, { "Content-Type": contentType });
281
+ createReadStream(filePath).pipe(res);
271
282
  });
272
-
273
- return { server, baseUrl: `http://localhost:${server.port}` };
283
+ await new Promise<void>((resolve) => {
284
+ http.listen(0, "127.0.0.1", resolve);
285
+ });
286
+ /* SAFETY: a TCP listener's `address()` is an `AddressInfo`; it is a string only for a pipe. */
287
+ const { port } = http.address() as { port: number };
288
+ const server = {
289
+ stop: () =>
290
+ new Promise<void>((resolve) => {
291
+ http.closeAllConnections();
292
+ http.close(() => resolve());
293
+ }),
294
+ };
295
+ return { server, baseUrl: `http://127.0.0.1:${port}` };
274
296
  }
275
297
 
276
298
  function resolveScreenshot(ref: Buffer | string): Buffer {
@@ -339,7 +361,7 @@ export async function verifyProject(opts: VerifyOptions): Promise<VerifyResult>
339
361
 
340
362
  // Step 2: Serve the built output
341
363
  onProgress("Starting local server for rendered pages...");
342
- const { server, baseUrl } = serveDirectory(distDir);
364
+ const { server, baseUrl } = await serveDirectory(distDir);
343
365
 
344
366
  const browser = opts.browser ?? (await launchBrowser());
345
367
  const results: PageVerifyResult[] = [];