@smallpen/local-package 0.1.0-alpha.2 → 0.1.0-alpha.5
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/assets/fonts/SourceSansPro-OFL.txt +46 -0
- package/assets/fonts/WorkSans-OFL.txt +46 -0
- package/package.json +2 -2
- package/src/draft-import.mjs +1 -11
- package/src/font-convert.mjs +23 -1
- package/src/fs-utils.mjs +95 -0
- package/src/index.mjs +6 -1
- package/src/initialize.mjs +1 -11
- package/src/kiwi-runtime.mjs +38 -36
- package/src/library-network.mjs +189 -0
- package/src/local-package-backend.mjs +215 -24
- package/src/local-package.mjs +532 -176
- package/src/local-workspace-session.mjs +223 -95
- package/src/remote-library.mjs +167 -28
- package/src/render.mjs +342 -94
- package/src/webp-decode.mjs +32 -0
- package/src/workspace.mjs +85 -15
package/src/webp-decode.mjs
CHANGED
|
@@ -34,6 +34,38 @@ export async function initWebpDecoder() {
|
|
|
34
34
|
await loadDecoder();
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// Reads the canvas size from the RIFF header without decoding, so callers can
|
|
38
|
+
// apply size limits before libwebp allocates pixels. Returns undefined when
|
|
39
|
+
// the header is not recognized (the decoder then reports the error).
|
|
40
|
+
export function webpDimensions(bytes) {
|
|
41
|
+
const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
42
|
+
const tag = (offset) => String.fromCharCode(...view.subarray(offset, offset + 4));
|
|
43
|
+
if (view.length < 30 || tag(0) !== "RIFF" || tag(8) !== "WEBP") {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const chunk = tag(12);
|
|
47
|
+
if (chunk === "VP8 ") {
|
|
48
|
+
return {
|
|
49
|
+
height: (view[28] | (view[29] << 8)) & 0x3fff,
|
|
50
|
+
width: (view[26] | (view[27] << 8)) & 0x3fff,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (chunk === "VP8L") {
|
|
54
|
+
const bits = view[21] | (view[22] << 8) | (view[23] << 16) | (view[24] << 24);
|
|
55
|
+
return {
|
|
56
|
+
height: ((bits >>> 14) & 0x3fff) + 1,
|
|
57
|
+
width: (bits & 0x3fff) + 1,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (chunk === "VP8X") {
|
|
61
|
+
return {
|
|
62
|
+
height: (view[27] | (view[28] << 8) | (view[29] << 16)) + 1,
|
|
63
|
+
width: (view[24] | (view[25] << 8) | (view[26] << 16)) + 1,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
37
69
|
// Synchronous decode of a complete WebP image to RGBA pixels. Requires a
|
|
38
70
|
// prior initWebpDecoder(); the renderer initializes it once per projection.
|
|
39
71
|
export function decodeWebpSync(bytes, diagnostics, path) {
|
package/src/workspace.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { realpath } from "node:fs/promises";
|
|
2
|
-
import { dirname, isAbsolute,
|
|
2
|
+
import { dirname, isAbsolute, posix, resolve, win32 } from "node:path";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
combineContextAxes,
|
|
@@ -9,16 +9,63 @@ import {
|
|
|
9
9
|
SmallPenError,
|
|
10
10
|
} from "@smallpen/core";
|
|
11
11
|
|
|
12
|
+
import { isWithinRoot } from "./fs-utils.mjs";
|
|
12
13
|
import { openPackage } from "./local-package.mjs";
|
|
13
|
-
import { openRemoteLibrary } from "./remote-library.mjs";
|
|
14
|
+
import { openRemoteLibrary, remoteManifestUrl } from "./remote-library.mjs";
|
|
14
15
|
|
|
15
16
|
function isRecord(value) {
|
|
16
17
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
// Local Library paths are relative to the declaring Package's directory, like
|
|
21
|
+
// Foundation dependency paths. Unlike dependencies they may climb out of the
|
|
22
|
+
// Workspace ("../libraries/x.smallpen" is the documented form and resolves
|
|
23
|
+
// with an external_library_path warning), but absolute, drive-letter, UNC,
|
|
24
|
+
// and backslash paths are machine-specific and are rejected.
|
|
25
|
+
export function localLibraryLocator(ownerLocator, source) {
|
|
26
|
+
const path = source?.path;
|
|
27
|
+
if (
|
|
28
|
+
typeof path !== "string" ||
|
|
29
|
+
path.length === 0 ||
|
|
30
|
+
path.includes("\0") ||
|
|
31
|
+
path.includes("\\") ||
|
|
32
|
+
isAbsolute(path) ||
|
|
33
|
+
posix.isAbsolute(path) ||
|
|
34
|
+
win32.isAbsolute(path)
|
|
35
|
+
) {
|
|
36
|
+
throw new SmallPenError(
|
|
37
|
+
"invalid_library_source",
|
|
38
|
+
"Local Library path must be relative to the declaring Package",
|
|
39
|
+
{ path },
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return resolve(dirname(ownerLocator), path);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// `remoteLibrarySnapshots` (a Map keyed by manifest URL) lets a long-lived
|
|
46
|
+
// caller reuse remote snapshots it already verified; with
|
|
47
|
+
// `reuseRemoteLibrarySnapshots` the network and disk cache are skipped.
|
|
48
|
+
async function remoteLibrarySnapshot(library, options) {
|
|
49
|
+
const memo =
|
|
50
|
+
options.remoteLibrarySnapshots instanceof Map
|
|
51
|
+
? options.remoteLibrarySnapshots
|
|
52
|
+
: undefined;
|
|
53
|
+
const key = remoteManifestUrl(library.source.url);
|
|
54
|
+
if (
|
|
55
|
+
memo?.has(key) &&
|
|
56
|
+
options.reuseRemoteLibrarySnapshots === true &&
|
|
57
|
+
options.refreshRemoteLibraries !== true
|
|
58
|
+
) {
|
|
59
|
+
return memo.get(key);
|
|
60
|
+
}
|
|
61
|
+
const snapshot = await openRemoteLibrary(library.source.url, {
|
|
62
|
+
cacheRoot: options.libraryCacheRoot,
|
|
63
|
+
expectedPackageId: library.packageId,
|
|
64
|
+
fetchImpl: options.fetchImpl,
|
|
65
|
+
refresh: options.refreshRemoteLibraries === true,
|
|
66
|
+
});
|
|
67
|
+
memo?.set(key, snapshot);
|
|
68
|
+
return snapshot;
|
|
22
69
|
}
|
|
23
70
|
|
|
24
71
|
function chooseFoundation(dependency) {
|
|
@@ -213,7 +260,8 @@ function validateExternalReferences(product, libraries) {
|
|
|
213
260
|
|
|
214
261
|
function contextSelections(product, foundation) {
|
|
215
262
|
const axes = [...combineContextAxes(product, foundation).values()].sort(
|
|
216
|
-
|
|
263
|
+
// Code-unit order: localeCompare would follow the host LANG.
|
|
264
|
+
(left, right) => (left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
|
|
217
265
|
);
|
|
218
266
|
let selections = [{}];
|
|
219
267
|
for (const axis of axes) {
|
|
@@ -313,14 +361,35 @@ function chooseLibrarySource(library) {
|
|
|
313
361
|
};
|
|
314
362
|
}
|
|
315
363
|
|
|
364
|
+
// Remote Libraries declare their own child Libraries, so a Library server
|
|
365
|
+
// could generate an endless chain of new Package IDs (or a huge fan-out) and
|
|
366
|
+
// keep resolution downloading and caching forever. Every declaration the
|
|
367
|
+
// resolution follows counts against one budget.
|
|
368
|
+
export const MAX_LIBRARY_DECLARATIONS = 256;
|
|
369
|
+
|
|
316
370
|
async function resolveDeclaredLibraries(owners, options, workspaceRoot) {
|
|
317
371
|
const conflicts = [];
|
|
318
372
|
const records = new Map();
|
|
319
373
|
const warnings = [];
|
|
374
|
+
let declarations = 0;
|
|
320
375
|
|
|
321
376
|
const visit = async (owner, library, index, depth, stack) => {
|
|
322
377
|
const path = `manifest.json.libraries[${index}]`;
|
|
323
378
|
const choice = chooseLibrarySource(library);
|
|
379
|
+
declarations += 1;
|
|
380
|
+
if (declarations > MAX_LIBRARY_DECLARATIONS) {
|
|
381
|
+
if (declarations === MAX_LIBRARY_DECLARATIONS + 1) {
|
|
382
|
+
conflicts.push(
|
|
383
|
+
conflict(
|
|
384
|
+
"library_limit_exceeded",
|
|
385
|
+
`Library resolution stopped after ${MAX_LIBRARY_DECLARATIONS} Library declarations`,
|
|
386
|
+
path,
|
|
387
|
+
[choice],
|
|
388
|
+
),
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
324
393
|
if (stack.has(library.packageId)) {
|
|
325
394
|
conflicts.push(
|
|
326
395
|
conflict(
|
|
@@ -335,12 +404,7 @@ async function resolveDeclaredLibraries(owners, options, workspaceRoot) {
|
|
|
335
404
|
let snapshot;
|
|
336
405
|
try {
|
|
337
406
|
if (library.source.type === "url") {
|
|
338
|
-
snapshot = await
|
|
339
|
-
cacheRoot: options.libraryCacheRoot,
|
|
340
|
-
expectedPackageId: library.packageId,
|
|
341
|
-
fetchImpl: options.fetchImpl,
|
|
342
|
-
refresh: options.refreshRemoteLibraries === true,
|
|
343
|
-
});
|
|
407
|
+
snapshot = await remoteLibrarySnapshot(library, options);
|
|
344
408
|
} else {
|
|
345
409
|
if (owner.remote) {
|
|
346
410
|
throw new SmallPenError(
|
|
@@ -349,7 +413,7 @@ async function resolveDeclaredLibraries(owners, options, workspaceRoot) {
|
|
|
349
413
|
{ packageId: library.packageId },
|
|
350
414
|
);
|
|
351
415
|
}
|
|
352
|
-
const candidate =
|
|
416
|
+
const candidate = localLibraryLocator(owner.locator, library.source);
|
|
353
417
|
const locator = await realpath(candidate);
|
|
354
418
|
snapshot = await openPackage(locator);
|
|
355
419
|
if (!isWithinRoot(workspaceRoot, locator)) {
|
|
@@ -363,10 +427,16 @@ async function resolveDeclaredLibraries(owners, options, workspaceRoot) {
|
|
|
363
427
|
}
|
|
364
428
|
}
|
|
365
429
|
} catch (error) {
|
|
430
|
+
// A Package chooses its URL Libraries: report only the typed code, never
|
|
431
|
+
// upstream status or transport text a Package could use as a probe.
|
|
432
|
+
const reason =
|
|
433
|
+
library.source.type === "url"
|
|
434
|
+
? (error?.code ?? "remote_library_unavailable")
|
|
435
|
+
: error.message;
|
|
366
436
|
conflicts.push(
|
|
367
437
|
conflict(
|
|
368
438
|
"library_unavailable",
|
|
369
|
-
`Library ${library.packageId} cannot be opened: ${
|
|
439
|
+
`Library ${library.packageId} cannot be opened: ${reason}`,
|
|
370
440
|
`${path}.source`,
|
|
371
441
|
[choice],
|
|
372
442
|
),
|
|
@@ -407,7 +477,7 @@ async function resolveDeclaredLibraries(owners, options, workspaceRoot) {
|
|
|
407
477
|
warnings.push(
|
|
408
478
|
warning(
|
|
409
479
|
"remote_library_offline",
|
|
410
|
-
`Library ${snapshot.manifest.name} is using its last verified cache: ${snapshot.remote.
|
|
480
|
+
`Library ${snapshot.manifest.name} is using its last verified cache: ${snapshot.remote.warningCode}`,
|
|
411
481
|
`${path}.source.url`,
|
|
412
482
|
),
|
|
413
483
|
);
|