From 2d6681014d7baad029bc108201f654b0ce1409b3 Mon Sep 17 00:00:00 2001 From: Hardik Date: Sat, 20 Jun 2026 23:57:01 +0530 Subject: [PATCH 1/3] fix(deploy): don't inject the CI runner token into ppms (drop --update-env) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deploy job runs inside the Forgejo Actions runner, whose env includes an ephemeral FORGEJO_TOKEN (per-job token, revoked when the job ends). 'pm2 restart --update-env' injected it into ppms, where it shadowed the real PAT in .env (Next.js won't override an already-set process.env var) — so the Report Issue button 401'd once the job token expired. Plain restart keeps the daemon's clean env. Co-Authored-By: Claude Opus 4.8 --- .forgejo/workflows/deploy.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml index 1241ab7..40b7e2a 100644 --- a/.forgejo/workflows/deploy.yml +++ b/.forgejo/workflows/deploy.yml @@ -31,7 +31,13 @@ jobs: pnpm build # includes prisma generate pnpm db:migrate:deploy - pm2 restart ppms --update-env + # NOT --update-env: this job runs inside the Forgejo Actions runner, whose + # environment includes an ephemeral FORGEJO_TOKEN (the per-job token, revoked + # when the job ends). --update-env would inject it into ppms, where it shadows + # the real PAT from .env (Next.js does not override an already-set process.env + # var) and breaks the Report Issue button once the job token expires. A plain + # restart re-execs ppms from the pm2 daemon's clean env, so .env wins. + pm2 restart ppms echo "=== Deployed $TAG ===" - name: Verify portal responds From e9e618fda8bfa994d38324eceb580abb1d12bcd1 Mon Sep 17 00:00:00 2001 From: "Claude (auto-fix)" Date: Sun, 21 Jun 2026 00:32:39 +0530 Subject: [PATCH 2/3] feat(po): add week, month, year to line-item unit options The PO line-items Unit of Measure dropdown only offered hr/day among time-based units. Add week, month and year so durations beyond days can be selected, as requested. UOM_OPTIONS is the single source of truth and `unit` is validated as a free-form string, so no schema/validation change is needed. Fixes #44 Co-Authored-By: Claude Opus 4.8 (1M context) --- App/components/po/po-line-items-editor.tsx | 7 +++++-- App/tests/unit/po-line-items-editor.test.tsx | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/App/components/po/po-line-items-editor.tsx b/App/components/po/po-line-items-editor.tsx index 65f3c93..403ad7a 100644 --- a/App/components/po/po-line-items-editor.tsx +++ b/App/components/po/po-line-items-editor.tsx @@ -20,8 +20,11 @@ const UOM_OPTIONS = [ { value: "mL", label: "mL — Millilitre" }, { value: "m", label: "m — Metre" }, { value: "m2", label: "m² — Sq. Metre" }, - { value: "hr", label: "hr — Hour" }, - { value: "day", label: "day — Day" }, + { value: "hr", label: "hr — Hour" }, + { value: "day", label: "day — Day" }, + { value: "week", label: "week — Week" }, + { value: "month", label: "month — Month" }, + { value: "year", label: "year — Year" }, { value: "lump", label: "lump — Lump Sum" }, { value: "Ltr", label: "Ltr — Litre (alt)" }, ]; diff --git a/App/tests/unit/po-line-items-editor.test.tsx b/App/tests/unit/po-line-items-editor.test.tsx index 1d24a93..53eeb19 100644 --- a/App/tests/unit/po-line-items-editor.test.tsx +++ b/App/tests/unit/po-line-items-editor.test.tsx @@ -93,6 +93,25 @@ describe("LineItemsEditor — edit mode", () => { const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0] as LineItemInput[]; expect(lastCall[0].gstRate).toBeCloseTo(0.05); }); + + it("offers month and year as unit-of-measure options", () => { + render(); + const selects = screen.getAllByRole("combobox") as HTMLSelectElement[]; + const unitSelect = selects.find((s) => s.value === "pc")!; + const values = Array.from(unitSelect.options).map((o) => o.value); + expect(values).toContain("month"); + expect(values).toContain("year"); + }); + + it("calls onChange with the selected duration unit", async () => { + const onChange = vi.fn(); + render(); + const selects = screen.getAllByRole("combobox") as HTMLSelectElement[]; + const unitSelect = selects.find((s) => s.value === "pc")!; + fireEvent.change(unitSelect, { target: { value: "year" } }); + const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0] as LineItemInput[]; + expect(lastCall[0].unit).toBe("year"); + }); }); // ── Totals calculation (edit mode) ──────────────────────────────────────────── From 9f8297aa7ed0caabd94eeda79760be6b92cc5752 Mon Sep 17 00:00:00 2001 From: Hardik Date: Sun, 21 Jun 2026 01:07:49 +0530 Subject: [PATCH 3/3] feat(staging): auto-refresh staging on every push to master New .forgejo/workflows/staging.yml rebuilds ppms-staging to latest master on every merge (push to master) on the host runner, so staging always mirrors the trunk; concurrency-coalesced + workflow_dispatch. Also drops --update-env from staging-up.sh (and unsets FORGEJO_*) so the runner's ephemeral token can't leak into ppms-staging. Co-Authored-By: Claude Opus 4.8 --- .forgejo/workflows/staging.yml | 27 +++++++++++++++++++++++++++ automation/README.md | 6 +++++- automation/staging-up.sh | 6 +++++- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 .forgejo/workflows/staging.yml diff --git a/.forgejo/workflows/staging.yml b/.forgejo/workflows/staging.yml new file mode 100644 index 0000000..04f892b --- /dev/null +++ b/.forgejo/workflows/staging.yml @@ -0,0 +1,27 @@ +name: Refresh staging + +# Rebuilds the pms1 staging instance (pm2 `ppms-staging`, port 3200) to the latest +# master on every merge to master, so staging always mirrors the trunk for +# smoke-testing before a release tag. Also runnable on demand (workflow_dispatch). +# See automation/README.md > "Staging". + +on: + push: + branches: [master] + workflow_dispatch: {} + +# Only one staging refresh at a time; a newer master push cancels an in-flight build +# (staging-up.sh always checks out the latest origin/master, so the newest wins). +concurrency: + group: refresh-staging + cancel-in-progress: true + +jobs: + refresh: + runs-on: host + steps: + - name: Rebuild staging on latest master + run: | + set -e + export NVM_DIR="$HOME/.nvm"; . "$NVM_DIR/nvm.sh" + "$HOME/issue-watcher/staging-up.sh" diff --git a/automation/README.md b/automation/README.md index e426785..af8b00d 100644 --- a/automation/README.md +++ b/automation/README.md @@ -121,7 +121,11 @@ before a release tag deploys them to prod. - Checkout: `~/pelagia-staging` (separate from `~/pms` and `~/pelagia-autofix`) - Process: pm2 `ppms-staging` on **port 3200**, against the prod-mirror test DB (`pelagia_test`), safe dev mode (console email, local storage, SSO disabled). -- Refresh to newer master + restart: re-run `~/issue-watcher/staging-up.sh`. +- **Auto-refresh:** [`.forgejo/workflows/staging.yml`](../.forgejo/workflows/staging.yml) + rebuilds staging on **every push to `master`** (i.e. every merged PR) on the host runner, + so staging always tracks the trunk. It runs `~/issue-watcher/staging-up.sh`; concurrent + runs are coalesced (newest master wins). Also triggerable on demand (`workflow_dispatch`). +- Manual refresh / restart: re-run `~/issue-watcher/staging-up.sh`. - Stop: `pm2 delete ppms-staging`. - **Access is SSH-tunnel only** — the dev server binds to `127.0.0.1:3200`, so it is not reachable from the public internet. Open a tunnel and browse `http://localhost:3200`: diff --git a/automation/staging-up.sh b/automation/staging-up.sh index 8625bb9..efb3d83 100644 --- a/automation/staging-up.sh +++ b/automation/staging-up.sh @@ -67,8 +67,12 @@ echo "Generating Prisma client..."; pnpm db:generate # must be applied or the new code 500s on the missing columns. echo "Applying pending migrations to the test DB..."; pnpm db:migrate:deploy +# Drop any FORGEJO_* the caller may carry (e.g. when invoked from the Forgejo +# Actions runner, whose ephemeral FORGEJO_TOKEN would otherwise be injected into +# the staging process). NOT --update-env on restart, for the same reason. +for v in $(env | grep -oE '^FORGEJO_[A-Z_]+' || true); do unset "$v"; done if pm2 describe "$NAME" >/dev/null 2>&1; then - pm2 restart "$NAME" --update-env + pm2 restart "$NAME" else pm2 start "$DIR/App/run-staging.sh" --name "$NAME" --interpreter bash fi