single-file-core 1.6.0 → 1.6.2
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/core/helper.js +5 -2
- package/modules/css-fonts-minifier.js +34 -13
- package/modules/template-formatter.js +8 -2
- package/package.json +10 -2
- package/vendor/zip/zip.js +4 -0
- package/.github/workflows/publish.yml +0 -88
- package/.github/workflows/test.yml +0 -31
- package/.github/workflows/vendor.yml +0 -39
- package/css-tree-build/README.md +0 -39
- package/css-tree-build/build.js +0 -66
- package/css-tree-build/package-lock.json +0 -511
- package/css-tree-build/package.json +0 -13
- package/deno.lock +0 -19
- package/doc/assets/singlefile-archive-byte-map.svg +0 -276
- package/doc/assets/singlefile-archive-lenses.svg +0 -245
- package/doc/singlefile-archive.md +0 -1968
- package/eslint.config.mjs +0 -74
- package/test/capture/README.md +0 -74
- package/test/capture/canonical-link.js +0 -50
- package/test/capture/common.js +0 -73
- package/test/capture/deferred-content-options.js +0 -71
- package/test/capture/dom.js +0 -26
- package/test/capture/maff-metadata.js +0 -205
- package/test/capture/resource-cap.js +0 -79
- package/test/capture/script-uri-sanitization.js +0 -82
- package/test/fixtures/README.md +0 -18
- package/test/fixtures/classic-digest.html +0 -5
- package/test/fixtures/generate-fixtures.sh +0 -31
- package/test/fixtures/multi-page-dedup.zip.html +0 -70
- package/test/fixtures/multi-page.zip.html +0 -26
- package/test/fixtures/single-page.zip.html +0 -11
- package/test/fixtures/site/multi-page/alpha.html +0 -2
- package/test/fixtures/site/multi-page/beta.html +0 -2
- package/test/fixtures/site/multi-page/index.html +0 -2
- package/test/fixtures/site/multi-page/sub/gamma.html +0 -2
- package/test/fixtures/site/multi-page-dedup/index.html +0 -2
- package/test/fixtures/site/multi-page-dedup/logo.png +0 -0
- package/test/fixtures/site/multi-page-dedup/one.html +0 -2
- package/test/fixtures/site/multi-page-dedup/shared.css +0 -3
- package/test/fixtures/site/multi-page-dedup/two.html +0 -2
- package/test/run.js +0 -109
- package/test/sfz-harness/README.md +0 -87
- package/test/sfz-harness/adopted-stylesheets-hook.js +0 -240
- package/test/sfz-harness/byte-map.js +0 -137
- package/test/sfz-harness/charset-round-trip.js +0 -161
- package/test/sfz-harness/check-determinism.js +0 -57
- package/test/sfz-harness/common.js +0 -154
- package/test/sfz-harness/content-type-sniffing.js +0 -83
- package/test/sfz-harness/css-fonts-minifier.js +0 -235
- package/test/sfz-harness/css-property-filter.js +0 -113
- package/test/sfz-harness/dom-stub.js +0 -13
- package/test/sfz-harness/entry-compression.js +0 -77
- package/test/sfz-harness/filename-characters.js +0 -55
- package/test/sfz-harness/filename-max-length.js +0 -69
- package/test/sfz-harness/font-face-composite.js +0 -135
- package/test/sfz-harness/format-rules.js +0 -617
- package/test/sfz-harness/gen-e2e-page.js +0 -14
- package/test/sfz-harness/inlined-functions.js +0 -82
- package/test/sfz-harness/option-wiring.js +0 -60
- package/test/sfz-harness/pages-archive.js +0 -318
- package/test/sfz-harness/pages-router.js +0 -143
- package/test/sfz-harness/relocation-cost.js +0 -94
- package/test/sfz-harness/search-triggers.js +0 -35
- package/test/sfz-harness/smoke.js +0 -10
- package/test/sfz-harness/stored-trigger.js +0 -95
- package/test/sfz-harness/trigger-seeds.json +0 -120
- package/test/sfz-harness/zip64.js +0 -77
- package/zip-build/README.md +0 -56
- package/zip-build/lib/fflate-streams.js +0 -47
- package/zip-build/lib/fflate.js +0 -2692
- package/zip-build/lib/zip-vendor-worker.js +0 -3
- package/zip-build/lib/zip-vendor.js +0 -3
- package/zip-build/lib/zip.js +0 -25
- package/zip-build/package-lock.json +0 -665
- package/zip-build/package.json +0 -15
- package/zip-build/reserved-property-names.json +0 -10630
- package/zip-build/rollup.config.js +0 -61
package/core/helper.js
CHANGED
|
@@ -123,6 +123,9 @@ export {
|
|
|
123
123
|
getDataURI,
|
|
124
124
|
digest,
|
|
125
125
|
getValidFilename,
|
|
126
|
+
DEFAULT_REPLACED_CHARACTERS,
|
|
127
|
+
DEFAULT_REPLACEMENT_CHARACTER,
|
|
128
|
+
DEFAULT_REPLACEMENT_CHARACTERS,
|
|
126
129
|
parseDocContent,
|
|
127
130
|
markInvalidNesting,
|
|
128
131
|
fixInvalidNesting,
|
|
@@ -926,14 +929,14 @@ function getComputedStyle(win, element, pseudoElement) {
|
|
|
926
929
|
|
|
927
930
|
function getValidFilename(filename, replacedCharacters = DEFAULT_REPLACED_CHARACTERS, replacementCharacter = DEFAULT_REPLACEMENT_CHARACTER, replacementCharacters = DEFAULT_REPLACEMENT_CHARACTERS) {
|
|
928
931
|
replacementCharacters.forEach((indexReplacementCharacter, index) => {
|
|
929
|
-
if (replacedCharacters[index] !== undefined && indexReplacementCharacter != replacedCharacters[index]) {
|
|
932
|
+
if (indexReplacementCharacter && replacedCharacters[index] !== undefined && indexReplacementCharacter != replacedCharacters[index]) {
|
|
930
933
|
// no "+" here, unlike the fallback below: a lookalike replaces its character one for
|
|
931
934
|
// one, so collapsing a run would drop characters the name needs ("C++" -> "C+")
|
|
932
935
|
filename = filename.replace(new RegExp("[" + getCharacterClassContent(replacedCharacters[index]) + "]", "g"), indexReplacementCharacter);
|
|
933
936
|
}
|
|
934
937
|
});
|
|
935
938
|
replacedCharacters.forEach((replacedCharacter, index) => {
|
|
936
|
-
if (replacementCharacters[index]
|
|
939
|
+
if (!replacementCharacters[index]) {
|
|
937
940
|
filename = filename.replace(new RegExp("[" + getCharacterClassContent(replacedCharacter) + "]+", "g"), replacementCharacter);
|
|
938
941
|
}
|
|
939
942
|
});
|
|
@@ -38,6 +38,9 @@ const helper = {
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
const REGEXP_COMMA = /\s*,\s*/;
|
|
41
|
+
const REGEXP_SPACES = /\s+/g;
|
|
42
|
+
const REGEXP_FONT_STYLE_ANGLE = /-?\d*\.?\d+/g;
|
|
43
|
+
const DEFAULT_OBLIQUE_ANGLE = 14;
|
|
41
44
|
const REGEXP_DASH = /-/;
|
|
42
45
|
const REGEXP_QUESTION_MARK = /\?/g;
|
|
43
46
|
const REGEXP_STARTS_U_PLUS = /^U\+/i;
|
|
@@ -323,11 +326,11 @@ function testUsedFont(ruleData, familyName, declaredFonts, filteredUsedFonts) {
|
|
|
323
326
|
let test;
|
|
324
327
|
const optionalUsedFonts = filteredUsedFonts && filteredUsedFonts.get(familyName);
|
|
325
328
|
if (optionalUsedFonts && optionalUsedFonts.length) {
|
|
326
|
-
|
|
329
|
+
const fontStyle = getDeclarationValue(ruleData.block.children, "font-style") || "normal";
|
|
327
330
|
if (VALID_FONT_STYLES.find(rule => fontStyle.trim().match(rule))) {
|
|
328
331
|
const fontWeight = helper.getFontWeight(getDeclarationValue(ruleData.block.children, "font-weight") || "400");
|
|
329
332
|
const declaredFontsWeights = declaredFonts
|
|
330
|
-
.filter(fontInfo => fontInfo.fontFamily == familyName && fontInfo.fontStyle
|
|
333
|
+
.filter(fontInfo => fontInfo.fontFamily == familyName && testFontStyle(fontInfo.fontStyle, fontStyle))
|
|
331
334
|
.map(fontInfo => fontInfo.fontWeight.split(" "))
|
|
332
335
|
.sort((weight1, weight2) => Number.parseInt(weight1[0], 10) - Number.parseInt(weight2[0], 10));
|
|
333
336
|
let usedFontWeights = optionalUsedFonts
|
|
@@ -343,16 +346,6 @@ function testUsedFont(ruleData, familyName, declaredFonts, filteredUsedFonts) {
|
|
|
343
346
|
})
|
|
344
347
|
.filter(fontWeight => fontWeight);
|
|
345
348
|
test = testFontweight(fontWeight, usedFontWeights);
|
|
346
|
-
if (!test) {
|
|
347
|
-
usedFontWeights = optionalUsedFonts
|
|
348
|
-
.map(fontInfo => {
|
|
349
|
-
fontInfo = Array.from(fontInfo);
|
|
350
|
-
fontInfo[2] = fontStyle = "normal";
|
|
351
|
-
return getUsedFontWeight(fontInfo, fontStyle, declaredFontsWeights);
|
|
352
|
-
})
|
|
353
|
-
.filter(fontWeight => fontWeight);
|
|
354
|
-
test = testFontweight(fontWeight, usedFontWeights);
|
|
355
|
-
}
|
|
356
349
|
}
|
|
357
350
|
} else {
|
|
358
351
|
test = true;
|
|
@@ -363,6 +356,34 @@ function testUsedFont(ruleData, familyName, declaredFonts, filteredUsedFonts) {
|
|
|
363
356
|
return test;
|
|
364
357
|
}
|
|
365
358
|
|
|
359
|
+
function testFontStyle(fontStyle, otherFontStyle) {
|
|
360
|
+
const angles = getFontStyleAngles(fontStyle);
|
|
361
|
+
const otherAngles = getFontStyleAngles(otherFontStyle);
|
|
362
|
+
if (angles && otherAngles) {
|
|
363
|
+
return angles[0] <= otherAngles[1] && otherAngles[0] <= angles[1];
|
|
364
|
+
} else {
|
|
365
|
+
return normalizeFontStyle(fontStyle) == normalizeFontStyle(otherFontStyle);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function getFontStyleAngles(fontStyle) {
|
|
370
|
+
fontStyle = normalizeFontStyle(fontStyle);
|
|
371
|
+
if (fontStyle == "normal") {
|
|
372
|
+
return [0, 0];
|
|
373
|
+
} else if (fontStyle == "italic") {
|
|
374
|
+
return [DEFAULT_OBLIQUE_ANGLE, DEFAULT_OBLIQUE_ANGLE];
|
|
375
|
+
} else if (fontStyle == "oblique" || fontStyle.startsWith("oblique ")) {
|
|
376
|
+
const angles = (fontStyle.match(REGEXP_FONT_STYLE_ANGLE) || [])
|
|
377
|
+
.map(angle => Number.parseFloat(angle))
|
|
378
|
+
.filter(angle => !Number.isNaN(angle));
|
|
379
|
+
return angles.length ? [Math.min(...angles), Math.max(...angles)] : [DEFAULT_OBLIQUE_ANGLE, DEFAULT_OBLIQUE_ANGLE];
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function normalizeFontStyle(fontStyle) {
|
|
384
|
+
return String(fontStyle || "normal").trim().toLowerCase().replace(REGEXP_SPACES, " ");
|
|
385
|
+
}
|
|
386
|
+
|
|
366
387
|
function testFontweight(fontWeight, usedFontWeights) {
|
|
367
388
|
let test;
|
|
368
389
|
for (const fontWeightValue of fontWeight.split(",")) {
|
|
@@ -503,7 +524,7 @@ function parseFamilyNames(fontFamilyNameTokenData, fontFamilyNames) {
|
|
|
503
524
|
function getUsedFontWeight(fontInfo, fontStyle, fontWeights) {
|
|
504
525
|
let foundWeight;
|
|
505
526
|
fontWeights = fontWeights.map(weights => weights.map(value => String(Number.parseInt(value, 10))));
|
|
506
|
-
if (fontInfo[2]
|
|
527
|
+
if (testFontStyle(fontInfo[2], fontStyle)) {
|
|
507
528
|
let fontWeight = Number(fontInfo[1]);
|
|
508
529
|
if (fontWeights.length > 1) {
|
|
509
530
|
if (fontWeight >= 400 && fontWeight <= 500) {
|
|
@@ -16881,8 +16881,14 @@ async function formatFilename(content, doc, options) {
|
|
|
16881
16881
|
filename = filename.replace(/\//g, filenameReplacementCharacter);
|
|
16882
16882
|
}
|
|
16883
16883
|
if (!options.keepFilename && ((options.filenameMaxLengthUnit == "bytes" && getContentSize(filename) > options.filenameMaxLength) || filename.length > options.filenameMaxLength)) {
|
|
16884
|
-
const
|
|
16885
|
-
|
|
16884
|
+
const filenameExtension = "." + getFilenameExtension(options);
|
|
16885
|
+
let extension;
|
|
16886
|
+
if (filename.endsWith(filenameExtension)) {
|
|
16887
|
+
extension = filenameExtension;
|
|
16888
|
+
} else {
|
|
16889
|
+
const extensionMatch = filename.match(/(\.[^.]{3,4})$/);
|
|
16890
|
+
extension = extensionMatch && extensionMatch[0] && extensionMatch[0].length > 1 ? extensionMatch[0] : "";
|
|
16891
|
+
}
|
|
16886
16892
|
const suffix = "…" + extension;
|
|
16887
16893
|
const maxLength = Math.max(options.filenameMaxLength - (options.filenameMaxLengthUnit == "bytes" ? getContentSize(suffix) : suffix.length), 0);
|
|
16888
16894
|
filename = options.filenameMaxLengthUnit == "bytes" ? await truncateText(filename, maxLength) : filename.substring(0, maxLength);
|
package/package.json
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "single-file-core",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "SingleFile Core",
|
|
5
5
|
"author": "Gildas Lormeau",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
7
|
+
"files": [
|
|
8
|
+
"*.js",
|
|
9
|
+
"core/",
|
|
10
|
+
"modules/",
|
|
11
|
+
"processors/",
|
|
12
|
+
"vendor/"
|
|
13
|
+
],
|
|
7
14
|
"scripts": {
|
|
8
15
|
"test": "deno run --allow-read --allow-run test/run.js",
|
|
9
16
|
"bump-patch": "npm version patch --no-git-tag-version && npm run bump-commit",
|
|
@@ -21,6 +28,7 @@
|
|
|
21
28
|
"homepage": "https://github.com/gildas-lormeau/single-file-core#readme",
|
|
22
29
|
"devDependencies": {
|
|
23
30
|
"@eslint/js": "^9.39.5",
|
|
24
|
-
"eslint": "^10.9.1"
|
|
31
|
+
"eslint": "^10.9.1",
|
|
32
|
+
"happy-dom": "^20.14.5"
|
|
25
33
|
}
|
|
26
34
|
}
|
package/vendor/zip/zip.js
CHANGED
|
@@ -9927,4 +9927,8 @@ try {
|
|
|
9927
9927
|
}
|
|
9928
9928
|
catch (e) { }
|
|
9929
9929
|
|
|
9930
|
+
setDefaultConfiguration({
|
|
9931
|
+
workerURI: null
|
|
9932
|
+
});
|
|
9933
|
+
|
|
9930
9934
|
export { BlobReader, BlobWriter, Data64URIReader, Data64URIWriter, ERR_ABORTED, ERR_AMBIGUOUS_ARCHIVE, ERR_BAD_FORMAT, ERR_CENTRAL_DIRECTORY_NOT_FOUND, ERR_DUPLICATED_NAME, ERR_ENCRYPTED, ERR_ENCRYPTED_CENTRAL_DIRECTORY, ERR_ENTRY_DATA_OUT_OF_BOUNDS, ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND, ERR_EOCDR_NOT_FOUND, ERR_EXTRAFIELD_ZIP64_NOT_FOUND, ERR_HTTP_RANGE, ERR_HTTP_RESOURCE_CHANGED, ERR_HTTP_STATUS, ERR_INVALID_AUTHENTICATION_CODE, ERR_INVALID_BASE_URI, ERR_INVALID_CODEC_DEFINITION, ERR_INVALID_CODEC_MODULE, ERR_INVALID_COMMENT, ERR_INVALID_COMMENT_TYPE, ERR_INVALID_COMPRESSED_DATA, ERR_INVALID_CRC32, ERR_INVALID_DATE, ERR_INVALID_ENCRYPTION_STRENGTH, ERR_INVALID_ENTRY, ERR_INVALID_ENTRY_COMMENT, ERR_INVALID_ENTRY_COMMENT_TYPE, ERR_INVALID_ENTRY_NAME, ERR_INVALID_EXTRAFIELD, ERR_INVALID_EXTRAFIELD_DATA, ERR_INVALID_EXTRAFIELD_DATA_TYPE, ERR_INVALID_EXTRAFIELD_TYPE, ERR_INVALID_FILENAME_VALIDATION, ERR_INVALID_FUNCTION_OPTION, ERR_INVALID_GID, ERR_INVALID_LEVEL, ERR_INVALID_MAX_APPENDED_DATA_SIZE, ERR_INVALID_MAX_WORKERS, ERR_INVALID_MSDOS_ATTRIBUTES, ERR_INVALID_MSDOS_DATA, ERR_INVALID_PASSWORD, ERR_INVALID_PASSWORD_TYPE, ERR_INVALID_PASS_THROUGH_VALUE, ERR_INVALID_READER, ERR_INVALID_SIGNAL, ERR_INVALID_SIGNATURE_DATA, ERR_INVALID_STRICTNESS, ERR_INVALID_UID, ERR_INVALID_UNCOMPRESSED_SIZE, ERR_INVALID_UNIX_EXTRA_FIELD_TYPE, ERR_INVALID_UNIX_ID_SIZE, ERR_INVALID_UNIX_MODE, ERR_INVALID_URI, ERR_INVALID_VERSION, ERR_ITERATOR_COMPLETED_TOO_SOON, ERR_LOCAL_FILE_HEADER_NOT_FOUND, ERR_OVERLAPPING_ENTRY, ERR_RESERVED_COMPRESSION_METHOD, ERR_SPLIT_ZIP_FILE, ERR_UNDEFINED_COMPRESSION_METHOD, ERR_UNDEFINED_CRC32, ERR_UNDEFINED_READER, ERR_UNDEFINED_UNCOMPRESSED_SIZE, ERR_UNDETERMINED_SIZE, ERR_UNSAFE_FILENAME, ERR_UNSUPPORTED_COMPRESSION, ERR_UNSUPPORTED_CONTEXT, ERR_UNSUPPORTED_CRYPTO_API, ERR_UNSUPPORTED_ENCRYPTION, ERR_UNSUPPORTED_ENCRYPTION_PASS_THROUGH, ERR_UNSUPPORTED_ENCRYPTION_USDZ, ERR_UNSUPPORTED_FORMAT, ERR_UNSUPPORTED_SPLIT_USDZ, ERR_UNSUPPORTED_UINT64, ERR_WORKER_STARTUP_TIMEOUT, ERR_WRITER_NOT_INITIALIZED, ERR_WRITER_SIZE_NOT_WRITABLE, ERR_ZIP_CRYPTO_LAST_MOD_DATE, ERR_ZIP_NOT_EMPTY, HttpRangeReader, HttpReader, Reader, SplitDataReader, SplitDataWriter, TextReader, TextWriter, Uint8ArrayReader, Uint8ArrayWriter, VERSION, WARNING_APPENDED_DATA, WARNING_CLAMPED_LAST_MODIFICATION_DATE, WARNING_COMPRESSED_PATCHED_DATA, WARNING_COMPRESSION_UNAVAILABLE, WARNING_DUPLICATE_FILENAME, WARNING_MALFORMED_EXTRA_FIELD, WARNING_MISMATCHED_LOCAL_FILE_HEADER_BIT_FLAG, WARNING_MISMATCHED_LOCAL_FILE_HEADER_COMPRESSION_METHOD, WARNING_MISMATCHED_LOCAL_FILE_HEADER_CRC32_OR_SIZES, WARNING_MISMATCHED_LOCAL_FILE_HEADER_FILENAME, WARNING_MISMATCHED_ZIP64_END_OF_CENTRAL_DIRECTORY, WARNING_MULTIPLE_END_OF_CENTRAL_DIRECTORY, WARNING_PREPENDED_CENTRAL_DIRECTORY, WARNING_PREPENDED_DATA, WARNING_TRAILING_CENTRAL_DIRECTORY_DATA, WARNING_UNKNOWN_VERSION, WARNING_UNKNOWN_ZIP64_EXTENSIBLE_DATA, WARNING_UNSORTED_CENTRAL_DIRECTORY, WARNING_WRAPPED_ENTRIES_COUNT, Writer, ZipReader, ZipReaderStream, ZipWriter, ZipWriterStream, configure, createBlobTempStream, createOPFSTempStream, createSyncAccessHandleTempStream, deflateSync as deflateRaw, getMimeType, getRegisteredCodecs, getSupportedCompressionMethods, inflateSync as inflateRaw, initStream, isZipFile, readUint8Array, registerCodec, resetConfiguration, terminateWorkers, unregisterCodec };
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# .github/workflows/publish.yml
|
|
2
|
-
|
|
3
|
-
name: Publish
|
|
4
|
-
|
|
5
|
-
on:
|
|
6
|
-
release:
|
|
7
|
-
types: [published]
|
|
8
|
-
|
|
9
|
-
permissions:
|
|
10
|
-
id-token: write
|
|
11
|
-
contents: read
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
# nothing else guarantees that the released commit was verified: the vendor workflow runs on push,
|
|
15
|
-
# so this waits for its run on that exact commit and refuses to publish unless it succeeded
|
|
16
|
-
check_vendor:
|
|
17
|
-
name: 'Check vendor rebuild'
|
|
18
|
-
runs-on: ubuntu-latest
|
|
19
|
-
permissions:
|
|
20
|
-
contents: read
|
|
21
|
-
actions: read
|
|
22
|
-
steps:
|
|
23
|
-
# only used to resolve the tag to a commit, so it runs no code from the repository and keeps no
|
|
24
|
-
# git credentials behind
|
|
25
|
-
- uses: actions/checkout@v5
|
|
26
|
-
with:
|
|
27
|
-
ref: ${{ github.event.release.tag_name }}
|
|
28
|
-
persist-credentials: false
|
|
29
|
-
# the tag is typed in the release UI, so nothing ties it to the version that is about to be
|
|
30
|
-
# published: a mistyped tag, or a bump that was never pushed, publishes a version nobody asked for
|
|
31
|
-
- name: Verify the tag matches the declared version
|
|
32
|
-
env:
|
|
33
|
-
TAG_NAME: ${{ github.event.release.tag_name }}
|
|
34
|
-
run: |
|
|
35
|
-
version=$(jq -r .version package.json)
|
|
36
|
-
if [ "$TAG_NAME" != "v$version" ]; then
|
|
37
|
-
echo "::error::release $TAG_NAME but package.json declares $version"
|
|
38
|
-
exit 1
|
|
39
|
-
fi
|
|
40
|
-
- name: Wait for the vendor rebuild check of the released commit
|
|
41
|
-
env:
|
|
42
|
-
GH_TOKEN: ${{ github.token }}
|
|
43
|
-
run: |
|
|
44
|
-
sha=$(git rev-parse HEAD)
|
|
45
|
-
echo "released commit: $sha"
|
|
46
|
-
missing=0
|
|
47
|
-
for attempt in $(seq 1 60); do
|
|
48
|
-
run=$(gh api "repos/$GITHUB_REPOSITORY/actions/workflows/vendor.yml/runs?head_sha=$sha&per_page=1" \
|
|
49
|
-
--jq 'if (.workflow_runs | length) == 0 then "none none none"
|
|
50
|
-
else (.workflow_runs[0] | "\(.status) \(.conclusion) \(.html_url)") end')
|
|
51
|
-
run_status=$(echo "$run" | cut -d' ' -f1)
|
|
52
|
-
conclusion=$(echo "$run" | cut -d' ' -f2)
|
|
53
|
-
url=$(echo "$run" | cut -d' ' -f3)
|
|
54
|
-
if [ "$run_status" = "none" ]; then
|
|
55
|
-
missing=$((missing + 1))
|
|
56
|
-
if [ "$missing" -ge 10 ]; then
|
|
57
|
-
echo "::error::no vendor run for $sha, run the vendor workflow on this commit and publish again"
|
|
58
|
-
exit 1
|
|
59
|
-
fi
|
|
60
|
-
echo "no vendor run found yet for $sha"
|
|
61
|
-
else
|
|
62
|
-
echo "vendor run: status=$run_status conclusion=$conclusion $url"
|
|
63
|
-
if [ "$run_status" = "completed" ]; then
|
|
64
|
-
if [ "$conclusion" = "success" ]; then
|
|
65
|
-
exit 0
|
|
66
|
-
fi
|
|
67
|
-
echo "::error::the vendor rebuild concluded $conclusion for $sha, see $url"
|
|
68
|
-
exit 1
|
|
69
|
-
fi
|
|
70
|
-
fi
|
|
71
|
-
sleep 30
|
|
72
|
-
done
|
|
73
|
-
echo "::error::timed out waiting for the vendor rebuild of $sha"
|
|
74
|
-
exit 1
|
|
75
|
-
|
|
76
|
-
publish_npm:
|
|
77
|
-
needs: check_vendor
|
|
78
|
-
runs-on: ubuntu-latest
|
|
79
|
-
steps:
|
|
80
|
-
- uses: actions/checkout@v5
|
|
81
|
-
with:
|
|
82
|
-
ref: ${{ github.event.release.tag_name }}
|
|
83
|
-
# Setup .npmrc file to publish to npm
|
|
84
|
-
- uses: actions/setup-node@v5
|
|
85
|
-
with:
|
|
86
|
-
node-version: '24'
|
|
87
|
-
registry-url: 'https://registry.npmjs.org'
|
|
88
|
-
- run: npm publish
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# .github/workflows/test.yml
|
|
2
|
-
|
|
3
|
-
name: Test
|
|
4
|
-
|
|
5
|
-
# tag pushes are excluded: creating a release would otherwise re-run the check already done on the branch
|
|
6
|
-
on:
|
|
7
|
-
push:
|
|
8
|
-
branches: ['**']
|
|
9
|
-
pull_request:
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
sfz-harness:
|
|
13
|
-
name: 'SFZ format rules'
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
steps:
|
|
16
|
-
- uses: actions/checkout@v5
|
|
17
|
-
- uses: denoland/setup-deno@v2
|
|
18
|
-
with:
|
|
19
|
-
deno-version: v2.x
|
|
20
|
-
# the suites drive the writer directly, with no browser and no network
|
|
21
|
-
- run: npm test
|
|
22
|
-
lint:
|
|
23
|
-
name: 'Lint'
|
|
24
|
-
runs-on: ubuntu-latest
|
|
25
|
-
steps:
|
|
26
|
-
- uses: actions/checkout@v5
|
|
27
|
-
- uses: actions/setup-node@v5
|
|
28
|
-
with:
|
|
29
|
-
node-version: '24'
|
|
30
|
-
- run: npm ci
|
|
31
|
-
- run: npx eslint .
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# .github/workflows/vendor.yml
|
|
2
|
-
|
|
3
|
-
name: Vendor
|
|
4
|
-
|
|
5
|
-
# tag pushes are excluded: creating a release would otherwise re-run the check already done on the branch
|
|
6
|
-
on:
|
|
7
|
-
push:
|
|
8
|
-
branches: ['**']
|
|
9
|
-
pull_request:
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
rebuild:
|
|
13
|
-
name: 'Rebuild vendor/zip'
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
steps:
|
|
16
|
-
- uses: actions/checkout@v5
|
|
17
|
-
- uses: actions/setup-node@v5
|
|
18
|
-
with:
|
|
19
|
-
node-version: '24'
|
|
20
|
-
- run: npm ci
|
|
21
|
-
working-directory: zip-build
|
|
22
|
-
- run: npm run build
|
|
23
|
-
working-directory: zip-build
|
|
24
|
-
# the checked-in files in vendor/zip must be exactly what the sources in zip-build produce
|
|
25
|
-
- run: git diff --exit-code vendor/zip
|
|
26
|
-
rebuild-css-tree:
|
|
27
|
-
name: 'Rebuild vendor/css-tree.js'
|
|
28
|
-
runs-on: ubuntu-latest
|
|
29
|
-
steps:
|
|
30
|
-
- uses: actions/checkout@v5
|
|
31
|
-
- uses: actions/setup-node@v5
|
|
32
|
-
with:
|
|
33
|
-
node-version: '24'
|
|
34
|
-
- run: npm ci
|
|
35
|
-
working-directory: css-tree-build
|
|
36
|
-
- run: npm run build
|
|
37
|
-
working-directory: css-tree-build
|
|
38
|
-
# the checked-in file must be exactly what the sources in css-tree-build produce
|
|
39
|
-
- run: git diff --exit-code vendor/css-tree.js
|
package/css-tree-build/README.md
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# Rebuilding `vendor/css-tree.js`
|
|
2
|
-
|
|
3
|
-
[`../vendor/css-tree.js`](../vendor/css-tree.js) is a custom build of
|
|
4
|
-
[css-tree](https://www.npmjs.com/package/css-tree) with one patch applied to
|
|
5
|
-
`lib/tokenizer/TokenStream.js`: the token offset field is widened from 24 to
|
|
6
|
-
27 bits (`OFFSET_MASK`/`TYPE_SHIFT`, with unsigned shifts for the token type).
|
|
7
|
-
Upstream css-tree packs each token as `type << 24 | offset`, so any stylesheet
|
|
8
|
-
larger than 16MB (2^24 bytes) silently corrupts the token stream and parsing
|
|
9
|
-
never terminates. Real pages ship such stylesheets (see
|
|
10
|
-
https://github.com/gildas-lormeau/SingleFile/issues/1962, a 23.5MB stylesheet
|
|
11
|
-
on brookings.edu). With 27 bits the limit becomes 128MB.
|
|
12
|
-
|
|
13
|
-
This directory rebuilds the file deterministically. All dependencies are
|
|
14
|
-
pinned to exact versions by `package.json` and `package-lock.json`:
|
|
15
|
-
|
|
16
|
-
```sh
|
|
17
|
-
cd css-tree-build
|
|
18
|
-
npm ci
|
|
19
|
-
npm run build
|
|
20
|
-
git diff --exit-code ../vendor/css-tree.js
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
An empty diff means the checked-in file matches what the sources produce. The
|
|
24
|
-
GitHub Actions workflow `.github/workflows/vendor.yml` runs the same steps on
|
|
25
|
-
every push.
|
|
26
|
-
|
|
27
|
-
The patch is applied to the readable npm sources by an esbuild plugin in
|
|
28
|
-
`build.js`, guarded by exact occurrence counts: if a css-tree update changes
|
|
29
|
-
`TokenStream.js`, the build fails loudly instead of silently dropping the
|
|
30
|
-
patch.
|
|
31
|
-
|
|
32
|
-
## Updating css-tree
|
|
33
|
-
|
|
34
|
-
1. Bump `css-tree` in `package.json` and run `npm install`.
|
|
35
|
-
2. Run `npm run build`. If the build fails because `TokenStream.js` changed,
|
|
36
|
-
review the patch in `build.js` against the new sources (or drop it once
|
|
37
|
-
upstream stores offsets wider than 24 bits).
|
|
38
|
-
3. Commit the regenerated `../vendor/css-tree.js` together with the changes in
|
|
39
|
-
this directory.
|
package/css-tree-build/build.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
import { build } from "esbuild";
|
|
4
|
-
|
|
5
|
-
const require = createRequire(import.meta.url);
|
|
6
|
-
const packagePath = require.resolve("css-tree/package.json");
|
|
7
|
-
const licensePath = packagePath.replace(/package\.json$/, "LICENSE");
|
|
8
|
-
const { version } = JSON.parse(readFileSync(packagePath));
|
|
9
|
-
|
|
10
|
-
const PATCHES = [
|
|
11
|
-
["const OFFSET_MASK = 0x00FFFFFF;", "const OFFSET_MASK = 0x07FFFFFF;", 1],
|
|
12
|
-
["const TYPE_SHIFT = 24;", "const TYPE_SHIFT = 27;", 1],
|
|
13
|
-
[">> TYPE_SHIFT", ">>> TYPE_SHIFT", 8]
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
function patchTokenStream(source) {
|
|
17
|
-
for (const [pattern, replacement, expectedCount] of PATCHES) {
|
|
18
|
-
const count = source.split(pattern).length - 1;
|
|
19
|
-
if (count != expectedCount) {
|
|
20
|
-
throw new Error(`css-tree update changed TokenStream.js: found ${count} occurrence(s) of ${JSON.stringify(pattern)}, expected ${expectedCount} — review the 16MB token-offset patch`);
|
|
21
|
-
}
|
|
22
|
-
source = source.replaceAll(pattern, replacement);
|
|
23
|
-
}
|
|
24
|
-
return source;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const patchPlugin = {
|
|
28
|
-
name: "patch-token-stream",
|
|
29
|
-
setup(build) {
|
|
30
|
-
let patched = false;
|
|
31
|
-
build.onLoad({ filter: /[/\\]css-tree[/\\].*[/\\]TokenStream\.js$/ }, args => {
|
|
32
|
-
patched = true;
|
|
33
|
-
return { contents: patchTokenStream(readFileSync(args.path, "utf8")), loader: "js" };
|
|
34
|
-
});
|
|
35
|
-
build.onEnd(() => {
|
|
36
|
-
if (!patched) {
|
|
37
|
-
throw new Error("TokenStream.js was not loaded — the 16MB token-offset patch was not applied");
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const banner = [
|
|
44
|
-
`// css-tree ${version} (https://github.com/csstree/csstree), bundled by css-tree-build/build.js`,
|
|
45
|
-
"// with the token offset field widened from 24 to 27 bits so stylesheets larger",
|
|
46
|
-
"// than 16MB do not corrupt the token stream (parsing never terminated on them)",
|
|
47
|
-
"",
|
|
48
|
-
"/*",
|
|
49
|
-
readFileSync(licensePath, "utf8").trim().split("\n").map(line => (" * " + line).trimEnd()).join("\n"),
|
|
50
|
-
" */"
|
|
51
|
-
].join("\n");
|
|
52
|
-
|
|
53
|
-
await build({
|
|
54
|
-
stdin: {
|
|
55
|
-
contents: "export * from \"css-tree\";",
|
|
56
|
-
resolveDir: import.meta.dirname
|
|
57
|
-
},
|
|
58
|
-
bundle: true,
|
|
59
|
-
minify: true,
|
|
60
|
-
format: "esm",
|
|
61
|
-
platform: "browser",
|
|
62
|
-
legalComments: "none",
|
|
63
|
-
banner: { js: banner },
|
|
64
|
-
plugins: [patchPlugin],
|
|
65
|
-
outfile: "../vendor/css-tree.js"
|
|
66
|
-
});
|