Upload index.html with huggingface_hub
Browse files- index.html +313 -17
index.html
CHANGED
|
@@ -1,19 +1,315 @@
|
|
| 1 |
<!doctype html>
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
| 6 |
+
<title>GitHub Actions expiration check — check action expiry dates</title>
|
| 7 |
+
<meta name="description" content="This browser tool flags GitHub Actions lines affected by retired runner images, shut-down action versions, and Node runtimes past end of life.">
|
| 8 |
+
<style>
|
| 9 |
+
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
|
| 10 |
+
.wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
|
| 11 |
+
textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
|
| 12 |
+
.bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
|
| 13 |
+
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
|
| 14 |
+
table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
|
| 15 |
+
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
|
| 16 |
+
.note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
|
| 17 |
+
</style>
|
| 18 |
+
<script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
|
| 19 |
+
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/actions-expiry-lint"></head>
|
| 20 |
+
<body>
|
| 21 |
+
<div class="wrap">
|
| 22 |
+
<h1>GitHub Actions expiration check — check action expiry dates</h1>
|
| 23 |
+
<p class="lede">Flags GitHub Actions lines that stop working on a date - retired runner images, shut-down action versions, Node runtimes past end-of-life. Checked against any 'as of' date. Runs entirely in your browser — nothing is uploaded.</p>
|
| 24 |
+
<textarea id="src" spellcheck="false">name: build-and-release
|
| 25 |
+
|
| 26 |
+
on:
|
| 27 |
+
push:
|
| 28 |
+
branches: [main]
|
| 29 |
+
workflow_dispatch:
|
| 30 |
+
|
| 31 |
+
jobs:
|
| 32 |
+
test:
|
| 33 |
+
runs-on: ubuntu-20.04
|
| 34 |
+
strategy:
|
| 35 |
+
matrix:
|
| 36 |
+
node-version: ['18.20.4', '20.x', '21']
|
| 37 |
+
steps:
|
| 38 |
+
- uses: actions/checkout@v3
|
| 39 |
+
- uses: actions/setup-node@v3
|
| 40 |
+
with:
|
| 41 |
+
node-version: ${{ matrix.node-version }}
|
| 42 |
+
- uses: actions/cache@v3
|
| 43 |
+
with:
|
| 44 |
+
path: ~/.npm
|
| 45 |
+
key: npm-${{ hashFiles('package-lock.json') }}
|
| 46 |
+
- run: npm ci && npm test
|
| 47 |
+
- name: publish coverage
|
| 48 |
+
uses: codecov/codecov-action@v3
|
| 49 |
+
- uses: actions/upload-artifact@v3
|
| 50 |
+
with:
|
| 51 |
+
name: coverage
|
| 52 |
+
path: coverage/
|
| 53 |
+
|
| 54 |
+
docs:
|
| 55 |
+
runs-on: ubuntu-latest
|
| 56 |
+
steps:
|
| 57 |
+
- uses: actions/checkout@v4
|
| 58 |
+
- uses: actions/setup-python@v4
|
| 59 |
+
with:
|
| 60 |
+
python-version: '3.12'
|
| 61 |
+
- name: export the version
|
| 62 |
+
run: echo "::set-output name=version::$(cat VERSION)"
|
| 63 |
+
- name: remember the cache hit
|
| 64 |
+
run: echo "::save-state name=cachehit::true"
|
| 65 |
+
|
| 66 |
+
mac:
|
| 67 |
+
runs-on: macos-13
|
| 68 |
+
steps:
|
| 69 |
+
- uses: actions/checkout@v4
|
| 70 |
+
- uses: actions/setup-node@v4
|
| 71 |
+
with:
|
| 72 |
+
node-version: '22'
|
| 73 |
+
- run: echo "::add-path::$HOME/.local/bin"
|
| 74 |
+
|
| 75 |
+
win:
|
| 76 |
+
runs-on: windows-2019
|
| 77 |
+
steps:
|
| 78 |
+
- uses: actions/checkout@v4
|
| 79 |
+
- uses: peter-evans/create-pull-request@master
|
| 80 |
+
|
| 81 |
+
release:
|
| 82 |
+
needs: [test, docs]
|
| 83 |
+
runs-on: ubuntu-20.04
|
| 84 |
+
steps:
|
| 85 |
+
- uses: actions/download-artifact@v3
|
| 86 |
+
- uses: actions/create-release@v1
|
| 87 |
+
env:
|
| 88 |
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}</textarea>
|
| 89 |
+
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
|
| 90 |
+
<div id="out" aria-live="polite"></div>
|
| 91 |
+
<div id="paid"><strong>Free here:</strong> Check the workflow file open in your editor against all 13 dated checks. <strong>In the editor:</strong> Sweep every workflow in the repo and write a dated actions-expiry-report.md you keep — that part asks for a licence key.</div>
|
| 92 |
+
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
|
| 93 |
+
</div>
|
| 94 |
+
<script>var AXL_RULES = {
|
| 95 |
+
"revision": "2026.09",
|
| 96 |
+
"revised": "2026-09-11",
|
| 97 |
+
"soon_days": 270,
|
| 98 |
+
"checks": [
|
| 99 |
+
{"id": "runner-retired", "sev": "error", "what": "runs-on names a runner image GitHub has already removed"},
|
| 100 |
+
{"id": "runner-retiring", "sev": "warn", "what": "runs-on names a runner image with a retirement date still ahead"},
|
| 101 |
+
{"id": "runner-floating", "sev": "info", "what": "runs-on uses a -latest label, so the image moves under you"},
|
| 102 |
+
{"id": "action-shutdown", "sev": "error", "what": "action major version whose backing service was switched off"},
|
| 103 |
+
{"id": "action-runtime-old", "sev": "warn", "what": "action major version built on a Node runtime the runner no longer provides"},
|
| 104 |
+
{"id": "action-archived", "sev": "error", "what": "action repository is archived and takes no more fixes"},
|
| 105 |
+
{"id": "action-unpinned", "sev": "warn", "what": "uses: points at a moving branch instead of a tag or commit"},
|
| 106 |
+
{"id": "node-eol", "sev": "error", "what": "node-version is past its end-of-life date"},
|
| 107 |
+
{"id": "node-eol-soon", "sev": "warn", "what": "node-version reaches end-of-life within the notice window"},
|
| 108 |
+
{"id": "node-odd-release", "sev": "warn", "what": "node-version is an odd-numbered line that never becomes LTS"},
|
| 109 |
+
{"id": "cmd-set-output", "sev": "error", "what": "::set-output:: workflow command, disabled by the runner"},
|
| 110 |
+
{"id": "cmd-save-state", "sev": "error", "what": "::save-state:: workflow command, disabled by the runner"},
|
| 111 |
+
{"id": "cmd-set-env", "sev": "error", "what": "::set-env:: or ::add-path:: workflow command, disabled by the runner"}
|
| 112 |
+
],
|
| 113 |
+
"runners": [
|
| 114 |
+
{"label": "ubuntu-18.04", "retired": "2022-12-01", "use": "ubuntu-24.04"},
|
| 115 |
+
{"label": "ubuntu-20.04", "retired": "2025-04-15", "use": "ubuntu-24.04"},
|
| 116 |
+
{"label": "macos-12", "retired": "2024-12-03", "use": "macos-15"},
|
| 117 |
+
{"label": "macos-13", "retired": "2025-12-04", "use": "macos-15"},
|
| 118 |
+
{"label": "windows-2019", "retired": "2025-06-30", "use": "windows-2025"}
|
| 119 |
+
],
|
| 120 |
+
"floating": ["ubuntu-latest", "macos-latest", "windows-latest"],
|
| 121 |
+
"actions": [
|
| 122 |
+
{"name": "actions/upload-artifact", "shutdown_below": 4, "shutdown_on": "2025-01-30", "use": "v4"},
|
| 123 |
+
{"name": "actions/download-artifact", "shutdown_below": 4, "shutdown_on": "2025-01-30", "use": "v4"},
|
| 124 |
+
{"name": "actions/checkout", "old_below": 4, "use": "v4"},
|
| 125 |
+
{"name": "actions/setup-node", "old_below": 4, "use": "v4"},
|
| 126 |
+
{"name": "actions/cache", "old_below": 4, "use": "v4"},
|
| 127 |
+
{"name": "actions/setup-python", "old_below": 5, "use": "v5"},
|
| 128 |
+
{"name": "actions/setup-java", "old_below": 4, "use": "v4"},
|
| 129 |
+
{"name": "actions/setup-go", "old_below": 5, "use": "v5"},
|
| 130 |
+
{"name": "actions/setup-dotnet", "old_below": 4, "use": "v4"},
|
| 131 |
+
{"name": "actions/github-script", "old_below": 7, "use": "v7"},
|
| 132 |
+
{"name": "actions/deploy-pages", "old_below": 4, "use": "v4"},
|
| 133 |
+
{"name": "actions/upload-pages-artifact", "old_below": 3, "use": "v3"},
|
| 134 |
+
{"name": "docker/build-push-action", "old_below": 5, "use": "v5"},
|
| 135 |
+
{"name": "docker/login-action", "old_below": 3, "use": "v3"},
|
| 136 |
+
{"name": "docker/setup-buildx-action", "old_below": 3, "use": "v3"},
|
| 137 |
+
{"name": "codecov/codecov-action", "old_below": 4, "use": "v4"}
|
| 138 |
+
],
|
| 139 |
+
"archived": [
|
| 140 |
+
{"name": "actions/create-release", "use": "softprops/action-gh-release, or gh release create"},
|
| 141 |
+
{"name": "actions/upload-release-asset", "use": "softprops/action-gh-release, or gh release upload"},
|
| 142 |
+
{"name": "actions/setup-ruby", "use": "ruby/setup-ruby"}
|
| 143 |
+
],
|
| 144 |
+
"node": [
|
| 145 |
+
{"major": "12", "eol": "2022-04-30"},
|
| 146 |
+
{"major": "14", "eol": "2023-04-30"},
|
| 147 |
+
{"major": "16", "eol": "2023-09-11"},
|
| 148 |
+
{"major": "17", "eol": "2022-06-01", "odd": true},
|
| 149 |
+
{"major": "18", "eol": "2025-04-30"},
|
| 150 |
+
{"major": "19", "eol": "2023-06-01", "odd": true},
|
| 151 |
+
{"major": "20", "eol": "2026-04-30"},
|
| 152 |
+
{"major": "21", "eol": "2024-06-01", "odd": true},
|
| 153 |
+
{"major": "22", "eol": "2027-04-30"},
|
| 154 |
+
{"major": "23", "eol": "2025-06-01", "odd": true},
|
| 155 |
+
{"major": "24", "eol": "2028-04-30"}
|
| 156 |
+
]
|
| 157 |
+
}
|
| 158 |
+
;</script>
|
| 159 |
+
<script>// Actions Expiry Lint — the brain. One file, runs in Node (extension) and in the browser (free web check).
|
| 160 |
+
'use strict';
|
| 161 |
+
var D = (typeof module !== 'undefined' && module.exports) ? require('./rules.json') : window.AXL_RULES;
|
| 162 |
+
|
| 163 |
+
var RULES = D.checks;
|
| 164 |
+
var RULE_COUNT = RULES.length;
|
| 165 |
+
|
| 166 |
+
function days(from, to) { return Math.floor((Date.parse(to) - Date.parse(from)) / 86400000); }
|
| 167 |
+
function ago(n) { return n === 1 ? '1 day ago' : n + ' days ago'; }
|
| 168 |
+
function within(n) { return n === 1 ? 'in 1 day' : 'in ' + n + ' days'; }
|
| 169 |
+
function esc(s) { return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
|
| 170 |
+
function token(line, label) { return new RegExp('(^|[^A-Za-z0-9._-])' + esc(label) + '([^A-Za-z0-9._/-]|$)').test(line); }
|
| 171 |
+
|
| 172 |
+
var USES = /uses\s*:\s*['"]?([A-Za-z0-9_.-]+\/[A-Za-z0-9_./-]+?)@([^\s'"#]+)/;
|
| 173 |
+
var NODEV = /node-version\s*:\s*(.+)$/;
|
| 174 |
+
var SHA = /^[0-9a-f]{40}$/i;
|
| 175 |
+
|
| 176 |
+
function check(text, opts) {
|
| 177 |
+
opts = opts || {};
|
| 178 |
+
var today = String(opts.today || new Date().toISOString().slice(0, 10)).slice(0, 10);
|
| 179 |
+
var out = [];
|
| 180 |
+
var lines = String(text == null ? '' : text).split(/\r?\n/);
|
| 181 |
+
|
| 182 |
+
function add(id, msg, ln) {
|
| 183 |
+
for (var i = 0; i < RULES.length; i++) {
|
| 184 |
+
if (RULES[i].id === id) { out.push({ check: id, sev: RULES[i].sev, msg: msg, line: ln }); return; }
|
| 185 |
+
}
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
for (var i = 0; i < lines.length; i++) {
|
| 189 |
+
var ln = i + 1;
|
| 190 |
+
var raw = lines[i];
|
| 191 |
+
if (/^\s*#/.test(raw)) continue;
|
| 192 |
+
var line = raw.replace(/#.*$/, '');
|
| 193 |
+
if (!line.trim()) continue;
|
| 194 |
+
|
| 195 |
+
// --- runner images -------------------------------------------------
|
| 196 |
+
if (!/uses\s*:/.test(line)) {
|
| 197 |
+
for (var r = 0; r < D.runners.length; r++) {
|
| 198 |
+
var im = D.runners[r];
|
| 199 |
+
if (!token(line, im.label)) continue;
|
| 200 |
+
var d = days(im.retired, today);
|
| 201 |
+
if (d >= 0) add('runner-retired', 'Runner image ' + im.label + ' was removed on ' + im.retired + ' (' + ago(d) + '). Jobs asking for it fail to start. Move to ' + im.use + '.', ln);
|
| 202 |
+
else add('runner-retiring', 'Runner image ' + im.label + ' is scheduled for removal on ' + im.retired + ' (' + within(-d) + ' from ' + today + '). Move to ' + im.use + ' before then.', ln);
|
| 203 |
+
}
|
| 204 |
+
for (var f = 0; f < D.floating.length; f++) {
|
| 205 |
+
if (token(line, D.floating[f])) add('runner-floating', D.floating[f] + ' is a moving label — GitHub repoints it to a newer image without changing your file. Pin the image if the build depends on it.', ln);
|
| 206 |
+
}
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
// --- uses: action@ref ----------------------------------------------
|
| 210 |
+
var mu = line.match(USES);
|
| 211 |
+
if (mu) {
|
| 212 |
+
var repo = mu[1], ref = mu[2], a, k;
|
| 213 |
+
for (k = 0; k < D.archived.length; k++) {
|
| 214 |
+
a = D.archived[k];
|
| 215 |
+
if (repo === a.name || repo.indexOf(a.name + '/') === 0) add('action-archived', repo + ' is an archived repository — it is read-only and gets no fixes or runtime updates. Use ' + a.use + ' instead.', ln);
|
| 216 |
+
}
|
| 217 |
+
var mver = ref.match(/^v?(\d+)/);
|
| 218 |
+
if (!mver && !SHA.test(ref)) add('action-unpinned', 'uses: ' + repo + '@' + ref + ' follows a moving ref. Whatever is on that branch runs with your repository token on the next push. Pin a release tag or a full commit SHA.', ln);
|
| 219 |
+
if (mver) {
|
| 220 |
+
var major = parseInt(mver[1], 10);
|
| 221 |
+
for (k = 0; k < D.actions.length; k++) {
|
| 222 |
+
a = D.actions[k];
|
| 223 |
+
if (repo !== a.name) continue;
|
| 224 |
+
if (a.shutdown_below && major < a.shutdown_below) {
|
| 225 |
+
var sd = days(a.shutdown_on, today);
|
| 226 |
+
if (sd >= 0) add('action-shutdown', repo + '@v' + major + ' was switched off on ' + a.shutdown_on + ' (' + ago(sd) + ') — the service behind it no longer answers, so the step errors out. Use ' + a.use + '.', ln);
|
| 227 |
+
else add('action-runtime-old', repo + '@v' + major + ' is scheduled for shutdown on ' + a.shutdown_on + ' (' + within(-sd) + ' from ' + today + '). Move to ' + a.use + ' before then.', ln);
|
| 228 |
+
} else if (a.old_below && major < a.old_below) {
|
| 229 |
+
add('action-runtime-old', repo + '@v' + major + ' runs on a Node runtime the current runner images no longer ship. Use ' + a.use + '.', ln);
|
| 230 |
+
}
|
| 231 |
+
}
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
// --- node-version ---------------------------------------------------
|
| 236 |
+
var mn = line.match(NODEV);
|
| 237 |
+
var majors = [];
|
| 238 |
+
if (mn) {
|
| 239 |
+
var toks = mn[1].replace(/[\[\]'",]/g, ' ').split(/\s+/);
|
| 240 |
+
for (k = 0; k < toks.length; k++) {
|
| 241 |
+
var mt = toks[k].match(/^(\d+)(\.|$)/);
|
| 242 |
+
if (mt && majors.indexOf(mt[1]) < 0) majors.push(mt[1]);
|
| 243 |
+
}
|
| 244 |
+
}
|
| 245 |
+
for (var mi = 0; mi < majors.length; mi++) {
|
| 246 |
+
var maj = majors[mi];
|
| 247 |
+
for (k = 0; k < D.node.length; k++) {
|
| 248 |
+
var nv = D.node[k];
|
| 249 |
+
if (nv.major !== maj) continue;
|
| 250 |
+
var nd = days(nv.eol, today);
|
| 251 |
+
if (nd >= 0) add('node-eol', 'Node ' + maj + ' left maintenance on ' + nv.eol + ' (' + ago(nd) + '). It gets no more security patches, and setup-node keeps installing it anyway.', ln);
|
| 252 |
+
else if (-nd <= D.soon_days) add('node-eol-soon', 'Node ' + maj + ' reaches end-of-life on ' + nv.eol + ' (' + within(-nd) + ' from ' + today + '). Plan the bump inside this window.', ln);
|
| 253 |
+
if (nv.odd) add('node-odd-release', 'Node ' + maj + ' is an odd-numbered line — odd lines never become LTS, so this one was short-lived by design. Pick an even line.', ln);
|
| 254 |
+
}
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
// --- retired workflow commands ---------------------------------------
|
| 258 |
+
if (/::\s*set-output/.test(line)) add('cmd-set-output', 'The ::set-output:: command was disabled by the runner; the value never reaches the next step. Append to the $GITHUB_OUTPUT file instead.', ln);
|
| 259 |
+
if (/::\s*save-state/.test(line)) add('cmd-save-state', 'The ::save-state:: command was disabled by the runner. Append to the $GITHUB_STATE file instead.', ln);
|
| 260 |
+
if (/::\s*set-env/.test(line) || /::\s*add-path/.test(line)) add('cmd-set-env', 'The ::set-env:: and ::add-path:: commands were disabled in November 2020 after a command-injection advisory. Append to $GITHUB_ENV or $GITHUB_PATH instead.', ln);
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
out.sort(function (x, y) { return (x.line - y.line) || (x.check < y.check ? -1 : 1); });
|
| 264 |
+
return { findings: out, revision: D.revision, revised: D.revised, checked_as_of: today, rule_count: RULE_COUNT };
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
var AXLENGINE = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULE_COUNT };
|
| 268 |
+
if (typeof module !== 'undefined' && module.exports) module.exports = AXLENGINE;
|
| 269 |
+
else window.AXLENGINE = AXLENGINE;
|
| 270 |
+
</script>
|
| 271 |
+
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
|
| 272 |
+
'use strict';
|
| 273 |
+
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); }
|
| 274 |
+
function summaryLine(res) {
|
| 275 |
+
const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
|
| 276 |
+
return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
|
| 277 |
+
}
|
| 278 |
+
function toText(res, label) {
|
| 279 |
+
const f = (res && res.findings) || [];
|
| 280 |
+
const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
|
| 281 |
+
f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); });
|
| 282 |
+
return lines.join('\n');
|
| 283 |
+
}
|
| 284 |
+
function toHtml(res) {
|
| 285 |
+
const f = (res && res.findings) || [];
|
| 286 |
+
if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
|
| 287 |
+
const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
|
| 288 |
+
return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
|
| 289 |
+
}
|
| 290 |
+
var AXLREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
|
| 291 |
+
if (typeof module !== 'undefined' && module.exports) module.exports = AXLREPORT;
|
| 292 |
+
else window.AXLREPORT = AXLREPORT;
|
| 293 |
+
</script>
|
| 294 |
+
<script>
|
| 295 |
+
(function(){
|
| 296 |
+
var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
|
| 297 |
+
today.value=new Date().toISOString().slice(0,10);
|
| 298 |
+
function run(){ try { var res=window.AXLENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.AXLREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
|
| 299 |
+
go.onclick=run; if (src.value.trim()) run();
|
| 300 |
+
})();
|
| 301 |
+
</script>
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://marketplace.visualstudio.com/items?itemName=ReadyStack.actions-expiry-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'actions-expiry-lint',src:'mp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">VS Code Marketplace</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.actions-expiry-lint</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://buy.polar.sh/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
|
| 305 |
+
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind Actions Expiry Lint every day. When a rule changes, you get a single email with what changed and the updated check. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"actions-expiry-lint",lang:"en",source:"rule_alert"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section>
|
| 306 |
+
|
| 307 |
+
<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">The GitHub Actions lines that stop working on a date, and the day each one stopped</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">Contract DevOps work is commonly quoted at $75-$150 an hour, so one licence is under half an hour of it.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_XCrmjhZWtrYL52PyB4bFpa56M9QOjPckoP0Jd1Niz9b" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
|
| 308 |
+
<section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does Actions Expiry Lint actually check?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads GitHub Actions workflow files and flags every line that stops working on a date: retired runner images, action major versions whose backing service was switched off, Node versions past end-of-life, archived action repositories, and workflow commands the runner disabled. Each finding names the date it happened and the replacement line. Thirteen checks, all date-driven.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is this for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">CI maintainers, platform engineers and solo maintainers who own the .github/workflows folder in a repository nobody has opened for a year. It is most useful right after an AI assistant or an old template produced the workflow, because those sources reproduce runner images and action versions that were correct on the day they were trained.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why will actionlint or Dependabot not tell me this?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">actionlint checks workflow syntax and expression types. Dependabot raises a pull request when an action has a newer tag. Neither one holds a calendar. Neither will tell you that the macos-13 image was removed on 2025-12-04, that upload-artifact v3 was switched off on 2025-01-30, or that Node 20 left maintenance on 2026-04-30.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what needs a licence key?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free covers one file completely: open a workflow in VS Code and all thirteen checks run with full messages and full dates, or paste the file into the web page instead. The paid part is scope and ownership - one command sweeps every workflow in the repository and writes a dated report file you keep.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What would this cost me otherwise?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Contract DevOps work is commonly quoted at $75 to $150 an hour, so one licence costs less than half an hour of it. The comparison that matters more is the release window: a job that names a removed runner image does not fail slowly or halfway, it refuses to start at all.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What happens when the rule set gets old?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Every check is a date plus a replacement, held in one rules.json file, and the engine compares those dates against an as-of day you choose. Set the date back and the same file reports different findings, which is how you can see what your workflow looked like when it was written and what it looks like today.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Actions Expiry Lint actually check?", "acceptedAnswer": {"@type": "Answer", "text": "It reads GitHub Actions workflow files and flags every line that stops working on a date: retired runner images, action major versions whose backing service was switched off, Node versions past end-of-life, archived action repositories, and workflow commands the runner disabled. Each finding names the date it happened and the replacement line. Thirteen checks, all date-driven."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "CI maintainers, platform engineers and solo maintainers who own the .github/workflows folder in a repository nobody has opened for a year. It is most useful right after an AI assistant or an old template produced the workflow, because those sources reproduce runner images and action versions that were correct on the day they were trained."}}, {"@type": "Question", "name": "Why will actionlint or Dependabot not tell me this?", "acceptedAnswer": {"@type": "Answer", "text": "actionlint checks workflow syntax and expression types. Dependabot raises a pull request when an action has a newer tag. Neither one holds a calendar. Neither will tell you that the macos-13 image was removed on 2025-12-04, that upload-artifact v3 was switched off on 2025-01-30, or that Node 20 left maintenance on 2026-04-30."}}, {"@type": "Question", "name": "What is free and what needs a licence key?", "acceptedAnswer": {"@type": "Answer", "text": "Free covers one file completely: open a workflow in VS Code and all thirteen checks run with full messages and full dates, or paste the file into the web page instead. The paid part is scope and ownership - one command sweeps every workflow in the repository and writes a dated report file you keep."}}, {"@type": "Question", "name": "What would this cost me otherwise?", "acceptedAnswer": {"@type": "Answer", "text": "Contract DevOps work is commonly quoted at $75 to $150 an hour, so one licence costs less than half an hour of it. The comparison that matters more is the release window: a job that names a removed runner image does not fail slowly or halfway, it refuses to start at all."}}, {"@type": "Question", "name": "What happens when the rule set gets old?", "acceptedAnswer": {"@type": "Answer", "text": "Every check is a date plus a replacement, held in one rules.json file, and the engine compares those dates against an as-of day you choose. Set the date back and the same file reports different findings, which is how you can see what your workflow looked like when it was written and what it looks like today."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:0,slug:"actions-expiry-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/workflow-break-date-audit"><span class="yj-rel-t">GitHub Actions Break-Date Audit</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/workflow-expiry-check"><span class="yj-rel-t">Workflow Expiry - dated GitHub Actions breakage</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cron-schedule-lint"><span class="yj-rel-t">Cron Schedule Lint for crontab, Kubernetes CronJob, GitHub Actions, Spring and</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/actions-deprecation-lint-2026"><span class="yj-rel-t">GitHub Actions Deprecation Lint: the 2026 runner and action EOL dates</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/webflow-restaurant-template"><span class="yj-rel-t">Webflow Restaurant Template — delivered as self-contained HTML you own: 26 pag</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cert-lifetime-lint-47day"><span class="yj-rel-t">Cert Lifetime Lint - 200/100/47-Day TLS Audit</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section><!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&","<":"<",">":">",'"':"""}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div>
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>
|
| 312 |
+
|
| 313 |
+
<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script>
|
| 314 |
+
</body>
|
| 315 |
</html>
|