mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
* feat(ci): publish the What's New card from the repository Move the post-update highlight document into the repo and publish it from GitHub Actions, so changing the card is a pull request instead of a task that needs personal Cloudflare credentials. - docs/whats-new.json is the source of truth, next to its spec doc. - pnpm guard validates it through the daemon's own parser on every PR, so a document that would silently show no card cannot merge. - whats-new-publish.yml uploads on merge to main (and on manual dispatch, with a read-only dry-run mode) and verifies the object by reading it back from the origin the daemon fetches, past the edge cache. * docs: name the CI job that actually runs the guard check * fix(ci): gate the What's New publish behind a main-only environment A workflow_dispatch runs the workflow file from the ref it is dispatched against, so the in-workflow main-only assertion was editable by whoever triggered it: anyone with write access could publish unreviewed content to every installed client from their own branch. Split the workflow so credentials live in one place and that place is gated by GitHub rather than by YAML. `validate` holds no secrets and runs on any ref; `publish` declares the `whats-new-publish` environment, whose deployment-branch policy allows main only and which owns the R2 secrets. Dropping the environment declaration to escape the branch policy also drops the secrets. Also points the card at the new Arena URLs supplied by product. * fix(ci): pin the What's New publish trust boundary in a check that runs The missing-credentials error still told operators the R2 keys are repository secrets. Following that string is the exact setup the previous revision closed: repository secrets are readable from any job on any branch, so a modified workflow dispatched from an unreviewed ref could publish to every installed client — or just take the keys. Point the error at the `whats-new-publish` environment and refuse the repository path in the same breath. The boundary itself had no coverage: nothing read the workflow, so moving `environment:` or a `secrets.*` expression back onto the arbitrary-ref job would stay green while still publishing correctly from main. That coverage cannot be lane-routed. Running the planner over `.github/workflows/whats-new-publish.yml` selects neither `web_tests_required` nor `ui_p0_validation_required`, so the `e2e_vitest` workload does not run for a workflow-only edit — an assertion living only there would be skipped for exactly the change class it guards. The check therefore lives in `pnpm guard`, which CI invokes from the unconditional preflight job, same reasoning as the document guard next to it. It asserts that the credential-bearing job declares the environment by its exact name, needs `validate` and is gated on its output with no `always()`/`cancelled()` escape, reads all four bucket bindings, and that no other job attaches an environment or reads any secret at all. The e2e suite drives it over the real workflow mutated into each of those regressions in turn, so every assertion is proven able to fail; gutting the checker turns all eleven red. * chore(whats-new): seed the document from the live card Bringing the workflow in with new copy attached ties the infrastructure to a content decision. The Arena CTA this document pointed at is not live — `/llm-arena-for-design/` and `/zh/llm-arena/llm-arena-for-design/` both 404 — and the card renders regardless, so merging would have put a dead button on every client's Home. Seed the repository copy from the object that is already serving instead: byte-identical to `https://whatsnew.open-design.ai/whats-new.json`, id `0.21.0`. That puts the current state under version control and makes the first automatic publish idempotent — it uploads exactly what is already there, so the merge exercises the whole chain (validate, upload, read-back against the origin the daemon fetches) without changing anything a user sees. The Arena copy is not lost; it moves to the PR body as a follow-up for the release owner, who can now change the card through this workflow without holding personal Cloudflare credentials. * fix(ci): detect secrets by context, not by access shape The guard added last round matched `${{ secrets.NAME }}` with a regex over the raw workflow text. A guard whose whole job is to stop a secret reaching the arbitrary-ref job had a blind spot for most of the ways to read one. Verified against the previous implementation, each returning zero violations while a secret reached `validate`: `secrets['NAME']`, `secrets["NAME"]`, `secrets [ 'NAME' ]`, `secrets . NAME`, `toJSON(secrets)`, a bare `secrets`, and `fromJSON(toJSON(secrets)).NAME`. Extending the regex would have repeated the mistake that caused it. One bypass proves why: a double-quoted YAML scalar may carry a backslash line continuation, so STOLEN: "${{ sec\ rets.NAME }}" parses to a live secret read while the source text contains no such substring. No text scanner can see that, however many shapes it lists. So the workflow is read through a YAML parser, and detection is a single property instead of an enumeration: GitHub's expression language has no string-to-context evaluation, so a secret cannot be reached without naming the `secrets` context as an identifier. Finding that identifier inside any `${{ … }}` expression covers every access form, including ones nobody has thought of. Parsing also retires the hand-rolled job splitter and its own failure modes, and lets the check distinguish "no violations" from "nothing was examined" — an unparseable document, a flow-style jobs mapping, or a renamed publish job now fail closed rather than passing quietly. Two holes the shape list never covered are closed as a side effect: a job-level `secrets:` key passes credentials to a called workflow with no expression at all, and workflow-level `env:` is inherited by every job. `yaml@2.9.0` becomes a root devDependency. It was already in the lockfile via `tools/release` and pinned repository-wide by the existing pnpm override, so the lockfile gains only the root importer entry. Test coverage moves with it: 26 of 46 cases now go red when the checker is gutted, up from 11. --------- Co-authored-by: lefarcen <ontf116@gmail.com>
74 lines
2.5 KiB
JSON
74 lines
2.5 KiB
JSON
{
|
|
"name": "open-design",
|
|
"version": "0.21.6",
|
|
"private": true,
|
|
"packageManager": "pnpm@10.33.2",
|
|
"type": "module",
|
|
"description": "Local-first design product: detects your installed code-agent CLI, runs design skills + design systems, streams artifacts into a sandboxed preview.",
|
|
"license": "Apache-2.0",
|
|
"bin": {
|
|
"od": "./apps/daemon/bin/od.mjs"
|
|
},
|
|
"scripts": {
|
|
"postinstall": "node ./scripts/postinstall.mjs",
|
|
"bootstrap": "node ./scripts/postinstall.mjs",
|
|
"tools-dev": "pnpm exec tools-dev",
|
|
"tools-pack": "pnpm exec tools-pack",
|
|
"tools-release": "pnpm exec tools-release",
|
|
"tools-serve": "pnpm exec tools-serve",
|
|
"guard": "tsx ./scripts/guard.ts",
|
|
"lint:craft": "tsx ./scripts/lint-craft-references.ts",
|
|
"i18n:check": "tsx ./scripts/i18n-check.ts",
|
|
"i18n:coverage": "tsx ./scripts/i18n-coverage-report.ts",
|
|
"sync:community-pets": "node --experimental-strip-types scripts/sync-community-pets.ts",
|
|
"bake:community-pets": "node --experimental-strip-types scripts/bake-community-pets.ts",
|
|
"seed:test-projects": "node --experimental-strip-types scripts/seed-test-projects.ts",
|
|
"seed:curated-design-skills": "node --experimental-strip-types scripts/seed-curated-design-skills.ts",
|
|
"backfill:failed-runs": "node --experimental-strip-types scripts/backfill-failed-runs-with-artifacts.ts",
|
|
"typecheck": "pnpm -r --workspace-concurrency=4 --if-present run typecheck && tsc -p scripts/tsconfig.json --noEmit"
|
|
},
|
|
"devDependencies": {
|
|
"@open-design/components": "workspace:*",
|
|
"@open-design/daemon": "workspace:*",
|
|
"@open-design/tools-dev": "workspace:*",
|
|
"@open-design/tools-pack": "workspace:*",
|
|
"@open-design/tools-release": "workspace:*",
|
|
"@open-design/tools-serve": "workspace:*",
|
|
"@types/node": "20.19.39",
|
|
"tsx": "4.22.3",
|
|
"typescript": "5.9.3",
|
|
"yaml": "2.9.0"
|
|
},
|
|
"engines": {
|
|
"node": "~24",
|
|
"pnpm": ">=10.33.2 <11"
|
|
},
|
|
"pnpm": {
|
|
"overrides": {
|
|
"brace-expansion": "5.0.9",
|
|
"devalue": "5.8.1",
|
|
"fast-uri": "3.1.5",
|
|
"hono": "4.12.34",
|
|
"ip-address": "10.3.1",
|
|
"postcss": "8.5.15",
|
|
"protobufjs": "8.4.0",
|
|
"qs": "6.15.2",
|
|
"tar": "7.5.22",
|
|
"tmp": "0.2.7",
|
|
"undici@^6": "6.28.0",
|
|
"undici@^7": "7.29.0",
|
|
"yaml": "2.9.0"
|
|
},
|
|
"onlyBuiltDependencies": [
|
|
"better-sqlite3",
|
|
"core-js",
|
|
"electron",
|
|
"electron-winstaller",
|
|
"esbuild",
|
|
"onnxruntime-node",
|
|
"protobufjs",
|
|
"sharp"
|
|
]
|
|
}
|
|
}
|