From 26775525e1fcac4afe7280136624c21d21e9bd95 Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 1 Jul 2026 01:52:03 +0800 Subject: [PATCH] Drop POST and DELETE /-/refs (publish is the only way refs enter) POST /-/refs (register-only) and DELETE /-/refs (unregister) had no production caller: the publish action uses /-/tarball + /-/publish, which registers the ref internally. Register-only only enabled the unsupported on-demand-build result (unversioned upstream bytes that pnpm-strict rejects). Removing both makes 'a ref exists only once it has been published with artifacts' an invariant and drops the now-dead unregisterRef. GET /-/refs (discovery) stays. The test fixture seeds its ref via /-/publish (the production path); the register/unregister/auth tests fold into publish-based equivalents; the optional-dependency URL rewriting moves to a buildPreviewTarball unit assertion (it had been covered only by the now-passthrough packument test). --- README.md | 27 ++---- src/app.ts | 46 +-------- src/preview/getConfiguredRefs.ts | 8 -- test/buildPreviewTarball.test.ts | 8 ++ test/worker.test.ts | 158 +++++++++++++++---------------- 5 files changed, 95 insertions(+), 152 deletions(-) diff --git a/README.md b/README.md index 23df4fe..30469d3 100644 --- a/README.md +++ b/README.md @@ -173,32 +173,23 @@ with `void secret put ADMIN_TOKEN`); without it configured the write endpoints return 503. `GET /-/refs` is a public read. ```bash -# List configured refs (static env + runtime R2 index) - no auth required. -# Each entry: { ref, version, publishedAt, prUrl, expiresAt }. publishedAt/prUrl -# are null until the ref is published (prUrl only when published from a PR); -# expiresAt is the index TTL (null for static env refs, which never expire). +# List registered refs - no auth required. +# Each entry: { ref, version, publishedAt, prUrl, expiresAt }. publishedAt is the +# server-stamped release date; prUrl is null unless published from a PR; expiresAt +# is the index TTL (90 days out). curl https://.../-/refs -# Register a ref at runtime (no redeploy). -curl -X POST -H "authorization: Bearer $ADMIN_TOKEN" -H 'content-type: application/json' \ - -d '{"ref":"commit.a832a55"}' https://.../-/refs - -# Unregister a ref -curl -X DELETE -H "authorization: Bearer $ADMIN_TOKEN" -H 'content-type: application/json' \ - -d '{"ref":"commit.a832a55"}' https://.../-/refs - # Purge a generated build (its tarball + meta) from R2 curl -X POST -H "authorization: Bearer $ADMIN_TOKEN" -H 'content-type: application/json' \ -d '{"package":"vite-plus","version":"0.0.0-commit.a832a55"}' https://.../-/purge ``` -Tarball upload (`PUT /-/tarball//.tgz`) and publish -(`POST /-/publish`, stores metadata + registers the ref) are also admin-guarded; -they are driven by the [publish action](#publishing-from-ci), not by hand. +Refs are created by publishing: tarball upload (`PUT /-/tarball//.tgz`) +then `POST /-/publish` (stores metadata + registers the ref), both admin-guarded +and driven by the [publish action](#publishing-from-ci), not by hand. -A registered ref is reflected immediately and built into the packument on the -next request (and into R2 on first fetch). This is the no-redeploy path for -exposing new pkg.pr.new builds. +A published ref is reflected immediately and built into the packument on the next +request. This is the no-redeploy path for exposing new pkg.pr.new builds. ### Publishing from CI diff --git a/src/app.ts b/src/app.ts index 4da5fef..4f0acc4 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,13 +8,11 @@ import { parsePreviewVersion, shaToVersion, } from './preview/parsePreviewVersion' -import { parseConfiguredPreviewRefs } from './preview/parseConfiguredPreviewRefs' import { getConfiguredRefs, getConfiguredRefsWithEtag, latestVersionByPr, registerRef, - unregisterRef, } from './preview/getConfiguredRefs' import { parseNpmTarballPath, @@ -265,7 +263,7 @@ app.post('/-/publish', async (c) => { return c.json({ ref, version: parsed.version, published }, 201) }) -/** List the configured preview refs (static env + runtime R2 index). Public read. */ +/** List the registered preview refs (the runtime R2 index). Public read. */ app.get('/-/refs', async (c) => { const refs = await getConfiguredRefs(c.env) return c.json({ @@ -274,52 +272,12 @@ app.get('/-/refs', async (c) => { version: r.version, publishedAt: r.publishedAt ?? null, prUrl: r.prUrl ?? null, - // ISO TTL; null for static env refs, which never expire. + // ISO TTL (90 days out), refreshed on each (re)publish. expiresAt: r.expiresAt ? new Date(r.expiresAt).toISOString() : null, })), }) }) -/** - * Register a preview ref at runtime (no redeploy). Body: - * `{ "ref": "commit." }`. - */ -app.post('/-/refs', async (c) => { - admin(c) - const body = (await c.req.json().catch(() => ({}))) as { ref?: string } - const ref = (body.ref ?? '').trim() - - let parsed - try { - ;[parsed] = parseConfiguredPreviewRefs(ref) - } catch { - throw new HttpError(400, `Invalid ref: ${ref || '(empty)'}`) - } - - try { - await registerRef(c.env, ref) - } catch (err) { - throw new HttpError(503, String(err)) - } - // The tarballs/integrity are built and uploaded by the publish action (CI); - // until then this ref's packument uses name-derived platform metas and the - // tarball endpoint redirects platform binaries to pkg.pr.new. - return c.json({ added: ref, version: parsed.version }, 201) -}) - -/** Unregister a runtime preview ref. Body: `{ "ref": "commit." }`. */ -app.delete('/-/refs', async (c) => { - admin(c) - const body = (await c.req.json().catch(() => ({}))) as { ref?: string } - const ref = (body.ref ?? '').trim() - try { - await unregisterRef(c.env, ref) - } catch (err) { - throw new HttpError(400, String(err)) - } - return c.json({ removed: ref }) -}) - /** * Purge a generated build from R2. Body: * `{ "package": "vite-plus", "version": "0.0.0-commit." }`. diff --git a/src/preview/getConfiguredRefs.ts b/src/preview/getConfiguredRefs.ts index 0ed5c14..1dd6b62 100644 --- a/src/preview/getConfiguredRefs.ts +++ b/src/preview/getConfiguredRefs.ts @@ -152,11 +152,3 @@ export async function registerRef( }) return parsed } - -/** Remove a runtime-registered ref. */ -export async function unregisterRef(env: Env, ref: string): Promise { - const [parsed] = parseConfiguredPreviewRefs(ref) - await mutateRefIndex(env, (index) => { - delete index[canonical(parsed)] - }) -} diff --git a/test/buildPreviewTarball.test.ts b/test/buildPreviewTarball.test.ts index 47b1c65..d2ad014 100644 --- a/test/buildPreviewTarball.test.ts +++ b/test/buildPreviewTarball.test.ts @@ -27,10 +27,14 @@ async function makeUpstream(pkg: Record) { describe('buildPreviewTarball', () => { it('rewrites package.json name/version/deps and preserves other files', async () => { + const binSha = 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' const upstream = await makeUpstream({ name: '@voidzero-dev/vite-plus-core', version: '1891', dependencies: { 'vite-plus': '1891', picomatch: '^2.3.1' }, + optionalDependencies: { + '@voidzero-dev/vite-plus-darwin-arm64': `https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-darwin-arm64@${binSha}`, + }, bin: { vp: './bin/vp' }, }) @@ -45,6 +49,10 @@ describe('buildPreviewTarball', () => { expect(build.packageJson.version).toBe('0.0.0-commit.a832a55') expect(build.packageJson.dependencies['vite-plus']).toBe('0.0.0-commit.a832a55') expect(build.packageJson.dependencies.picomatch).toBe('^2.3.1') + // pkg.pr.new optionalDependency URLs are rewritten to version strings. + expect( + build.packageJson.optionalDependencies['@voidzero-dev/vite-plus-darwin-arm64'], + ).toBe(`0.0.0-commit.${binSha}`) const files = await parseTarGzip(build.tarball) diff --git a/test/worker.test.ts b/test/worker.test.ts index 83afd3f..730d8d9 100644 --- a/test/worker.test.ts +++ b/test/worker.test.ts @@ -137,17 +137,50 @@ afterAll(() => { vi.unstubAllGlobals() }) -// The suite's fixture preview ref (`commit.a832a55`). It used to be injected via -// the VITE_PLUS_PREVIEW_REFS env var; refs are now runtime-only, so register it -// dynamically before each test (idempotent, so it survives storage isolation). +// The suite's fixture preview ref (`commit.a832a55`), published before each test +// (idempotent, so it survives storage isolation). Publishing vite-plus + core +// with their rewritten package.json is exactly what a CI publish stores, so +// packuments inject the ref the way they do in production. (`POST /-/refs` +// register-only was removed; a ref exists only once it has been published.) +const FIXTURE_VERSION = '0.0.0-commit.a832a55' beforeEach(async () => { - await SELF.fetch(`${BASE}/-/refs`, { + await SELF.fetch(`${BASE}/-/publish`, { method: 'POST', headers: { authorization: 'Bearer test-admin-token', 'content-type': 'application/json', }, - body: JSON.stringify({ ref: 'commit.a832a55' }), + body: JSON.stringify({ + ref: 'commit.a832a55', + packages: [ + { + name: 'vite-plus', + version: FIXTURE_VERSION, + packageJson: { + name: 'vite-plus', + version: FIXTURE_VERSION, + dependencies: { '@voidzero-dev/vite-plus-core': FIXTURE_VERSION }, + optionalDependencies: { + '@voidzero-dev/vite-plus-darwin-arm64': `0.0.0-commit.${PLATFORM_SHA}`, + }, + bin: { vp: './bin/vp' }, + }, + integrity: 'sha512-Zm9vYmFy', + shasum: 'a'.repeat(40), + }, + { + name: '@voidzero-dev/vite-plus-core', + version: FIXTURE_VERSION, + packageJson: { + name: '@voidzero-dev/vite-plus-core', + version: FIXTURE_VERSION, + dependencies: { 'vite-plus': FIXTURE_VERSION }, + }, + integrity: 'sha512-Y29yZQ', + shasum: 'b'.repeat(40), + }, + ], + }), }) }) @@ -364,26 +397,19 @@ describe('packument endpoint', () => { }) it('injects the version into a platform package packument with os/cpu', async () => { - // Register the platform commit ref so the platform packument exposes it. - await SELF.fetch(`${BASE}/-/refs`, { - method: 'POST', - headers: { - authorization: 'Bearer test-admin-token', - 'content-type': 'application/json', - }, - body: JSON.stringify({ ref: `commit.${PLATFORM_SHA}` }), - }) + // The fixture ref has no darwin meta published, so the platform packument + // derives os/cpu from the package name (platformMetaFromName). const res = await SELF.fetch( `${BASE}/@voidzero-dev%2Fvite-plus-darwin-arm64`, { headers: { accept: 'application/json' } }, ) const body = (await res.json()) as Record - const v = body.versions[`0.0.0-commit.${PLATFORM_SHA}`] + const v = body.versions[FIXTURE_VERSION] expect(v).toBeTruthy() expect(v.os).toEqual(['darwin']) expect(v.cpu).toEqual(['arm64']) expect(v.dist.tarball).toBe( - `${BASE}/tarballs/@voidzero-dev/vite-plus-darwin-arm64/0.0.0-commit.${PLATFORM_SHA}.tgz`, + `${BASE}/tarballs/@voidzero-dev/vite-plus-darwin-arm64/${FIXTURE_VERSION}.tgz`, ) }) @@ -724,19 +750,6 @@ describe('CORS', () => { }) describe('admin: refs', () => { - it('writes require auth (reads do not)', async () => { - // POST/DELETE require the bearer token. - expect( - (await SELF.fetch(`${BASE}/-/refs`, { method: 'POST' })).status, - ).toBe(401) - expect( - (await SELF.fetch(`${BASE}/-/refs`, { - method: 'POST', - headers: { authorization: 'Bearer wrong' }, - })).status, - ).toBe(401) - }) - it('lists the registered refs without auth (public read)', async () => { const res = await SELF.fetch(`${BASE}/-/refs`) expect(res.status).toBe(200) @@ -868,71 +881,52 @@ describe('admin: refs', () => { expect(body['dist-tags']['pr-777']).toBe(verB) }) - it('registers a ref and injects it into the packument', async () => { - const add = await SELF.fetch(`${BASE}/-/refs`, { - method: 'POST', - headers: { ...AUTH, 'content-type': 'application/json' }, - body: JSON.stringify({ ref: 'commit.a832a55' }), - }) - expect(add.status).toBe(201) - expect((await add.json()) as any).toMatchObject({ - version: '0.0.0-commit.a832a55', - }) - - // The dynamically registered ref now appears in the packument. - const pack = await SELF.fetch(`${BASE}/vite-plus`, { - headers: { accept: 'application/json' }, - }) - const body = (await pack.json()) as Record - expect(body.versions['0.0.0-commit.a832a55']).toBeTruthy() - }) - - it('rejects an invalid ref', async () => { - const res = await SELF.fetch(`${BASE}/-/refs`, { + it('rejects an invalid ref (publish)', async () => { + const res = await SELF.fetch(`${BASE}/-/publish`, { method: 'POST', headers: { ...AUTH, 'content-type': 'application/json' }, - body: JSON.stringify({ ref: 'nonsense' }), + body: JSON.stringify({ + ref: 'nonsense', + packages: [ + { + name: 'vite-plus', + version: '0.0.0-commit.abc1234', + packageJson: { name: 'vite-plus', version: '0.0.0-commit.abc1234' }, + integrity: 'sha512-x', + shasum: 'x', + }, + ], + }), }) expect(res.status).toBe(400) }) - it('registers concurrently without overwriting (incremental)', async () => { - const a = 'commit.aaaaaaa' - const b = 'commit.bbbbbbb' - const post = (ref: string) => - SELF.fetch(`${BASE}/-/refs`, { + it('publishes concurrently without overwriting (incremental)', async () => { + const publish = (sha: string) => + SELF.fetch(`${BASE}/-/publish`, { method: 'POST', headers: { ...AUTH, 'content-type': 'application/json' }, - body: JSON.stringify({ ref }), + body: JSON.stringify({ + ref: `commit.${sha}`, + packages: [ + { + name: 'vite-plus', + version: `0.0.0-commit.${sha}`, + packageJson: { name: 'vite-plus', version: `0.0.0-commit.${sha}` }, + integrity: 'sha512-x', + shasum: 'x', + }, + ], + }), }) - // Two PRs registering at the same time: both must survive. - await Promise.all([post(a), post(b)]) + // Two PRs publishing at the same time: both refs must survive the CAS. + await Promise.all([publish('aaaaaaa'), publish('bbbbbbb')]) const list = (await ( - await SELF.fetch(`${BASE}/-/refs`, { headers: AUTH }) + await SELF.fetch(`${BASE}/-/refs`) ).json()) as { refs: Array<{ ref: string }> } const refs = list.refs.map((r) => r.ref) - expect(refs).toContain(a) - expect(refs).toContain(b) - }) - - it('unregisters a ref', async () => { - // Use a sha that is not the fixture ref, so removal is observable. - const ref = 'commit.beefcafe' - await SELF.fetch(`${BASE}/-/refs`, { - method: 'POST', - headers: { ...AUTH, 'content-type': 'application/json' }, - body: JSON.stringify({ ref }), - }) - const del = await SELF.fetch(`${BASE}/-/refs`, { - method: 'DELETE', - headers: { ...AUTH, 'content-type': 'application/json' }, - body: JSON.stringify({ ref }), - }) - expect(del.status).toBe(200) - const list = (await ( - await SELF.fetch(`${BASE}/-/refs`, { headers: AUTH }) - ).json()) as { refs: Array<{ ref: string }> } - expect(list.refs.some((r) => r.ref === ref)).toBe(false) + expect(refs).toContain('commit.aaaaaaa') + expect(refs).toContain('commit.bbbbbbb') }) })