sproutboat 0.11.11 → 0.11.13
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/CHANGELOG.md +35 -2
- package/SURFACE.md +2 -2
- package/THIRD_PARTY_NOTICES.md +3 -5
- package/package.json +9 -10
- package/scripts/build-cli.ts +2 -44
- package/src/bearssl.ts +2 -1
- package/src/build.ts +208 -118
- package/src/bundle.ts +31 -3
- package/src/compile-cache.ts +98 -0
- package/src/compile.ts +28 -37
- package/src/dev.ts +10 -0
- package/src/main.ts +1 -0
- package/src/node-shims.ts +378 -0
- package/src/standalone-build.ts +4 -14
- package/src/toolchain.ts +3 -8
package/src/bundle.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* #89 — bundle the handler before Porffor sees it.
|
|
3
3
|
*
|
|
4
|
-
* Porffor
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Porffor alpha 9 supports ESM imports. We still bundle first so capability
|
|
5
|
+
* validation sees every dependency and the runtime wrapper receives one
|
|
6
|
+
* controlled default export.
|
|
7
7
|
*
|
|
8
8
|
* Bun's bundler resolves relative imports across the project and bare
|
|
9
9
|
* specifiers out of the project's own `node_modules`, then emits a single ESM
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* reaching for a Node API fails exactly as user code would.
|
|
13
13
|
*/
|
|
14
14
|
import { relative } from "node:path";
|
|
15
|
+
import { NODE_SHIMS } from "./node-shims";
|
|
15
16
|
|
|
16
17
|
export type BundleResult = {
|
|
17
18
|
/** One self-contained ESM module: what gets validated, hashed, and compiled. */
|
|
@@ -27,6 +28,32 @@ export class BundleError extends Error {
|
|
|
27
28
|
|
|
28
29
|
const entryLabel = (entryPath: string, projectDir: string): string => relative(projectDir, entryPath) || entryPath;
|
|
29
30
|
|
|
31
|
+
const shimSpecifiers = new RegExp(
|
|
32
|
+
`^(${Object.keys(NODE_SHIMS)
|
|
33
|
+
.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
34
|
+
.join("|")})$`,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Bun already bundles working polyfills for most node:* built-ins under
|
|
39
|
+
* `target: "browser"` (verified for node:url, node:querystring). This
|
|
40
|
+
* plugin only overrides the ones in NODE_SHIMS — currently just node:path,
|
|
41
|
+
* whose bundled resolve() otherwise falls back to `process.cwd()`.
|
|
42
|
+
*/
|
|
43
|
+
const nodeShimsPlugin: import("bun").BunPlugin = {
|
|
44
|
+
name: "sproutboat-node-shims",
|
|
45
|
+
setup(build) {
|
|
46
|
+
build.onResolve({ filter: shimSpecifiers }, (args) => ({ path: args.path, namespace: "sb-node-shim" }));
|
|
47
|
+
build.onLoad({ filter: /.*/, namespace: "sb-node-shim" }, (args) => ({
|
|
48
|
+
// SAFETY: onResolve above only ever routes a specifier matched by
|
|
49
|
+
// shimSpecifiers (built from NODE_SHIMS's own keys) into this
|
|
50
|
+
// namespace, so args.path is always one of NODE_SHIMS's keys here.
|
|
51
|
+
contents: (NODE_SHIMS as Record<string, string>)[args.path],
|
|
52
|
+
loader: "js",
|
|
53
|
+
}));
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
30
57
|
/** Bun reports resolution failures on `AggregateError.errors`; its own message is just "Bundle failed". */
|
|
31
58
|
function bundleDetail(cause: unknown): string {
|
|
32
59
|
const errors = cause instanceof AggregateError ? cause.errors : [];
|
|
@@ -51,6 +78,7 @@ export async function bundleHandler(entryPath: string, projectDir: string): Prom
|
|
|
51
78
|
minify: false,
|
|
52
79
|
splitting: false,
|
|
53
80
|
sourcemap: "none",
|
|
81
|
+
plugins: [nodeShimsPlugin],
|
|
54
82
|
});
|
|
55
83
|
} catch (cause) {
|
|
56
84
|
// An unresolvable specifier arrives as an AggregateError whose `errors`
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { chmod, mkdir, mkdtemp, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
type CacheRecord = { key: string; hash: string; size: number };
|
|
6
|
+
|
|
7
|
+
function digest(bytes: Uint8Array): string {
|
|
8
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function verified(entry: string, key: string): Promise<Buffer | null> {
|
|
12
|
+
try {
|
|
13
|
+
// SAFETY: cache.json is private cache data; the key, metadata and binary
|
|
14
|
+
// digest are all checked before this entry can be used.
|
|
15
|
+
const record = JSON.parse(await readFile(resolve(entry, "cache.json"), "utf8")) as CacheRecord;
|
|
16
|
+
if (record.key !== key) return null;
|
|
17
|
+
const path = resolve(entry, "sprout");
|
|
18
|
+
const info = await stat(path);
|
|
19
|
+
if (!info.isFile() || (info.mode & 0o111) === 0 || info.size !== record.size) return null;
|
|
20
|
+
const bytes = await readFile(path);
|
|
21
|
+
return digest(bytes) === record.hash ? bytes : null;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function publish(bytes: Uint8Array, outPath: string): Promise<void> {
|
|
28
|
+
await mkdir(dirname(outPath), { recursive: true });
|
|
29
|
+
const candidate = `${outPath}.${randomUUID()}.tmp`;
|
|
30
|
+
try {
|
|
31
|
+
await writeFile(candidate, bytes);
|
|
32
|
+
await chmod(candidate, 0o555);
|
|
33
|
+
await rename(candidate, outPath);
|
|
34
|
+
} finally {
|
|
35
|
+
await rm(candidate, { force: true });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A per-project binary cache. The lock covers compilation and publication, so
|
|
40
|
+
* another process can only read a complete, checksum-verified entry. */
|
|
41
|
+
export async function compileCached(
|
|
42
|
+
cacheRoot: string,
|
|
43
|
+
key: string,
|
|
44
|
+
outPath: string,
|
|
45
|
+
compile: (candidatePath: string) => Promise<void>,
|
|
46
|
+
): Promise<"hit" | "miss"> {
|
|
47
|
+
const entry = resolve(cacheRoot, key);
|
|
48
|
+
const cached = await verified(entry, key);
|
|
49
|
+
if (cached) {
|
|
50
|
+
await publish(cached, outPath);
|
|
51
|
+
return "hit";
|
|
52
|
+
}
|
|
53
|
+
await mkdir(cacheRoot, { recursive: true });
|
|
54
|
+
const lock = `${entry}.lock`;
|
|
55
|
+
const deadline = Date.now() + 12 * 60_000;
|
|
56
|
+
while (true) {
|
|
57
|
+
try {
|
|
58
|
+
await mkdir(lock);
|
|
59
|
+
break;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// SAFETY: Node filesystem errors expose code; unrelated failures are
|
|
62
|
+
// rethrown instead of being treated as a held lock.
|
|
63
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
64
|
+
const ready = await verified(entry, key);
|
|
65
|
+
if (ready) {
|
|
66
|
+
await publish(ready, outPath);
|
|
67
|
+
return "hit";
|
|
68
|
+
}
|
|
69
|
+
if (Date.now() > deadline) throw new Error(`timed out waiting for compile cache lock ${lock}`);
|
|
70
|
+
const lockInfo = await stat(lock).catch(() => null);
|
|
71
|
+
if (lockInfo && Date.now() - lockInfo.mtimeMs > 15 * 60_000) await rm(lock, { recursive: true, force: true });
|
|
72
|
+
else await Bun.sleep(100);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
let stage: string | undefined;
|
|
76
|
+
try {
|
|
77
|
+
const ready = await verified(entry, key);
|
|
78
|
+
if (ready) {
|
|
79
|
+
await publish(ready, outPath);
|
|
80
|
+
return "hit";
|
|
81
|
+
}
|
|
82
|
+
await rm(entry, { recursive: true, force: true });
|
|
83
|
+
stage = await mkdtemp(resolve(cacheRoot, `.candidate-${key}-`));
|
|
84
|
+
const candidate = resolve(stage, "sprout");
|
|
85
|
+
await compile(candidate);
|
|
86
|
+
const bytes = await readFile(candidate);
|
|
87
|
+
const record: CacheRecord = { key, hash: digest(bytes), size: bytes.byteLength };
|
|
88
|
+
await chmod(candidate, 0o555);
|
|
89
|
+
await writeFile(resolve(stage, "cache.json"), JSON.stringify(record));
|
|
90
|
+
await rename(stage, entry);
|
|
91
|
+
stage = undefined;
|
|
92
|
+
await publish(bytes, outPath);
|
|
93
|
+
return "miss";
|
|
94
|
+
} finally {
|
|
95
|
+
if (stage) await rm(stage, { recursive: true, force: true });
|
|
96
|
+
await rm(lock, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/compile.ts
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* unusable it stops with its specific integrity or archive error. Contributors
|
|
11
11
|
* may explicitly opt into Porffor's source fallback.
|
|
12
12
|
*/
|
|
13
|
-
import { existsSync } from "node:fs";
|
|
14
13
|
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
15
14
|
import { dirname, resolve } from "node:path";
|
|
16
15
|
import { ensurePorfforPatched } from "./patch-porffor";
|
|
@@ -50,8 +49,12 @@ const COMPILE_TIMEOUT_MS = Number(process.env.SPROUTBOAT_COMPILE_TIMEOUT_MS || 6
|
|
|
50
49
|
|
|
51
50
|
export type CompileInput = {
|
|
52
51
|
sourcePath: string;
|
|
52
|
+
/** Stable compiler input for dev rebuilds, independent of the candidate binary path. */
|
|
53
|
+
generatedPath?: string;
|
|
53
54
|
/** The bundled module (#89). Falls back to reading `sourcePath` verbatim. */
|
|
54
55
|
source?: string;
|
|
56
|
+
/** The exact wrapped module used to compute a verified compile-cache key. */
|
|
57
|
+
generatedSource?: string;
|
|
55
58
|
outPath: string;
|
|
56
59
|
vars: Record<string, string>;
|
|
57
60
|
bindings?: Bindings;
|
|
@@ -195,26 +198,29 @@ export async function compileSprout(input: CompileInput): Promise<void> {
|
|
|
195
198
|
// OS may still be executing. The linker cannot overwrite either, so clear it
|
|
196
199
|
// first rather than failing with "can't write output file".
|
|
197
200
|
await rm(input.outPath, { force: true });
|
|
198
|
-
const generatedPath = resolve(outDir, "sprout.generated.js");
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
201
|
+
const generatedPath = input.generatedPath ?? resolve(outDir, "sprout.generated.js");
|
|
202
|
+
await mkdir(dirname(generatedPath), { recursive: true });
|
|
203
|
+
const generatedSource =
|
|
204
|
+
input.generatedSource ??
|
|
205
|
+
(await (async () => {
|
|
206
|
+
const [source, prelude] = await Promise.all([
|
|
207
|
+
input.source === undefined ? readFile(input.sourcePath, "utf8") : Promise.resolve(input.source),
|
|
208
|
+
loadPrelude(input.transport ?? "broker"),
|
|
209
|
+
]);
|
|
210
|
+
return wrapNativeFetchHandler(
|
|
211
|
+
source,
|
|
212
|
+
prelude,
|
|
213
|
+
input.vars,
|
|
214
|
+
input.bindings ?? EMPTY_BINDINGS,
|
|
215
|
+
undefined,
|
|
216
|
+
input.compatibilityDate,
|
|
217
|
+
input.appName,
|
|
218
|
+
input.assets,
|
|
219
|
+
input.transport,
|
|
220
|
+
input.versionMetadata,
|
|
221
|
+
);
|
|
222
|
+
})());
|
|
223
|
+
await writeFile(generatedPath, generatedSource);
|
|
218
224
|
|
|
219
225
|
const launcher = resolve(porffor, "runtime/index.js");
|
|
220
226
|
// A musl build shells `zig` bare (Porffor hardcodes the literal command), so
|
|
@@ -222,22 +228,7 @@ export async function compileSprout(input: CompileInput): Promise<void> {
|
|
|
222
228
|
// below instead and has no zigBin to contribute here.
|
|
223
229
|
const zigDir = input.zigBin ? `${dirname(input.zigBin)}:` : "";
|
|
224
230
|
const hostCompiler = input.target === "host" ? await resolveHostCompiler() : undefined;
|
|
225
|
-
|
|
226
|
-
// there. Running from the npm package under Bun, `process.execPath` is Bun
|
|
227
|
-
// itself and `npm i -g` puts no dependency `.bin` on PATH, so resolve the
|
|
228
|
-
// esbuild dependency directly. `Bun.which` is the last resort.
|
|
229
|
-
const packagedEsbuild = resolve(dirname(process.execPath), "esbuild");
|
|
230
|
-
let esbuild: string | null = existsSync(packagedEsbuild) ? packagedEsbuild : null;
|
|
231
|
-
if (!esbuild) {
|
|
232
|
-
try {
|
|
233
|
-
esbuild = Bun.resolveSync("esbuild/bin/esbuild", import.meta.dir);
|
|
234
|
-
} catch {
|
|
235
|
-
esbuild = null;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
esbuild ??= Bun.which("esbuild");
|
|
239
|
-
if (!esbuild) throw new Error("esbuild could not be located; install it or reinstall sproutboat");
|
|
240
|
-
const path = `${zigDir}${dirname(esbuild)}:${process.env.PATH ?? ""}`;
|
|
231
|
+
const path = `${zigDir}${process.env.PATH ?? ""}`;
|
|
241
232
|
const command = PACKAGED
|
|
242
233
|
? [process.execPath, "__porffor", launcher]
|
|
243
234
|
: [process.execPath, resolve(import.meta.dir, "main.ts"), "__porffor", launcher];
|
package/src/dev.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* project, one port.
|
|
12
12
|
*/
|
|
13
13
|
import { existsSync, watch, type FSWatcher } from "node:fs";
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
14
15
|
import { mkdir, readFile, rm } from "node:fs/promises";
|
|
15
16
|
import { connect } from "node:net";
|
|
16
17
|
import { dirname, resolve } from "node:path";
|
|
@@ -21,6 +22,7 @@ import { amber, dim, leaf, ok } from "./style";
|
|
|
21
22
|
import type { SproutboatConfig } from "./config";
|
|
22
23
|
|
|
23
24
|
const RESTART_DEBOUNCE_MS = 120;
|
|
25
|
+
const DEV_COMPILE_SESSION = randomUUID();
|
|
24
26
|
|
|
25
27
|
export type DevInput = {
|
|
26
28
|
projectDir: string;
|
|
@@ -121,6 +123,14 @@ async function start(input: DevInput, port: number): Promise<Running> {
|
|
|
121
123
|
optimize: "dev",
|
|
122
124
|
reuseSproutPath: input.reuseSproutPath,
|
|
123
125
|
outputDirectory: candidateDir,
|
|
126
|
+
// Alpha 9 keys its C-unit cache by the entry's absolute path. Keep that
|
|
127
|
+
// path fixed across saves while each candidate keeps its own binary.
|
|
128
|
+
generatedPath: resolve(
|
|
129
|
+
input.projectDir,
|
|
130
|
+
".sproutboat/dev-compile",
|
|
131
|
+
`${process.pid}-${DEV_COMPILE_SESSION}`,
|
|
132
|
+
"sprout.generated.js",
|
|
133
|
+
),
|
|
124
134
|
});
|
|
125
135
|
} catch (error) {
|
|
126
136
|
await rm(candidateDir, { recursive: true, force: true });
|
package/src/main.ts
CHANGED
|
@@ -319,6 +319,7 @@ async function build(directory?: string, target: "linux-x86_64" | "host" = "linu
|
|
|
319
319
|
target,
|
|
320
320
|
});
|
|
321
321
|
console.log(ok(`built ${project.config.name}`));
|
|
322
|
+
console.log(dim(` compile ${artifact.compileMs} ms · binary cache ${artifact.compileCache}`));
|
|
322
323
|
if (target === "host")
|
|
323
324
|
console.log(dim(" host build — runs here, not deployable; drop --target host to build for a box"));
|
|
324
325
|
console.log(artifact.artifactDir);
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun's bundler ships full Node-compatible polyfills for node:path,
|
|
3
|
+
* node:url, and node:querystring under `target: "browser"`, but each has
|
|
4
|
+
* exactly one thing wrong with it for a Porffor-compiled handler — found
|
|
5
|
+
* by auditing bundled output against the capability check (source.ts) and,
|
|
6
|
+
* for node:url, by an actual failure through the real Porffor compiler.
|
|
7
|
+
* Auditing the bundle alone was not enough:
|
|
8
|
+
*
|
|
9
|
+
* - node:path's bundled `resolve()` falls back to `process.cwd()`, which
|
|
10
|
+
* trips the `process.` ban in `../packages/runtime/src/source.ts`.
|
|
11
|
+
* - node:querystring's bundled polyfill pulls in Bun's full Buffer
|
|
12
|
+
* implementation internally (for percent-decoding), tripping the
|
|
13
|
+
* `Buffer.` ban.
|
|
14
|
+
* - node:url's bundled `URL`/`URLSearchParams` exports are
|
|
15
|
+
* `var { URL, URLSearchParams } = globalThis;` — passes every capability
|
|
16
|
+
* check (no banned pattern in it) but Porffor's compiler rejects it at
|
|
17
|
+
* parse time as `Identifier 'URL' has already been declared`: Porffor
|
|
18
|
+
* pre-binds `URL` as a global, and a second top-level declaration of
|
|
19
|
+
* that exact name is a compile error, confirmed by actually building a
|
|
20
|
+
* handler through `sproutboat build` rather than just checking the
|
|
21
|
+
* bundle. `URL`/`URLSearchParams` are already global in a compiled
|
|
22
|
+
* handler (see `docs/concepts/handler.mdx`), so nothing needs importing
|
|
23
|
+
* — the fix is a shim that re-exports the globals under aliased local
|
|
24
|
+
* names instead of declaring anything literally named `URL`.
|
|
25
|
+
*
|
|
26
|
+
* None of these are the capability check being wrong — every case is a
|
|
27
|
+
* real thing (`process.cwd()`, `Buffer`, a name Porffor itself reserves)
|
|
28
|
+
* that doesn't exist the way a compiled handler needs it to, so the shims
|
|
29
|
+
* replace it rather than the check being loosened.
|
|
30
|
+
*/
|
|
31
|
+
const NODE_PATH_SHIM = `
|
|
32
|
+
function assertPath(path) {
|
|
33
|
+
if (typeof path !== "string")
|
|
34
|
+
throw TypeError("Path must be a string. Received " + JSON.stringify(path));
|
|
35
|
+
}
|
|
36
|
+
function normalizeStringPosix(path, allowAboveRoot) {
|
|
37
|
+
var res = "", lastSegmentLength = 0, lastSlash = -1, dots = 0, code;
|
|
38
|
+
for (var i = 0; i <= path.length; ++i) {
|
|
39
|
+
if (i < path.length) code = path.charCodeAt(i);
|
|
40
|
+
else if (code === 47) break;
|
|
41
|
+
else code = 47;
|
|
42
|
+
if (code === 47) {
|
|
43
|
+
if (lastSlash === i - 1 || dots === 1) {
|
|
44
|
+
} else if (lastSlash !== i - 1 && dots === 2) {
|
|
45
|
+
if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 || res.charCodeAt(res.length - 2) !== 46) {
|
|
46
|
+
if (res.length > 2) {
|
|
47
|
+
var lastSlashIndex = res.lastIndexOf("/");
|
|
48
|
+
if (lastSlashIndex !== res.length - 1) {
|
|
49
|
+
if (lastSlashIndex === -1) res = "", lastSegmentLength = 0;
|
|
50
|
+
else res = res.slice(0, lastSlashIndex), lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
|
|
51
|
+
lastSlash = i, dots = 0;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
} else if (res.length === 2 || res.length === 1) {
|
|
55
|
+
res = "", lastSegmentLength = 0, lastSlash = i, dots = 0;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (allowAboveRoot) {
|
|
60
|
+
if (res.length > 0) res += "/..";
|
|
61
|
+
else res = "..";
|
|
62
|
+
lastSegmentLength = 2;
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
if (res.length > 0) res += "/" + path.slice(lastSlash + 1, i);
|
|
66
|
+
else res = path.slice(lastSlash + 1, i);
|
|
67
|
+
lastSegmentLength = i - lastSlash - 1;
|
|
68
|
+
}
|
|
69
|
+
lastSlash = i, dots = 0;
|
|
70
|
+
} else if (code === 46 && dots !== -1) ++dots;
|
|
71
|
+
else dots = -1;
|
|
72
|
+
}
|
|
73
|
+
return res;
|
|
74
|
+
}
|
|
75
|
+
function _format(sep, pathObject) {
|
|
76
|
+
var dir = pathObject.dir || pathObject.root, base = pathObject.base || (pathObject.name || "") + (pathObject.ext || "");
|
|
77
|
+
if (!dir) return base;
|
|
78
|
+
if (dir === pathObject.root) return dir + base;
|
|
79
|
+
return dir + sep + base;
|
|
80
|
+
}
|
|
81
|
+
function resolve() {
|
|
82
|
+
// Node falls back to process.cwd() here; a compiled handler has no real
|
|
83
|
+
// filesystem or cwd, so an all-relative resolve() resolves against a
|
|
84
|
+
// fixed virtual root instead.
|
|
85
|
+
var resolvedPath = "", resolvedAbsolute = false, cwd;
|
|
86
|
+
for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
|
|
87
|
+
var path;
|
|
88
|
+
if (i >= 0) path = arguments[i];
|
|
89
|
+
else {
|
|
90
|
+
if (cwd === undefined) cwd = "/";
|
|
91
|
+
path = cwd;
|
|
92
|
+
}
|
|
93
|
+
if (assertPath(path), path.length === 0) continue;
|
|
94
|
+
resolvedPath = path + "/" + resolvedPath, resolvedAbsolute = path.charCodeAt(0) === 47;
|
|
95
|
+
}
|
|
96
|
+
if (resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute), resolvedAbsolute)
|
|
97
|
+
if (resolvedPath.length > 0) return "/" + resolvedPath;
|
|
98
|
+
else return "/";
|
|
99
|
+
else if (resolvedPath.length > 0) return resolvedPath;
|
|
100
|
+
else return ".";
|
|
101
|
+
}
|
|
102
|
+
function normalize(path) {
|
|
103
|
+
if (assertPath(path), path.length === 0) return ".";
|
|
104
|
+
var isAbsolute = path.charCodeAt(0) === 47, trailingSeparator = path.charCodeAt(path.length - 1) === 47;
|
|
105
|
+
if (path = normalizeStringPosix(path, !isAbsolute), path.length === 0 && !isAbsolute) path = ".";
|
|
106
|
+
if (path.length > 0 && trailingSeparator) path += "/";
|
|
107
|
+
if (isAbsolute) return "/" + path;
|
|
108
|
+
return path;
|
|
109
|
+
}
|
|
110
|
+
function isAbsolute(path) {
|
|
111
|
+
return assertPath(path), path.length > 0 && path.charCodeAt(0) === 47;
|
|
112
|
+
}
|
|
113
|
+
function join() {
|
|
114
|
+
if (arguments.length === 0) return ".";
|
|
115
|
+
var joined;
|
|
116
|
+
for (var i = 0; i < arguments.length; ++i) {
|
|
117
|
+
var arg = arguments[i];
|
|
118
|
+
if (assertPath(arg), arg.length > 0)
|
|
119
|
+
if (joined === undefined) joined = arg;
|
|
120
|
+
else joined += "/" + arg;
|
|
121
|
+
}
|
|
122
|
+
if (joined === undefined) return ".";
|
|
123
|
+
return normalize(joined);
|
|
124
|
+
}
|
|
125
|
+
function relative(from, to) {
|
|
126
|
+
if (assertPath(from), assertPath(to), from === to) return "";
|
|
127
|
+
if (from = resolve(from), to = resolve(to), from === to) return "";
|
|
128
|
+
var fromStart = 1;
|
|
129
|
+
for (; fromStart < from.length; ++fromStart) if (from.charCodeAt(fromStart) !== 47) break;
|
|
130
|
+
var fromEnd = from.length, fromLen = fromEnd - fromStart, toStart = 1;
|
|
131
|
+
for (; toStart < to.length; ++toStart) if (to.charCodeAt(toStart) !== 47) break;
|
|
132
|
+
var toEnd = to.length, toLen = toEnd - toStart, length = fromLen < toLen ? fromLen : toLen, lastCommonSep = -1, i = 0;
|
|
133
|
+
for (; i <= length; ++i) {
|
|
134
|
+
if (i === length) {
|
|
135
|
+
if (toLen > length) {
|
|
136
|
+
if (to.charCodeAt(toStart + i) === 47) return to.slice(toStart + i + 1);
|
|
137
|
+
else if (i === 0) return to.slice(toStart + i);
|
|
138
|
+
} else if (fromLen > length) {
|
|
139
|
+
if (from.charCodeAt(fromStart + i) === 47) lastCommonSep = i;
|
|
140
|
+
else if (i === 0) lastCommonSep = 0;
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
var fromCode = from.charCodeAt(fromStart + i), toCode = to.charCodeAt(toStart + i);
|
|
145
|
+
if (fromCode !== toCode) break;
|
|
146
|
+
else if (fromCode === 47) lastCommonSep = i;
|
|
147
|
+
}
|
|
148
|
+
var out = "";
|
|
149
|
+
for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i)
|
|
150
|
+
if (i === fromEnd || from.charCodeAt(i) === 47)
|
|
151
|
+
if (out.length === 0) out += "..";
|
|
152
|
+
else out += "/..";
|
|
153
|
+
if (out.length > 0) return out + to.slice(toStart + lastCommonSep);
|
|
154
|
+
else {
|
|
155
|
+
if (toStart += lastCommonSep, to.charCodeAt(toStart) === 47) ++toStart;
|
|
156
|
+
return to.slice(toStart);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function dirname(path) {
|
|
160
|
+
if (assertPath(path), path.length === 0) return ".";
|
|
161
|
+
var code = path.charCodeAt(0), hasRoot = code === 47, end = -1, matchedSlash = true;
|
|
162
|
+
for (var i = path.length - 1; i >= 1; --i)
|
|
163
|
+
if (code = path.charCodeAt(i), code === 47) {
|
|
164
|
+
if (!matchedSlash) { end = i; break; }
|
|
165
|
+
} else matchedSlash = false;
|
|
166
|
+
if (end === -1) return hasRoot ? "/" : ".";
|
|
167
|
+
if (hasRoot && end === 1) return "//";
|
|
168
|
+
return path.slice(0, end);
|
|
169
|
+
}
|
|
170
|
+
function basename(path, ext) {
|
|
171
|
+
if (ext !== undefined && typeof ext !== "string") throw TypeError('"ext" argument must be a string');
|
|
172
|
+
assertPath(path);
|
|
173
|
+
var start = 0, end = -1, matchedSlash = true, i;
|
|
174
|
+
if (ext !== undefined && ext.length > 0 && ext.length <= path.length) {
|
|
175
|
+
if (ext.length === path.length && ext === path) return "";
|
|
176
|
+
var extIdx = ext.length - 1, firstNonSlashEnd = -1;
|
|
177
|
+
for (i = path.length - 1; i >= 0; --i) {
|
|
178
|
+
var code = path.charCodeAt(i);
|
|
179
|
+
if (code === 47) {
|
|
180
|
+
if (!matchedSlash) { start = i + 1; break; }
|
|
181
|
+
} else {
|
|
182
|
+
if (firstNonSlashEnd === -1) matchedSlash = false, firstNonSlashEnd = i + 1;
|
|
183
|
+
if (extIdx >= 0)
|
|
184
|
+
if (code === ext.charCodeAt(extIdx)) { if (--extIdx === -1) end = i; }
|
|
185
|
+
else extIdx = -1, end = firstNonSlashEnd;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (start === end) end = firstNonSlashEnd;
|
|
189
|
+
else if (end === -1) end = path.length;
|
|
190
|
+
return path.slice(start, end);
|
|
191
|
+
} else {
|
|
192
|
+
for (i = path.length - 1; i >= 0; --i)
|
|
193
|
+
if (path.charCodeAt(i) === 47) {
|
|
194
|
+
if (!matchedSlash) { start = i + 1; break; }
|
|
195
|
+
} else if (end === -1) matchedSlash = false, end = i + 1;
|
|
196
|
+
if (end === -1) return "";
|
|
197
|
+
return path.slice(start, end);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function extname(path) {
|
|
201
|
+
assertPath(path);
|
|
202
|
+
var startDot = -1, startPart = 0, end = -1, matchedSlash = true, preDotState = 0;
|
|
203
|
+
for (var i = path.length - 1; i >= 0; --i) {
|
|
204
|
+
var code = path.charCodeAt(i);
|
|
205
|
+
if (code === 47) {
|
|
206
|
+
if (!matchedSlash) { startPart = i + 1; break; }
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (end === -1) matchedSlash = false, end = i + 1;
|
|
210
|
+
if (code === 46) { if (startDot === -1) startDot = i; else if (preDotState !== 1) preDotState = 1; }
|
|
211
|
+
else if (startDot !== -1) preDotState = -1;
|
|
212
|
+
}
|
|
213
|
+
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) return "";
|
|
214
|
+
return path.slice(startDot, end);
|
|
215
|
+
}
|
|
216
|
+
function format(pathObject) {
|
|
217
|
+
if (pathObject === null || typeof pathObject !== "object")
|
|
218
|
+
throw TypeError('The "pathObject" argument must be of type Object. Received type ' + typeof pathObject);
|
|
219
|
+
return _format("/", pathObject);
|
|
220
|
+
}
|
|
221
|
+
function parse(path) {
|
|
222
|
+
assertPath(path);
|
|
223
|
+
var ret = { root: "", dir: "", base: "", ext: "", name: "" };
|
|
224
|
+
if (path.length === 0) return ret;
|
|
225
|
+
var code = path.charCodeAt(0), isAbsolute2 = code === 47, start;
|
|
226
|
+
if (isAbsolute2) ret.root = "/", start = 1;
|
|
227
|
+
else start = 0;
|
|
228
|
+
var startDot = -1, startPart = 0, end = -1, matchedSlash = true, i = path.length - 1, preDotState = 0;
|
|
229
|
+
for (; i >= start; --i) {
|
|
230
|
+
if (code = path.charCodeAt(i), code === 47) {
|
|
231
|
+
if (!matchedSlash) { startPart = i + 1; break; }
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (end === -1) matchedSlash = false, end = i + 1;
|
|
235
|
+
if (code === 46) { if (startDot === -1) startDot = i; else if (preDotState !== 1) preDotState = 1; }
|
|
236
|
+
else if (startDot !== -1) preDotState = -1;
|
|
237
|
+
}
|
|
238
|
+
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
|
|
239
|
+
if (end !== -1)
|
|
240
|
+
if (startPart === 0 && isAbsolute2) ret.base = ret.name = path.slice(1, end);
|
|
241
|
+
else ret.base = ret.name = path.slice(startPart, end);
|
|
242
|
+
} else {
|
|
243
|
+
if (startPart === 0 && isAbsolute2) ret.name = path.slice(1, startDot), ret.base = path.slice(1, end);
|
|
244
|
+
else ret.name = path.slice(startPart, startDot), ret.base = path.slice(startPart, end);
|
|
245
|
+
ret.ext = path.slice(startDot, end);
|
|
246
|
+
}
|
|
247
|
+
if (startPart > 0) ret.dir = path.slice(0, startPart - 1);
|
|
248
|
+
else if (isAbsolute2) ret.dir = "/";
|
|
249
|
+
return ret;
|
|
250
|
+
}
|
|
251
|
+
var sep = "/";
|
|
252
|
+
var delimiter = ":";
|
|
253
|
+
var path = { resolve, normalize, isAbsolute, join, relative, dirname, basename, extname, format, parse, sep, delimiter };
|
|
254
|
+
path.posix = path;
|
|
255
|
+
path.win32 = path;
|
|
256
|
+
export default path;
|
|
257
|
+
export { resolve, normalize, isAbsolute, join, relative, dirname, basename, extname, format, parse, sep, delimiter };
|
|
258
|
+
`;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* node:querystring's bundled polyfill pulls in Bun's full Buffer
|
|
262
|
+
* implementation (used internally for percent-decoding), which the
|
|
263
|
+
* capability check correctly rejects — Buffer isn't something Porffor's
|
|
264
|
+
* compiled output can rely on. This shim implements parse/stringify/
|
|
265
|
+
* escape/unescape (plus the encode/decode aliases Node exports) over
|
|
266
|
+
* plain string ops and encodeURIComponent/decodeURIComponent instead.
|
|
267
|
+
*/
|
|
268
|
+
const NODE_QUERYSTRING_SHIM = `
|
|
269
|
+
function qsUnescape(str) {
|
|
270
|
+
try { return decodeURIComponent(String(str).replace(/\\+/g, " ")); }
|
|
271
|
+
catch { return String(str); }
|
|
272
|
+
}
|
|
273
|
+
function qsEscape(str) {
|
|
274
|
+
return encodeURIComponent(String(str));
|
|
275
|
+
}
|
|
276
|
+
function parse(qs, sep, eq, options) {
|
|
277
|
+
sep = sep || "&";
|
|
278
|
+
eq = eq || "=";
|
|
279
|
+
var obj = Object.create(null);
|
|
280
|
+
if (typeof qs !== "string" || qs.length === 0) return obj;
|
|
281
|
+
var decode = (options && options.decodeURIComponent) || qsUnescape;
|
|
282
|
+
var maxKeys = options && typeof options.maxKeys === "number" ? options.maxKeys : 1000;
|
|
283
|
+
var pairs = qs.split(sep);
|
|
284
|
+
var limit = maxKeys > 0 ? Math.min(pairs.length, maxKeys) : pairs.length;
|
|
285
|
+
for (var i = 0; i < limit; i++) {
|
|
286
|
+
var pair = pairs[i];
|
|
287
|
+
var eqIdx = pair.indexOf(eq);
|
|
288
|
+
var key, value;
|
|
289
|
+
if (eqIdx >= 0) { key = pair.slice(0, eqIdx); value = pair.slice(eqIdx + eq.length); }
|
|
290
|
+
else { key = pair; value = ""; }
|
|
291
|
+
key = decode(key);
|
|
292
|
+
value = decode(value);
|
|
293
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
294
|
+
if (Array.isArray(obj[key])) obj[key].push(value);
|
|
295
|
+
else obj[key] = [obj[key], value];
|
|
296
|
+
} else obj[key] = value;
|
|
297
|
+
}
|
|
298
|
+
return obj;
|
|
299
|
+
}
|
|
300
|
+
function stringify(obj, sep, eq, options) {
|
|
301
|
+
sep = sep || "&";
|
|
302
|
+
eq = eq || "=";
|
|
303
|
+
obj = obj || {};
|
|
304
|
+
var encode = (options && options.encodeURIComponent) || qsEscape;
|
|
305
|
+
var parts = [];
|
|
306
|
+
for (var key in obj) {
|
|
307
|
+
if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
|
|
308
|
+
var value = obj[key];
|
|
309
|
+
var encodedKey = encode(key);
|
|
310
|
+
if (Array.isArray(value)) {
|
|
311
|
+
for (var i = 0; i < value.length; i++) parts.push(encodedKey + eq + encode(value[i]));
|
|
312
|
+
} else {
|
|
313
|
+
parts.push(encodedKey + eq + encode(value));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return parts.join(sep);
|
|
317
|
+
}
|
|
318
|
+
export { parse, stringify, qsEscape as escape, qsUnescape as unescape, stringify as encode, parse as decode };
|
|
319
|
+
export default { parse, stringify, escape: qsEscape, unescape: qsUnescape, encode: stringify, decode: parse };
|
|
320
|
+
`;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* `URL`/`URLSearchParams` are re-exported under aliased local names
|
|
324
|
+
* (`NodeURL`/`NodeURLSearchParams`) rather than declared as top-level
|
|
325
|
+
* `URL`/`URLSearchParams` — Porffor pre-binds `URL` as a global and
|
|
326
|
+
* rejects a second top-level declaration of that name. legacy
|
|
327
|
+
* parse/format/resolve are hand-written over the WHATWG URL rather than
|
|
328
|
+
* Bun's internal legacy-Url implementation, which isn't something this
|
|
329
|
+
* shim can import from; good enough for the common absolute-URL case,
|
|
330
|
+
* not a byte-for-byte port of Node's edge cases (relative/protocol-less
|
|
331
|
+
* input, unusual schemes).
|
|
332
|
+
*/
|
|
333
|
+
const NODE_URL_SHIM = `
|
|
334
|
+
var NodeURL = globalThis.URL;
|
|
335
|
+
var NodeURLSearchParams = globalThis.URLSearchParams;
|
|
336
|
+
function urlFormat(urlObject) {
|
|
337
|
+
if (typeof urlObject === "string") return urlObject;
|
|
338
|
+
if (urlObject instanceof NodeURL) return urlObject.href;
|
|
339
|
+
var protocol = urlObject.protocol || "";
|
|
340
|
+
var slashes = urlObject.slashes !== false && !!(urlObject.host || urlObject.hostname);
|
|
341
|
+
var auth = urlObject.auth ? urlObject.auth + "@" : "";
|
|
342
|
+
var host = urlObject.host || ((urlObject.hostname || "") + (urlObject.port ? ":" + urlObject.port : ""));
|
|
343
|
+
var pathname = urlObject.pathname || "";
|
|
344
|
+
var search = urlObject.search || (urlObject.query ? "?" + (typeof urlObject.query === "string" ? urlObject.query : "") : "");
|
|
345
|
+
var hash = urlObject.hash || "";
|
|
346
|
+
return protocol + (slashes ? "//" : "") + auth + host + pathname + search + hash;
|
|
347
|
+
}
|
|
348
|
+
function legacyFromURL(u) {
|
|
349
|
+
return {
|
|
350
|
+
protocol: u.protocol,
|
|
351
|
+
slashes: true,
|
|
352
|
+
auth: u.username ? u.username + (u.password ? ":" + u.password : "") : null,
|
|
353
|
+
host: u.host,
|
|
354
|
+
port: u.port || null,
|
|
355
|
+
hostname: u.hostname,
|
|
356
|
+
hash: u.hash || null,
|
|
357
|
+
search: u.search || null,
|
|
358
|
+
query: u.search ? u.search.slice(1) : null,
|
|
359
|
+
pathname: u.pathname,
|
|
360
|
+
path: u.pathname + (u.search || ""),
|
|
361
|
+
href: u.href,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function urlParse(input) {
|
|
365
|
+
return legacyFromURL(new NodeURL(input));
|
|
366
|
+
}
|
|
367
|
+
function urlResolve(from, to) {
|
|
368
|
+
return new NodeURL(to, new NodeURL(from)).href;
|
|
369
|
+
}
|
|
370
|
+
export { NodeURL as URL, NodeURLSearchParams as URLSearchParams, urlFormat as format, urlParse as parse, urlResolve as resolve };
|
|
371
|
+
export default { URL: NodeURL, URLSearchParams: NodeURLSearchParams, format: urlFormat, parse: urlParse, resolve: urlResolve };
|
|
372
|
+
`;
|
|
373
|
+
|
|
374
|
+
export const NODE_SHIMS = {
|
|
375
|
+
"node:path": NODE_PATH_SHIM,
|
|
376
|
+
"node:querystring": NODE_QUERYSTRING_SHIM,
|
|
377
|
+
"node:url": NODE_URL_SHIM,
|
|
378
|
+
} satisfies Record<string, string>;
|