Generate asset versions during Docker builds
This commit is contained in:
@@ -10,6 +10,8 @@ COPY public ./public
|
|||||||
COPY server ./server
|
COPY server ./server
|
||||||
COPY shared ./shared
|
COPY shared ./shared
|
||||||
COPY db/migrations ./db/migrations
|
COPY db/migrations ./db/migrations
|
||||||
|
COPY scripts ./scripts
|
||||||
|
RUN node scripts/stamp-asset-version.mjs
|
||||||
|
|
||||||
# The optional Pi agent configuration is mounted read-only here at runtime.
|
# The optional Pi agent configuration is mounted read-only here at runtime.
|
||||||
ENV HOME=/home/node
|
ENV HOME=/home/node
|
||||||
|
|||||||
+3
-3
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Account — Roast Planner</title>
|
<title>Account — Roast Planner</title>
|
||||||
<link rel="stylesheet" href="/app.css?v=20260730-release2" />
|
<link rel="stylesheet" href="/app.css?v=__ASSET_VERSION__" />
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/icon.svg?v=__ASSET_VERSION__" type="image/svg+xml" />
|
||||||
<meta name="theme-color" content="#A8481A" />
|
<meta name="theme-color" content="#A8481A" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -254,6 +254,6 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/js/account.js?v=20260730-release2"></script>
|
<script type="module" src="/js/account.js?v=__ASSET_VERSION__"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+3
-3
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Admin — Roast Planner</title>
|
<title>Admin — Roast Planner</title>
|
||||||
<link rel="stylesheet" href="/app.css?v=20260730-release2" />
|
<link rel="stylesheet" href="/app.css?v=__ASSET_VERSION__" />
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/icon.svg?v=__ASSET_VERSION__" type="image/svg+xml" />
|
||||||
<meta name="theme-color" content="#A8481A" />
|
<meta name="theme-color" content="#A8481A" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -197,6 +197,6 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/js/admin.js?v=20260730-release2"></script>
|
<script type="module" src="/js/admin.js?v=__ASSET_VERSION__"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+3
-3
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Cupping — Roast Planner</title>
|
<title>Cupping — Roast Planner</title>
|
||||||
<link rel="stylesheet" href="/app.css?v=20260730-release2" />
|
<link rel="stylesheet" href="/app.css?v=__ASSET_VERSION__" />
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/icon.svg?v=__ASSET_VERSION__" type="image/svg+xml" />
|
||||||
<meta name="theme-color" content="#A8481A" />
|
<meta name="theme-color" content="#A8481A" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -260,6 +260,6 @@
|
|||||||
document.getElementById("cupping-session-view").classList.remove("hidden");
|
document.getElementById("cupping-session-view").classList.remove("hidden");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script type="module" src="/js/cupping.js?v=20260730-release2"></script>
|
<script type="module" src="/js/cupping.js?v=__ASSET_VERSION__"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+3
-3
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Reset your password — Roast Planner</title>
|
<title>Reset your password — Roast Planner</title>
|
||||||
<link rel="stylesheet" href="/app.css?v=20260730-release2" />
|
<link rel="stylesheet" href="/app.css?v=__ASSET_VERSION__" />
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/icon.svg?v=__ASSET_VERSION__" type="image/svg+xml" />
|
||||||
<meta name="theme-color" content="#A8481A" />
|
<meta name="theme-color" content="#A8481A" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -38,6 +38,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<script type="module" src="/js/forgot.js?v=20260730-release2"></script>
|
<script type="module" src="/js/forgot.js?v=__ASSET_VERSION__"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+5
-5
@@ -7,11 +7,11 @@
|
|||||||
<meta name="theme-color" content="#A8481A" />
|
<meta name="theme-color" content="#A8481A" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest?v=__ASSET_VERSION__" />
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/icon.svg?v=__ASSET_VERSION__" type="image/svg+xml" />
|
||||||
<link rel="apple-touch-icon" href="/icon.svg" />
|
<link rel="apple-touch-icon" href="/icon.svg?v=__ASSET_VERSION__" />
|
||||||
<link rel="stylesheet" href="/worksheet.css" media="print" />
|
<link rel="stylesheet" href="/worksheet.css" media="print" />
|
||||||
<link rel="stylesheet" href="/app.css?v=20260730-release2" />
|
<link rel="stylesheet" href="/app.css?v=__ASSET_VERSION__" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app-shell">
|
<div class="app-shell">
|
||||||
@@ -2492,6 +2492,6 @@
|
|||||||
|
|
||||||
<datalist id="cultivar-list"></datalist>
|
<datalist id="cultivar-list"></datalist>
|
||||||
|
|
||||||
<script type="module" src="/js/main.js?v=20260730-release2"></script>
|
<script type="module" src="/js/main.js?v=__ASSET_VERSION__"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Inventory — Roast Planner</title>
|
<title>Inventory — Roast Planner</title>
|
||||||
<link rel="stylesheet" href="/app.css?v=20260730-release2" />
|
<link rel="stylesheet" href="/app.css?v=__ASSET_VERSION__" />
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/icon.svg?v=__ASSET_VERSION__" type="image/svg+xml" />
|
||||||
<meta name="theme-color" content="#A8481A" />
|
<meta name="theme-color" content="#A8481A" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -217,6 +217,6 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/js/inventory.js?v=20260730-release2"></script>
|
<script type="module" src="/js/inventory.js?v=__ASSET_VERSION__"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { api, protectedFetch } from "./api.js?v=20260730-release2";
|
import { api, protectedFetch } from "./api.js?v=__ASSET_VERSION__";
|
||||||
import { initSideNav, loadNavUser } from "./nav.js?v=20260730-release2";
|
import { initSideNav, loadNavUser } from "./nav.js?v=__ASSET_VERSION__";
|
||||||
import { showToast } from "./toast.js?v=20260730-release2";
|
import { showToast } from "./toast.js?v=__ASSET_VERSION__";
|
||||||
|
|
||||||
function fmtDate(value) {
|
function fmtDate(value) {
|
||||||
return value ? new Date(value).toLocaleString() : "—";
|
return value ? new Date(value).toLocaleString() : "—";
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
import { api, protectedFetch } from "./api.js?v=20260730-release2";
|
import { api, protectedFetch } from "./api.js?v=__ASSET_VERSION__";
|
||||||
import { initSideNav, loadNavUser } from "./nav.js?v=20260730-release2";
|
import { initSideNav, loadNavUser } from "./nav.js?v=__ASSET_VERSION__";
|
||||||
import { showToast } from "./toast.js?v=20260730-release2";
|
import { showToast } from "./toast.js?v=__ASSET_VERSION__";
|
||||||
|
|
||||||
let currentUsers = [];
|
let currentUsers = [];
|
||||||
let planFilterUserId = null;
|
let planFilterUserId = null;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { api, protectedFetch } from "./api.js?v=20260730-release2";
|
import { api, protectedFetch } from "./api.js?v=__ASSET_VERSION__";
|
||||||
import { initSideNav, loadNavUser } from "./nav.js?v=20260730-release2";
|
import { initSideNav, loadNavUser } from "./nav.js?v=__ASSET_VERSION__";
|
||||||
import { showToast } from "./toast.js?v=20260730-release2";
|
import { showToast } from "./toast.js?v=__ASSET_VERSION__";
|
||||||
import { wireWhyPanels } from "./why-panels.js?v=20260730-release2";
|
import { wireWhyPanels } from "./why-panels.js?v=__ASSET_VERSION__";
|
||||||
import {
|
import {
|
||||||
SCORE_ATTRS,
|
SCORE_ATTRS,
|
||||||
SCORE_LABELS,
|
SCORE_LABELS,
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
MAX_FLAVOR_TAGS,
|
MAX_FLAVOR_TAGS,
|
||||||
MAX_CUP_COUNT,
|
MAX_CUP_COUNT,
|
||||||
computeTotalScore,
|
computeTotalScore,
|
||||||
} from "/shared/cupping.js?v=20260730-release2";
|
} from "/shared/cupping.js?v=__ASSET_VERSION__";
|
||||||
|
|
||||||
const sessionId = new URLSearchParams(location.search).get("session");
|
const sessionId = new URLSearchParams(location.search).get("session");
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { api, protectedFetch } from "./api.js?v=20260730-release2";
|
import { api, protectedFetch } from "./api.js?v=__ASSET_VERSION__";
|
||||||
import { initSideNav, loadNavUser } from "./nav.js?v=20260730-release2";
|
import { initSideNav, loadNavUser } from "./nav.js?v=__ASSET_VERSION__";
|
||||||
import { showToast } from "./toast.js?v=20260730-release2";
|
import { showToast } from "./toast.js?v=__ASSET_VERSION__";
|
||||||
|
|
||||||
let lots = [];
|
let lots = [];
|
||||||
let showArchived = false;
|
let showArchived = false;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { api, protectedFetch } from "./api.js?v=20260730-release2";
|
import { api, protectedFetch } from "./api.js?v=__ASSET_VERSION__";
|
||||||
import { showToast } from "./toast.js?v=20260730-release2";
|
import { showToast } from "./toast.js?v=__ASSET_VERSION__";
|
||||||
|
|
||||||
let lots = [];
|
let lots = [];
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -1,19 +1,19 @@
|
|||||||
import { FIELD_IDS, blankPlan } from "/shared/fields.js?v=20260730-release2";
|
import { FIELD_IDS, blankPlan } from "/shared/fields.js?v=__ASSET_VERSION__";
|
||||||
import { computeLedger } from "/shared/ledger.js?v=20260730-release2";
|
import { computeLedger } from "/shared/ledger.js?v=__ASSET_VERSION__";
|
||||||
import {
|
import {
|
||||||
formatDuration,
|
formatDuration,
|
||||||
formatSigned,
|
formatSigned,
|
||||||
parseRangeMidpoint,
|
parseRangeMidpoint,
|
||||||
} from "/shared/time.js?v=20260730-release2";
|
} from "/shared/time.js?v=__ASSET_VERSION__";
|
||||||
import { CULTIVARS, findCultivar } from "/shared/reference-data.js?v=20260730-release2";
|
import { CULTIVARS, findCultivar } from "/shared/reference-data.js?v=__ASSET_VERSION__";
|
||||||
import { buildPlanCurve, pointsToPathD, tToX, tempToY } from "/shared/curve.js?v=20260730-release2";
|
import { buildPlanCurve, pointsToPathD, tToX, tempToY } from "/shared/curve.js?v=__ASSET_VERSION__";
|
||||||
import { initPrefillPanel } from "./prefill-ui.js?v=20260730-release2";
|
import { initPrefillPanel } from "./prefill-ui.js?v=__ASSET_VERSION__";
|
||||||
import { initAlogPanel } from "./alog-ui.js?v=20260730-release2";
|
import { initAlogPanel } from "./alog-ui.js?v=__ASSET_VERSION__";
|
||||||
import { initPrint } from "./print.js?v=20260730-release2";
|
import { initPrint } from "./print.js?v=__ASSET_VERSION__";
|
||||||
import { initLotPicker } from "./lot-picker.js?v=20260730-release2";
|
import { initLotPicker } from "./lot-picker.js?v=__ASSET_VERSION__";
|
||||||
import { protectedFetch, csrfToken } from "./api.js?v=20260730-release2";
|
import { protectedFetch, csrfToken } from "./api.js?v=__ASSET_VERSION__";
|
||||||
import { initSideNav } from "./nav.js?v=20260730-release2";
|
import { initSideNav } from "./nav.js?v=__ASSET_VERSION__";
|
||||||
import { wireWhyPanels } from "./why-panels.js?v=20260730-release2";
|
import { wireWhyPanels } from "./why-panels.js?v=__ASSET_VERSION__";
|
||||||
|
|
||||||
const FIELD_ID_SET = new Set(FIELD_IDS);
|
const FIELD_ID_SET = new Set(FIELD_IDS);
|
||||||
const STORAGE_PREFIX = "roastPlannerPlan.v2";
|
const STORAGE_PREFIX = "roastPlannerPlan.v2";
|
||||||
@@ -903,7 +903,7 @@ function wirePwa() {
|
|||||||
const hadController = !!navigator.serviceWorker.controller;
|
const hadController = !!navigator.serviceWorker.controller;
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
.register("/sw.js?v=20260730-release2")
|
.register("/sw.js?v=__ASSET_VERSION__")
|
||||||
.then((registration) => {
|
.then((registration) => {
|
||||||
const showUpdate = () =>
|
const showUpdate = () =>
|
||||||
document.getElementById("btn-refresh").classList.remove("hidden");
|
document.getElementById("btn-refresh").classList.remove("hidden");
|
||||||
|
|||||||
+2
-2
@@ -8,8 +8,8 @@
|
|||||||
name="description"
|
name="description"
|
||||||
content="A structured worksheet that computes first crack, development, and drop from your bean — then keeps the plan next to what actually happened."
|
content="A structured worksheet that computes first crack, development, and drop from your bean — then keeps the plan next to what actually happened."
|
||||||
/>
|
/>
|
||||||
<link rel="stylesheet" href="/app.css?v=20260730-release2" />
|
<link rel="stylesheet" href="/app.css?v=__ASSET_VERSION__" />
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/icon.svg?v=__ASSET_VERSION__" type="image/svg+xml" />
|
||||||
<meta name="theme-color" content="#2A1D16" />
|
<meta name="theme-color" content="#2A1D16" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+3
-3
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Log in — Roast Planner</title>
|
<title>Log in — Roast Planner</title>
|
||||||
<link rel="stylesheet" href="/app.css?v=20260730-release2" />
|
<link rel="stylesheet" href="/app.css?v=__ASSET_VERSION__" />
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/icon.svg?v=__ASSET_VERSION__" type="image/svg+xml" />
|
||||||
<meta name="theme-color" content="#A8481A" />
|
<meta name="theme-color" content="#A8481A" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -44,6 +44,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<script type="module" src="/js/login.js?v=20260730-release2"></script>
|
<script type="module" src="/js/login.js?v=__ASSET_VERSION__"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+3
-3
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Set a new password — Roast Planner</title>
|
<title>Set a new password — Roast Planner</title>
|
||||||
<link rel="stylesheet" href="/app.css?v=20260730-release2" />
|
<link rel="stylesheet" href="/app.css?v=__ASSET_VERSION__" />
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/icon.svg?v=__ASSET_VERSION__" type="image/svg+xml" />
|
||||||
<meta name="theme-color" content="#A8481A" />
|
<meta name="theme-color" content="#A8481A" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -46,6 +46,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<script type="module" src="/js/reset.js?v=20260730-release2"></script>
|
<script type="module" src="/js/reset.js?v=__ASSET_VERSION__"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+3
-3
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Administrator setup — Roast Planner</title>
|
<title>Administrator setup — Roast Planner</title>
|
||||||
<link rel="stylesheet" href="/app.css?v=20260730-release2" />
|
<link rel="stylesheet" href="/app.css?v=__ASSET_VERSION__" />
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/icon.svg?v=__ASSET_VERSION__" type="image/svg+xml" />
|
||||||
<meta name="theme-color" content="#A8481A" />
|
<meta name="theme-color" content="#A8481A" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -58,6 +58,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<script type="module" src="/js/setup.js?v=20260730-release2"></script>
|
<script type="module" src="/js/setup.js?v=__ASSET_VERSION__"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+3
-3
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Create an account — Roast Planner</title>
|
<title>Create an account — Roast Planner</title>
|
||||||
<link rel="stylesheet" href="/app.css?v=20260730-release2" />
|
<link rel="stylesheet" href="/app.css?v=__ASSET_VERSION__" />
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/icon.svg?v=__ASSET_VERSION__" type="image/svg+xml" />
|
||||||
<meta name="theme-color" content="#A8481A" />
|
<meta name="theme-color" content="#A8481A" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -55,6 +55,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<script type="module" src="/js/signup.js?v=20260730-release2"></script>
|
<script type="module" src="/js/signup.js?v=__ASSET_VERSION__"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+20
-20
@@ -1,4 +1,4 @@
|
|||||||
const CACHE_NAME = "roast-planner-static-v8";
|
const CACHE_NAME = "roast-planner-static-__ASSET_VERSION__";
|
||||||
// Only /app is precached as a navigable page: it is a data-free authenticated shell (draft
|
// Only /app is precached as a navigable page: it is a data-free authenticated shell (draft
|
||||||
// data lives only in account-scoped local storage, and logout clears that namespace), so it is
|
// data lives only in account-scoped local storage, and logout clears that namespace), so it is
|
||||||
// the one page that is both safe and useful to relaunch offline. The marketing page, auth
|
// the one page that is both safe and useful to relaunch offline. The marketing page, auth
|
||||||
@@ -7,25 +7,25 @@ const CACHE_NAME = "roast-planner-static-v8";
|
|||||||
// stale content.
|
// stale content.
|
||||||
const APP_SHELL = [
|
const APP_SHELL = [
|
||||||
"/app",
|
"/app",
|
||||||
"/app.css?v=20260730-release2",
|
"/app.css?v=__ASSET_VERSION__",
|
||||||
"/worksheet.css?v=20260730-release2",
|
"/worksheet.css?v=__ASSET_VERSION__",
|
||||||
"/manifest.webmanifest?v=20260730-release2",
|
"/manifest.webmanifest?v=__ASSET_VERSION__",
|
||||||
"/icon.svg?v=20260730-release2",
|
"/icon.svg?v=__ASSET_VERSION__",
|
||||||
"/js/main.js?v=20260730-release2",
|
"/js/main.js?v=__ASSET_VERSION__",
|
||||||
"/js/api.js?v=20260730-release2",
|
"/js/api.js?v=__ASSET_VERSION__",
|
||||||
"/js/nav.js?v=20260730-release2",
|
"/js/nav.js?v=__ASSET_VERSION__",
|
||||||
"/js/toast.js?v=20260730-release2",
|
"/js/toast.js?v=__ASSET_VERSION__",
|
||||||
"/js/why-panels.js?v=20260730-release2",
|
"/js/why-panels.js?v=__ASSET_VERSION__",
|
||||||
"/js/lot-picker.js?v=20260730-release2",
|
"/js/lot-picker.js?v=__ASSET_VERSION__",
|
||||||
"/js/prefill-ui.js?v=20260730-release2",
|
"/js/prefill-ui.js?v=__ASSET_VERSION__",
|
||||||
"/js/alog-ui.js?v=20260730-release2",
|
"/js/alog-ui.js?v=__ASSET_VERSION__",
|
||||||
"/js/print.js?v=20260730-release2",
|
"/js/print.js?v=__ASSET_VERSION__",
|
||||||
"/shared/fields.js?v=20260730-release2",
|
"/shared/fields.js?v=__ASSET_VERSION__",
|
||||||
"/shared/ledger.js?v=20260730-release2",
|
"/shared/ledger.js?v=__ASSET_VERSION__",
|
||||||
"/shared/time.js?v=20260730-release2",
|
"/shared/time.js?v=__ASSET_VERSION__",
|
||||||
"/shared/reference-data.js?v=20260730-release2",
|
"/shared/reference-data.js?v=__ASSET_VERSION__",
|
||||||
"/shared/curve.js?v=20260730-release2",
|
"/shared/curve.js?v=__ASSET_VERSION__",
|
||||||
"/shared/cupping.js?v=20260730-release2",
|
"/shared/cupping.js?v=__ASSET_VERSION__",
|
||||||
];
|
];
|
||||||
|
|
||||||
self.addEventListener("install", (event) => {
|
self.addEventListener("install", (event) => {
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { createHash } from "node:crypto";
|
||||||
|
import { readFile, writeFile, readdir } from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
const PLACEHOLDER = "__ASSET_VERSION__";
|
||||||
|
const root = path.resolve(process.argv[2] || process.cwd());
|
||||||
|
const assetRoots = ["public", "shared"];
|
||||||
|
|
||||||
|
async function walk(directory) {
|
||||||
|
const entries = await readdir(directory, { withFileTypes: true });
|
||||||
|
const files = await Promise.all(
|
||||||
|
entries.map((entry) => {
|
||||||
|
const target = path.join(directory, entry.name);
|
||||||
|
return entry.isDirectory() ? walk(target) : target;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return files.flat().sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = (
|
||||||
|
await Promise.all(assetRoots.map((directory) => walk(path.join(root, directory))))
|
||||||
|
).flat();
|
||||||
|
const sources = await Promise.all(files.map((file) => readFile(file, "utf8")));
|
||||||
|
const version = createHash("sha256")
|
||||||
|
.update(files.map((file, index) => `${path.relative(root, file)}\0${sources[index]}`).join("\0"))
|
||||||
|
.digest("hex")
|
||||||
|
.slice(0, 16);
|
||||||
|
let replacements = 0;
|
||||||
|
await Promise.all(
|
||||||
|
files.map(async (file, index) => {
|
||||||
|
const source = sources[index];
|
||||||
|
const count = source.split(PLACEHOLDER).length - 1;
|
||||||
|
if (!count) return;
|
||||||
|
replacements += count;
|
||||||
|
await writeFile(file, source.replaceAll(PLACEHOLDER, version));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (!replacements) throw new Error(`No ${PLACEHOLDER} markers found under ${assetRoots.join(", ")}.`);
|
||||||
|
console.log(`Stamped ${replacements} asset references with version ${version}.`);
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
// Browser-safe. Geometry for the roast-curve SVG grid (viewBox 0 0 714 334), matching
|
// Browser-safe. Geometry for the roast-curve SVG grid (viewBox 0 0 714 334), matching
|
||||||
// manual-roast-planner.html's worksheet box 10 grid exactly.
|
// manual-roast-planner.html's worksheet box 10 grid exactly.
|
||||||
|
|
||||||
import { MACHINE } from "./reference-data.js?v=20260730-release2";
|
import { MACHINE } from "./reference-data.js?v=__ASSET_VERSION__";
|
||||||
|
|
||||||
export const PLOT = { x0: 52, x1: 652, y0: 24, y1: 294, tMaxS: 900 };
|
export const PLOT = { x0: 52, x1: 652, y0: 24, y1: 294, tMaxS: 900 };
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -2,8 +2,8 @@
|
|||||||
// Mirrors manual-roast-planner.html's worksheet box 6 (lines 1-9, totals A/C/D)
|
// Mirrors manual-roast-planner.html's worksheet box 6 (lines 1-9, totals A/C/D)
|
||||||
// and box 7 (the four sanity checks) exactly. Imported by both server and browser.
|
// and box 7 (the four sanity checks) exactly. Imported by both server and browser.
|
||||||
|
|
||||||
import { parseDuration, parseRangeMidpoint } from "./time.js?v=20260730-release2";
|
import { parseDuration, parseRangeMidpoint } from "./time.js?v=__ASSET_VERSION__";
|
||||||
import { YELLOW_RATIO, SANITY_BANDS } from "./reference-data.js?v=20260730-release2";
|
import { YELLOW_RATIO, SANITY_BANDS } from "./reference-data.js?v=__ASSET_VERSION__";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("./fields.js").Plan} plan
|
* @param {import("./fields.js").Plan} plan
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// (Step 1 cultivar table, Step 3 processing table, Step 4 roast-level table, Step 8 machine bands,
|
// (Step 1 cultivar table, Step 3 processing table, Step 4 roast-level table, Step 8 machine bands,
|
||||||
// Step 7 sanity bands). Keep this the single source of truth — don't retype these numbers elsewhere.
|
// Step 7 sanity bands). Keep this the single source of truth — don't retype these numbers elsewhere.
|
||||||
|
|
||||||
import { parseRangeMidpoint } from "./time.js?v=20260730-release2";
|
import { parseRangeMidpoint } from "./time.js?v=__ASSET_VERSION__";
|
||||||
|
|
||||||
export const YELLOW_RATIO = 0.56;
|
export const YELLOW_RATIO = 0.56;
|
||||||
const DEFAULT_DEV_THIRD_S = 90; // "1:30", the modal third-profile-number most cultivar rows share
|
const DEFAULT_DEV_THIRD_S = 90; // "1:30", the modal third-profile-number most cultivar rows share
|
||||||
|
|||||||
Reference in New Issue
Block a user