@krx3d/tizentube2 1.30.20 → 1.30.40
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.
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
# - dist/userScript.js (browser userscript, bundled & minified)
|
|
10
10
|
# - dist/service.js (DIAL service, bundled with Rollup)
|
|
11
11
|
# - Publishes the package to https://www.npmjs.com on release only
|
|
12
|
-
# -
|
|
13
|
-
# -
|
|
12
|
+
# - Deprecates older versions once the new one is confirmed live on npm
|
|
13
|
+
# - Fails the run if npm's `latest` tag does not match what it published
|
|
14
14
|
# - Purges jsDelivr CDN cache after publishing
|
|
15
15
|
#
|
|
16
16
|
# WHEN IT RUNS
|
|
@@ -30,14 +30,24 @@
|
|
|
30
30
|
#
|
|
31
31
|
# AUTOMATIC VERSION CLEANUP
|
|
32
32
|
# -------------------------
|
|
33
|
-
# After publishing
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
# - Deprecates versions >72 hours old (unpublish no longer allowed)
|
|
37
|
-
# - Result: Only the latest version remains active on npm
|
|
33
|
+
# After publishing, this workflow tidies up older versions — but only once the
|
|
34
|
+
# new version is actually readable on npm, and only with writes that change
|
|
35
|
+
# something. Both rules exist because of a real failure:
|
|
38
36
|
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
37
|
+
# - npm publish is asynchronous. The CLI exits 0 while the version is still
|
|
38
|
+
# being processed, and 1.30.30 only appeared in the registry 96 seconds later.
|
|
39
|
+
# - Unpublish and deprecate both work by rewriting the ENTIRE package document.
|
|
40
|
+
# A rewrite built from a snapshot taken before the new version landed deletes
|
|
41
|
+
# it again — which is how 1.30.30 vanished one second after it appeared,
|
|
42
|
+
# leaving `latest` at 1.30.20 while the GitHub release said 1.30.30.
|
|
43
|
+
#
|
|
44
|
+
# So:
|
|
45
|
+
# - Nothing touches the registry until GET /<pkg>/<version> succeeds
|
|
46
|
+
# - Versions that already carry a deprecation message are skipped entirely
|
|
47
|
+
# - The current version and every dist-tag target are never touched
|
|
48
|
+
# - Unpublishing is off (ALLOW_UNPUBLISH); npm rejects it from a granular token
|
|
49
|
+
# that bypasses 2FA, and each rejected attempt was still a document rewrite
|
|
50
|
+
# - The job fails if `latest` does not end up pointing at what it published
|
|
41
51
|
#
|
|
42
52
|
# VERSION BUMP RULE (push to main only)
|
|
43
53
|
# -------------------------------------
|
|
@@ -144,6 +154,11 @@ env:
|
|
|
144
154
|
PACKAGE_NAME_GH: 'KrX3D/TizenTube'
|
|
145
155
|
NODE_VERSION: '24'
|
|
146
156
|
UNPUBLISH_WINDOW_HOURS: '72'
|
|
157
|
+
# npm refuses `npm unpublish` from a granular access token that bypasses
|
|
158
|
+
# two-factor authentication, so every attempt was a 403 — and every 403 was
|
|
159
|
+
# still a full rewrite of the package document, which is how a freshly
|
|
160
|
+
# published version gets erased. Left off until the token can do it.
|
|
161
|
+
ALLOW_UNPUBLISH: 'false'
|
|
147
162
|
|
|
148
163
|
jobs:
|
|
149
164
|
build-publish-clean:
|
|
@@ -414,63 +429,137 @@ jobs:
|
|
|
414
429
|
echo "Publishing package..."
|
|
415
430
|
npm publish --access public
|
|
416
431
|
|
|
417
|
-
|
|
432
|
+
# --------------------
|
|
433
|
+
# CONFIRM THE PUBLISH LANDED
|
|
434
|
+
# --------------------
|
|
435
|
+
# npm's publish endpoint is asynchronous. The CLI prints "Your package is
|
|
436
|
+
# being processed and may take a few minutes to become available" and exits
|
|
437
|
+
# 0 well before the version exists in the registry document.
|
|
438
|
+
#
|
|
439
|
+
# That is not a cosmetic delay, because every cleanup step below works by
|
|
440
|
+
# PUTting the WHOLE package document back. npm builds that document from
|
|
441
|
+
# whatever it can read at the time, so a PUT issued while the new version
|
|
442
|
+
# is still in flight writes a snapshot that does not contain it — and the
|
|
443
|
+
# version that was just published is gone again.
|
|
444
|
+
#
|
|
445
|
+
# 1.30.30 is what that looks like: published 18:40:16, appeared in the
|
|
446
|
+
# registry's own time map at 18:41:52, erased at 18:41:53 by the last
|
|
447
|
+
# npm deprecate of the very same job. The GitHub release said 1.30.30
|
|
448
|
+
# while the latest tag pointed back at 1.30.20, so every TV loading the
|
|
449
|
+
# userscript from the CDN got a bundle one version behind its own app.
|
|
450
|
+
#
|
|
451
|
+
# So nothing touches the registry until the new version is readable, and if
|
|
452
|
+
# it never becomes readable the job fails instead of cleaning up around a
|
|
453
|
+
# version that isn't there.
|
|
454
|
+
- name: Confirm the published version is live on npm
|
|
418
455
|
if: steps.publish_npm.outcome == 'success'
|
|
456
|
+
id: confirm_publish
|
|
457
|
+
env:
|
|
458
|
+
PKG: ${{ env.PACKAGE_NAME }}
|
|
419
459
|
run: |
|
|
420
|
-
|
|
421
|
-
|
|
460
|
+
set -euo pipefail
|
|
461
|
+
VER=$(node -e "console.log(require('./package.json').version)")
|
|
462
|
+
echo "$VER" > /tmp/current_version.txt
|
|
463
|
+
echo "Waiting for ${PKG}@${VER} to become readable..."
|
|
464
|
+
for i in $(seq 1 40); do
|
|
465
|
+
# The registry is read directly rather than through npm view, which
|
|
466
|
+
# has its own HTTP cache and will happily answer from a packument
|
|
467
|
+
# fetched before the publish landed.
|
|
468
|
+
if curl -fsSL "https://registry.npmjs.org/${PKG}/${VER}" -o /tmp/published.json; then
|
|
469
|
+
echo "${PKG}@${VER} is live (attempt ${i})."
|
|
470
|
+
echo "confirmed=true" >> "$GITHUB_OUTPUT"
|
|
471
|
+
exit 0
|
|
472
|
+
fi
|
|
473
|
+
sleep 15
|
|
474
|
+
done
|
|
475
|
+
echo "confirmed=false" >> "$GITHUB_OUTPUT"
|
|
476
|
+
echo "::error::${PKG}@${VER} never became readable on npm after 10 minutes. Cleanup is skipped so nothing can overwrite it."
|
|
477
|
+
exit 1
|
|
422
478
|
|
|
423
479
|
# --------------------
|
|
424
|
-
# FETCH
|
|
480
|
+
# FETCH THE REGISTRY DOCUMENT
|
|
425
481
|
# --------------------
|
|
426
|
-
- name: Gather versions and
|
|
427
|
-
if: steps.
|
|
482
|
+
- name: Gather versions, times and deprecation state
|
|
483
|
+
if: steps.confirm_publish.outputs.confirmed == 'true'
|
|
428
484
|
env:
|
|
429
485
|
PKG: ${{ env.PACKAGE_NAME }}
|
|
430
486
|
run: |
|
|
431
487
|
set -euo pipefail
|
|
432
|
-
echo "Fetching
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
488
|
+
echo "Fetching the registry document for ${PKG} ..."
|
|
489
|
+
curl -fsSL "https://registry.npmjs.org/${PKG}" -o /tmp/packument.json
|
|
490
|
+
node -e '
|
|
491
|
+
const fs = require("fs");
|
|
492
|
+
const doc = JSON.parse(fs.readFileSync("/tmp/packument.json","utf8"));
|
|
493
|
+
const versions = Object.keys(doc.versions || {});
|
|
494
|
+
const deprecated = versions.filter(function (v) { return doc.versions[v].deprecated; });
|
|
495
|
+
fs.writeFileSync("/tmp/versions.json", JSON.stringify(versions));
|
|
496
|
+
fs.writeFileSync("/tmp/time.json", JSON.stringify(doc.time || {}));
|
|
497
|
+
fs.writeFileSync("/tmp/deprecated.json", JSON.stringify(deprecated));
|
|
498
|
+
fs.writeFileSync("/tmp/dist_tags.json", JSON.stringify(doc["dist-tags"] || {}));
|
|
499
|
+
console.log("Live versions: " + versions.length + ", already deprecated: " + deprecated.length);
|
|
500
|
+
console.log("dist-tags: " + JSON.stringify(doc["dist-tags"] || {}));
|
|
501
|
+
'
|
|
502
|
+
echo "Current repo version: $(cat /tmp/current_version.txt)"
|
|
439
503
|
|
|
440
504
|
# --------------------
|
|
441
505
|
# DECIDE WHAT TO REMOVE
|
|
442
506
|
# --------------------
|
|
507
|
+
# Two rules, both about never issuing a registry write that changes
|
|
508
|
+
# nothing — since each one rewrites the whole package document, and one of
|
|
509
|
+
# those rewrites is what erased 1.30.30:
|
|
510
|
+
#
|
|
511
|
+
# * a version that already carries a deprecation message is skipped. All
|
|
512
|
+
# 80 live versions were already deprecated, so the previous run made
|
|
513
|
+
# about 80 full-document writes for no effect at all.
|
|
514
|
+
#
|
|
515
|
+
# * unpublishing is off unless ALLOW_UNPUBLISH is "true". The npm token
|
|
516
|
+
# here is a granular access token, and npm refuses unpublish from tokens
|
|
517
|
+
# that bypass two-factor authentication ("Granular access tokens that
|
|
518
|
+
# bypass two-factor authentication may not perform this action"). Every
|
|
519
|
+
# attempt returned 403 — and every one of those was still a write.
|
|
520
|
+
# Re-enable it only with a token that can actually perform it.
|
|
521
|
+
#
|
|
522
|
+
# The version just published is kept, as before, and so is whatever each
|
|
523
|
+
# dist-tag resolves to: installing the tag npm points people at must not
|
|
524
|
+
# print a deprecation warning. The latest tag was itself deprecated by the
|
|
525
|
+
# run that lost 1.30.30.
|
|
443
526
|
- name: Decide versions to unpublish / deprecate
|
|
444
|
-
if: steps.
|
|
527
|
+
if: steps.confirm_publish.outputs.confirmed == 'true'
|
|
445
528
|
env:
|
|
446
529
|
UNPUBLISH_WINDOW_HOURS: ${{ env.UNPUBLISH_WINDOW_HOURS }}
|
|
530
|
+
ALLOW_UNPUBLISH: ${{ env.ALLOW_UNPUBLISH }}
|
|
447
531
|
run: |
|
|
448
532
|
set -euo pipefail
|
|
449
533
|
node -e '
|
|
450
534
|
const fs = require("fs");
|
|
451
535
|
const versions = JSON.parse(fs.readFileSync("/tmp/versions.json","utf8")||"[]");
|
|
452
536
|
const times = JSON.parse(fs.readFileSync("/tmp/time.json","utf8")||"{}");
|
|
537
|
+
const deprecated = new Set(JSON.parse(fs.readFileSync("/tmp/deprecated.json","utf8")||"[]"));
|
|
538
|
+
const distTags = JSON.parse(fs.readFileSync("/tmp/dist_tags.json","utf8")||"{}");
|
|
453
539
|
const current = fs.readFileSync("/tmp/current_version.txt","utf8").trim();
|
|
454
540
|
|
|
455
541
|
const windowHours = Number(process.env.UNPUBLISH_WINDOW_HOURS || 72);
|
|
542
|
+
const allowUnpublish = String(process.env.ALLOW_UNPUBLISH || "") === "true";
|
|
456
543
|
const now = Date.now();
|
|
457
544
|
|
|
545
|
+
const keep = new Set([current].concat(Object.keys(distTags).map(function (tag) { return distTags[tag]; })));
|
|
546
|
+
|
|
458
547
|
const toUnpublish = [];
|
|
459
548
|
const toDeprecate = [];
|
|
460
549
|
|
|
461
550
|
for (const v of versions) {
|
|
462
|
-
if (v
|
|
551
|
+
if (keep.has(v)) continue;
|
|
463
552
|
const t = times[v] ? new Date(times[v]) : null;
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (
|
|
467
|
-
else toDeprecate.push(v);
|
|
553
|
+
const ageHours = t ? (now - t.getTime()) / 36e5 : Infinity;
|
|
554
|
+
if (allowUnpublish && t && ageHours <= windowHours) { toUnpublish.push(v); continue; }
|
|
555
|
+
if (!deprecated.has(v)) toDeprecate.push(v);
|
|
468
556
|
}
|
|
469
557
|
|
|
470
558
|
fs.writeFileSync("/tmp/to_unpublish.txt", toUnpublish.length ? toUnpublish.join("\n") + "\n" : "");
|
|
471
559
|
fs.writeFileSync("/tmp/to_deprecate.txt", toDeprecate.length ? toDeprecate.join("\n") + "\n" : "");
|
|
472
|
-
console.log("
|
|
473
|
-
console.log("To
|
|
560
|
+
console.log("Keeping (current version + every dist-tag): " + Array.from(keep).join(", "));
|
|
561
|
+
console.log("To unpublish (<= " + windowHours + "h, enabled=" + allowUnpublish + "):", toUnpublish);
|
|
562
|
+
console.log("To deprecate (not already deprecated):", toDeprecate);
|
|
474
563
|
'
|
|
475
564
|
|
|
476
565
|
echo ""
|
|
@@ -485,7 +574,7 @@ jobs:
|
|
|
485
574
|
# UNPUBLISH
|
|
486
575
|
# --------------------
|
|
487
576
|
- name: Unpublish old versions (verified) and fallback to deprecate
|
|
488
|
-
if: steps.
|
|
577
|
+
if: steps.confirm_publish.outputs.confirmed == 'true'
|
|
489
578
|
env:
|
|
490
579
|
PKG: ${{ env.PACKAGE_NAME }}
|
|
491
580
|
run: |
|
|
@@ -542,7 +631,7 @@ jobs:
|
|
|
542
631
|
# DEPRECATE older (>72h)
|
|
543
632
|
# --------------------
|
|
544
633
|
- name: Deprecate older versions
|
|
545
|
-
if: steps.
|
|
634
|
+
if: steps.confirm_publish.outputs.confirmed == 'true'
|
|
546
635
|
env:
|
|
547
636
|
PKG: ${{ env.PACKAGE_NAME }}
|
|
548
637
|
run: |
|
|
@@ -563,13 +652,18 @@ jobs:
|
|
|
563
652
|
# Final wait + verify final state
|
|
564
653
|
# --------------------
|
|
565
654
|
- name: Wait before final verify
|
|
566
|
-
if: steps.
|
|
655
|
+
if: steps.confirm_publish.outputs.confirmed == 'true'
|
|
567
656
|
run: |
|
|
568
657
|
echo "Short wait to let registry propagate final state..."
|
|
569
658
|
sleep 8
|
|
570
659
|
|
|
660
|
+
# The cleanup steps above rewrite the whole package document, so the last
|
|
661
|
+
# thing this job does is check that the version it published is still the
|
|
662
|
+
# one npm hands out. Without this the job reported success while `latest`
|
|
663
|
+
# had silently fallen back a release, and the first sign of it was a TV
|
|
664
|
+
# loading a userscript older than its own app.
|
|
571
665
|
- name: Verify final state
|
|
572
|
-
if: steps.
|
|
666
|
+
if: steps.confirm_publish.outputs.confirmed == 'true'
|
|
573
667
|
env:
|
|
574
668
|
PKG: ${{ env.PACKAGE_NAME }}
|
|
575
669
|
run: |
|
|
@@ -577,14 +671,31 @@ jobs:
|
|
|
577
671
|
echo "Final versions on npm:"
|
|
578
672
|
npm view "${PKG}" versions --json
|
|
579
673
|
echo ""
|
|
580
|
-
|
|
581
|
-
npm view
|
|
674
|
+
|
|
675
|
+
# Read through the registry rather than `npm view`, whose cache is
|
|
676
|
+
# what made the original breakage invisible here.
|
|
677
|
+
curl -fsSL "https://registry.npmjs.org/${PKG}" -o /tmp/packument_final.json
|
|
678
|
+
node -e '
|
|
679
|
+
const fs = require("fs");
|
|
680
|
+
const doc = JSON.parse(fs.readFileSync("/tmp/packument_final.json","utf8"));
|
|
681
|
+
const want = fs.readFileSync("/tmp/current_version.txt","utf8").trim();
|
|
682
|
+
const latest = (doc["dist-tags"] || {}).latest;
|
|
683
|
+
const live = !!(doc.versions || {})[want];
|
|
684
|
+
const deprecated = live && !!doc.versions[want].deprecated;
|
|
685
|
+
console.log("published: " + want + ", latest tag: " + latest + ", still live: " + live);
|
|
686
|
+
let failed = false;
|
|
687
|
+
if (!live) { console.log("::error::" + want + " is no longer on npm. A cleanup write removed the version this run published."); failed = true; }
|
|
688
|
+
if (latest !== want) { console.log("::error::npm latest is " + latest + ", not " + want + ". The CDN will serve the wrong bundle to every TV."); failed = true; }
|
|
689
|
+
if (deprecated) { console.log("::error::" + want + " was published deprecated."); failed = true; }
|
|
690
|
+
if (failed) process.exit(1);
|
|
691
|
+
console.log("npm latest matches the version this run published.");
|
|
692
|
+
'
|
|
582
693
|
|
|
583
694
|
# --------------------
|
|
584
695
|
# Purge jsDelivr cache
|
|
585
696
|
# --------------------
|
|
586
697
|
- name: Generate jsDelivr URLs to purge
|
|
587
|
-
if: steps.
|
|
698
|
+
if: steps.confirm_publish.outputs.confirmed == 'true'
|
|
588
699
|
id: cdn_urls
|
|
589
700
|
run: |
|
|
590
701
|
set -euo pipefail
|
|
@@ -636,7 +747,7 @@ jobs:
|
|
|
636
747
|
printf '%s\n' "${URLS[@]}"
|
|
637
748
|
|
|
638
749
|
- name: Purge jsDelivr cache
|
|
639
|
-
if: steps.
|
|
750
|
+
if: steps.confirm_publish.outputs.confirmed == 'true'
|
|
640
751
|
# jsDelivr rate-limits how often the same path can be re-purged
|
|
641
752
|
# ("Purging request ... was throttled") — expected on frequent
|
|
642
753
|
# releases, not an actual failure of the publish itself. The next
|
|
@@ -652,7 +763,7 @@ jobs:
|
|
|
652
763
|
attempts: 3
|
|
653
764
|
|
|
654
765
|
- name: Purge individual URLs (fallback if batch fails)
|
|
655
|
-
if: steps.
|
|
766
|
+
if: steps.confirm_publish.outputs.confirmed == 'true' && failure()
|
|
656
767
|
continue-on-error: true
|
|
657
768
|
run: |
|
|
658
769
|
set +e
|
|
@@ -698,6 +809,6 @@ jobs:
|
|
|
698
809
|
echo "Individual purge attempts complete"
|
|
699
810
|
|
|
700
811
|
- name: Cache purge complete
|
|
701
|
-
if: steps.
|
|
812
|
+
if: steps.confirm_publish.outputs.confirmed == 'true'
|
|
702
813
|
|
|
703
814
|
run: echo "✅ jsDelivr cache purge complete (npm + GitHub)"
|