upup v2 (3.0.0): headless core, six framework packages, hardened server — CI validation draft#320
Draft
AminDhouib wants to merge 1123 commits into
Draft
upup v2 (3.0.0): headless core, six framework packages, hardened server — CI validation draft#320AminDhouib wants to merge 1123 commits into
AminDhouib wants to merge 1123 commits into
Conversation
…ete (F-109) onUploadComplete had zero call-sites and onFileUploaded only fired on the drive-transfer path. The /multipart/complete route IS server-side completion, so fire both hooks there, building the UploadedFile from data already trusted at complete-time (result.key, uploadedSize, result.downloadUrl) -- never from client-declared name/type, and without extending the signed token or touching the size-envelope check. README + a config.ts doc-comment spell out which hook fires on which path, including the honest gap: client-direct presigned-PUT uploads bypass the server entirely, so no server-side hook can fire for them.
…/F-155/F-406/F-408/F-409/F-412/F-413/F-414/F-601/F-603)
…, v2-clean branch (F-441, F-444, F-445, F-448)
…DoS in @upup/server (F-181)
…e surface (F-182)
…d nested .husky dirs (F-166)
…ailwind-config (F-167)
…ple turbo lint (F-167)
…ig factory (F-166, F-167)
…ror event (F-146) P6 C1. The core event surface now has exactly ONE upload-failure event: upload-error. resume()'s catch emits upload-error (matching upload()/retry()); CoreEvents['error'] is deleted; the lone HEIC step diagnostic is renamed 'error' -> 'pipeline-error'. Per the S3-D10 ruling (Option A), the two live cross-framework bridges of the bare 'error' event and the one remaining producer are migrated in the same commit so the contract is true end-to-end (a literal grep missed them — they used data-driven forwarding tables): - angular FORWARDED ['error','error'] -> ['upload-error','error']; @output() error payload widens to { error: Error; file?: UploadFile } (additive file? from the upload-error channel). - vanilla element.ts FORWARDED ['error','upup:error'] -> ['upload-error','upup:error'] (public upup:error DOM event name unchanged). - vanilla url-uploader producer emit('error',{message}) -> emit('upload-error',{error}) (also fixes a payload-shape defect against the typed {error: Error} channel). Behavior change: angular's error output and vanilla's upup:error DOM event now fire for EVERY upload failure (per-file), not just the practically-dead resume path — the correct semantic for their names. Public names @output() error / upup:error stay byte-identical: no DOM change, no parity regen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P6 C2. Each handler in emit() is wrapped in try/catch: a consumer's listener throwing no longer aborts later handlers or escapes emit(). A caught exception is reported dev-only via console.error and NEVER re-emitted — a second error event would be a second failure channel AND would misattribute a render bug as a pipeline/upload error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (F-144) P6 C3. Add FileManager.updateFile(id, patch): the sole writer of a file's status/key/metadata. Each transition produces a NEW UploadFile reference (F-144), so the orchestrator's ref-diff projection tracks status changes it previously missed (F-145), and FileManager becomes the real owner of file state (F-143 write-side). The 6 in-place Object.assign+set status sites (onFileStart/onFileComplete/onFileError/ markFilesReady/updatePendingFileStatuses/markUnsuccessfulFilesFailed) now route through updateFile. Sites 1-3 use 'if (!updated) return' (subsumes the old !files.has guard); sites 5-6 snapshot the map before iterating (updateFile replaces entries mid-loop). updateFile re-wraps the same backing bytes into a fresh File (a view, not a copy) and copies the UploadFile fields incl. non-enumerable relativePath — mirroring cloneUploadFile. Object-spread would strip File's blob slots and break xhr.send; a constraint comment guards against that regression, and a contract pin asserts the transitioned object stays instanceof File with the same size. core-upload-targets 'preserves the native File ... through transitions' updated: it pinned reference identity (toBe(file)); immutability yields a new reference, so it now asserts data-identity (same File, same name, same bytes) — the property that matters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P6 C4. uploadStatus is now a projection of core's state-change{status}, not written by
three divergent listeners. rebuildFilesProjection becomes projectFromCore(payload): it
derives uploadStatus from payload.status (core sets _status before every status-carrying
emit) and rebuilds files from core.files (no-op when unchanged, preserving ref stability).
The upload-start / upload-all-complete / upload-error listeners drop their
setState({uploadStatus}) writes and keep only their callbacks; upload-error keeps
uploadError + P4's uploadErrorCode (only the FAILED write is removed — status now arrives
via the state-change{FAILED} core emits right after). removeFile reads the projected
state.files (the last direct core.files read in the class).
Result: one writer of uploadStatus, and pause/resume/cancel — which emit state-change{status}
but were not among the three listened events — now propagate for free (fixes the stale
'SUCCESSFUL over an empty list' drift after cancel).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P6 C5. A private activeRun: Promise<UploadFile[]> | null gates the run entry points so a single UploadManager owns any run. upload() and retry() become thin gates that return the in-flight promise if a run is active, otherwise set activeRun around the extracted runUpload()/runRetry() body and clear it in finally. resume() no-ops if activeRun is set, else assigns its uploadFiles(incomplete) chain to activeRun and clears it in finally (the F-146 upload-error catch stays inside the chain). upload() also throws after destroy() (F-148 down-payment; the rest of terminal-destroy lands in C6). Behavior change (plan §4): concurrent upload/retry now share one run; a mid-run resume is a no-op — was: orphaned managers from double-clicks / auto-upload races. plugin-integration 'plugin observes retry events' now awaits between calls (two synchronous retries collapse onto one run under F-149); its intent (a plugin can observe retry) is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P6 C6. destroy() is now terminal. runUpload() no longer resets destroyed=false, and upload/resume/retry/addFiles/setFiles throw a plain Error after destroy() (upload was guarded in C5). destroy() releases the crashRecovery and pipelineEngine manager refs; it does NOT clear crash-recovery STORAGE (a normal unmount must stay recoverable) and does NOT null fileManager (the files/progress getters are asserted to work post-destroy). Companion: the orchestrator gains a disposed flag (set in destroy(), above P7's blob-revoke line) and guards the auto-upload setTimeout — an unmount that races the 0-ms timer no longer reaches the terminal core.upload(); a stray call is also .catch-swallowed. Verified across all six frameworks (fresh core per mount; no teardown path throws; no unhandled rejection in the react suite). CLAUDE.md records the three P6 contract rulings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P6 C7. updateOptions() now nulls the cached pipelineEngine when any auto-pipeline flag (heicConversion/stripExifData/imageCompression/thumbnailGenerator/checksumVerification) is in the partial, so the next upload() rebuilds the pipeline from the new flags. An explicit pipeline stays construction-only and is never invalidated (guarded by !this.options.pipeline). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…minor) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…shallow Test #59 ran the affected-tests resolver against the host repo's HEAD~1...HEAD, which does not exist under actions/checkout's fetch-depth-1 clone: 'fatal: ambiguous argument HEAD~1' -- red in CI since the test's own commit, green locally where clones are full. The test now builds its own two-commit throwaway repo and runs the CLI there, so the real git-diff subprocess path stays exercised end to end while the assertions get stronger (exact changedFiles + full suites verdict instead of key presence). RED proven in a shallow clone and a no-parent cwd repo; GREEN proven in both plus the full 80-test suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The drive-sandbox harness landed refresh-onedrive-token.mjs, and the retired-vocabulary census (first CI execution since the harness -- an earlier step failed first on every prior run) flagged its three references. Renamed to refresh-one-drive-token.mjs (kebab wire form per the F-654/F-725 two-form ruling) and swept the references: the nightly rotation step, the providers.mjs comment, the file's own header, and the CLAUDE.md pointer. Slug-to-env mapping is a hardcoded lookup, so the UPUP_TEST_ONEDRIVE_* secret names are untouched. vocab:check red (3 hits) -> green (1197 files clean), no new exceptions pinned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ser flake The keyboard-only spec failed twice in CI on the browse-files Enter press: the trace snapshot showed the button still [active] (focus was never stolen -- no product a11y defect; the browse path is a synchronous native-button handler), yet the raw page.keyboard.press Enter was dropped before native activation under runner load, so no filechooser event ever fired. All four tab-then-press sites now assert toBeFocused and press via the locator (re-commits focus + actionability right before the real key), keeping Tab-reachability and keyboard activation both proven. Reproduced locally 2/30 failed at 16 workers before; 40/40, 60/60 single-worker, and the full 63-test deep suite green after. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CodeQL js/incomplete-sanitization (high): the markdown escape replaced pipes but never backslashes, so a reason containing a literal backslash left the following pipe behind an even-length backslash run -- still a live table delimiter, able to inject a column into GITHUB_STEP_SUMMARY. Backslash is now escaped first, then pipe. Routing semantics untouched; a regression test drives the real CLI with a pipe-bearing filename and counts the structural pipes in the rendered row (81/81 script tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…llution CodeQL js/prototype-pollution-utility (medium): the dotted-path setter checked for __proto__/constructor/prototype in one upfront pass, which CodeQL cannot recognize as a barrier for the write sink -- and an inline check at each write is also the stronger shape. The guard now sits at both write sites (walk keys and the leaf assignment); behavior for legitimate keys is identical. setByPath is exported for testability and a new spec pins that __proto__.x and constructor.prototype.x attempts leave Object.prototype and the target untouched (18 files / 80 tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…leg + GitHub token sync) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rge-upload seeding
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…filenames now transfer
fetchDropboxFile built the Dropbox-API-Arg HTTP header with plain
JSON.stringify. HTTP headers are latin-1, and Dropbox file ids ARE path
strings, so a unicode filename either makes undici reject the header outright
(code points > 0xFF) or is sent mis-encoded as latin-1 bytes that Dropbox
cannot match to its UTF-8 path (a 409 path/not_found). Either way the
server-mode download of a unicode-named file breaks.
Fix: httpHeaderSafeJson() escapes every char >= 0x7F as a \uXXXX sequence
(Dropbox's prescribed "HTTP header safe JSON"), and fetchDropboxFile uses it
for the Dropbox-API-Arg header. The list/search calls are unaffected — their
JSON rides the request BODY, where UTF-8 is fine.
Proven test-first against the real sandbox accounts. A committed fixture
(upup-sandbox-ünï 'q' & (1).txt — ü/ï are non-latin1-safe in headers, the
apostrophe breaks GDrive/OData quoting, and &/space/parens exercise
URL-encoding) is seeded to all four providers and rides the existing
list/download/byte-integrity loops plus a new unicode-search live test. The
seeder gained matching escaping so it can upload the fixture in the first place
(headerSafeJson for its own Dropbox-API-Arg headers, escapeGDriveQueryValue for
the Drive find queries).
RED before the fix (drive:sandbox vitest live — ONLY dropbox's download fails;
box/google-drive/one-drive downloads stay green because their fetch does not
carry JSON in a header):
FAIL tests/integration/drive-clients-live.integration.test.ts >
drive-clients live — dropbox > a client can download each fixture and the
bytes are byte-exact (sha256 matches the committed fixture) from dropbox
UpupNetworkError: Drive API 409: {"error":{".tag":"path","path":{".tag":"not_found"}},"error_summary":"path/not_found/"}
❯ driveFetch src/drive-clients.ts:74:15
❯ Object.fetchDropboxFile [as fetchFile] src/drive-clients.ts:305:19
❯ tests/integration/drive-clients-live.integration.test.ts:261:34
Test Files 1 failed (1)
Tests 1 failed | 19 passed (20)
(exit code 1)
GREEN after the fix: vitest live 24 passed (4 providers × 6 tests), the
Playwright HTTP-surface layer 12 passed (drive→S3 transfer of every fixture,
all four providers), and a self-contained unit pin (httpHeaderSafeJson plus a
fetchDropboxFile header-capture mock) 2 passed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…aywright caps requests at 30s by default) The >5 MiB multipart test raised the TEST budget with test.setTimeout(240_000), but Playwright's APIRequestContext keeps its default 30s PER-REQUEST timeout. The transfer POST blocks on a server-side 6 MiB provider download before the MinIO upload, so a slow/cold provider in nightly CI could exceed the 30s per-request cap despite the 240s test budget — a latent flake. Pass an explicit 180s request timeout to the large-transfer POST and to the presigned-URL GET of the 6 MiB object. Verified (rotation-free): prettier + e2e-test typecheck clean, and one bare Playwright drive-sandbox run — 9 passed / 3 skipped (box/dropbox/google-drive live, one-drive skipped by design) — exercises the changed request options on three providers live. Task-3 quality-review Important item. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…h real routes + MinIO
The drive-sandbox Playwright suite proved live drive->S3 transfers but never
exercised the server's POLICY gates. This adds a SECOND in-process @upup/server
-- same node-bridge plumbing, same seeded token store -- configured with
maxFileSize 1 MiB + allowedTypes ['text/plain'], and proves all three rejection
paths live through the real routes against real MinIO:
- an over-declared size is rejected 413 before any drive call (the cheap
client-declared-size gate in drive-routes.ts),
- a disallowed mimeType is rejected 415 before any drive call, and
- the crown jewel: when the declared size is OMITTED so the cheap 413 gate
cannot fire, the AUTHORITATIVE streamed-byte cap inside
transferDriveFileToS3 aborts the real 6 MiB Box transfer mid-stream --
error status, no object persisted, no lingering incomplete multipart upload
(F-743, exercised with real provider bytes; Task 3's countObjectsWithSuffix
forward-infrastructure finally gets called).
The 6 MiB fixture routes to the multipart path (its real size exceeds the 5 MiB
single-put threshold); the first 5 MiB part uploads, then totalBytes (5242880)
> maxBytes (1048576) throws, the catch aborts the MPU, and the route surfaces
500 STORAGE_ERROR -- so the no-object + no-lingering-MPU asserts are the
meaningful ones. The abort was verified from the server error log, not just the
status colour:
UpupStorageError: Drive file exceeds the configured maxFileSize
(5242880 > 1048576 bytes)
at streamingMultipart (packages/server/src/transfer.ts:144:23)
Plumbing: the inline node-bridge boot in beforeAll is factored into a reusable
bootNodeServer(handler) helper (identical logic, parameterized over the handler)
so both servers share it; listFiles is generalized into
listFilesAt(request, base, ...) with listFiles kept as a thin wrapper -- every
existing test compiles unchanged; afterAll closes both servers. The policy
describe is Box-only (Client-Credentials mint, no rotating token) so it costs
zero rotation budget.
The streamed-cap POST carries an explicit timeout: 180_000 (matching the
existing multipart proof) -- Playwright's default 30s per-request cap is NOT
raised by test.setTimeout, and the server downloads ~5 MiB from Box before the
cap fires.
Verified (rotation-free, one bare drive-sandbox run):
12 passed / 3 skipped (26.8s) -- box/dropbox/google-drive live, one-drive
skipped by access-token-only design; the +3 are the new policy tests
(413 in 9ms, 415 in 4ms, streamed-cap abort in 1.9s).
Gates: full typecheck 32/32 tasks green (incl. @upup/e2e-test), test:quality
345 files + 4 workflows clean, prettier --write applied.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ransfer fails clean 500)
A native Google Doc has no downloadable binary content: Drive's alt=media
returns 403 fileNotDownloadable ("Only files with binary content can be
downloaded. Use Export with Docs Editors files."). The transfer route catches
the resulting UpupNetworkError(403) -- 403 != 401, so it is NOT a reauth -- and
surfaces it through res.fail as a clean 500 { error, code:STORAGE_ERROR } with
nothing persisted. This pins the CURRENT no-export limitation so export support
stays a deliberate future maintainer decision, not silent breakage or silent
addition.
- seed.mjs: seedGoogleDrive idempotently creates one native Doc
(application/vnd.google-apps.document) in the sandbox folder; drive.file
scope sees app-created files, so the seeder creating it is what makes it
visible.
- vitest live suite: pins that it lists cleanly (document mimeType, not a
folder).
- e2e HTTP spec: pins the transfer failing clean (500 JSON error body, object
count unchanged before/after).
Empirical adaptation vs the plan snippet: Drive v3 DOES report a size (~1 KiB
storage footprint) for a native Doc, so the listing assertion pins
`typeof size === 'number'`, not `size === undefined` -- the listing looks
ordinary; the no-binary limitation only surfaces at transfer time.
Verified (rotation-respecting):
- one full `drive:sandbox` wrapper run: OneDrive rotated + written back +
GitHub-synced; seed shows the native doc created; vitest surfaced the wrong
size guess (24 passed / 1 failed).
- fix re-validated gdrive-only (no OneDrive rotation): vitest -t "native Google
Doc" 1 passed / 24 skipped against the real Google API.
- bare Playwright drive-sandbox: 13 passed / 3 skipped (one-drive skipped by
design) -- the new gdoc transfer test passes; server log shows the 403->500.
Gates: typecheck 32/32, test:quality 345 files + 4 workflows clean,
prettier --check clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mment accuracy
- multipart test: note the >5 MiB path is guaranteed by declared-size routing
(size > SINGLE_PUT_MAX_BYTES = MIN_PART_SIZE, non-configurable), not by the
MPU-count assert; name that assert; note countOpenMultipartUploads does not
paginate (unlike countObjectsWithSuffix)
- assert labels: interpolate ${fixture.name} into the presigned-URL check and
${POLICY_PROVIDER} into the policy request paths + "seeded in" message (the
describe title stays a literal, greppable string — noted inline)
- comment accuracy: streamed-cap test accumulates the first full ~5 MiB part
(not ~1 MiB) before the cap trips; spec + playwright config headers now cover
all four providers; fixtures.mjs reworded — the final part is short because
6 MiB is not a whole multiple of the 5 MiB part size, and +45 exercises the
generator truncation branch
- seed.mjs: throw an actionable error if one-drive createUploadSession returns
no uploadUrl (mirrors the google-drive no-Location guard)
- drive-clients.ts: bidirectional twin back-refs from httpHeaderSafeJson /
escapeDriveQueryValue to their seed.mjs twins (the twins have no unit pin)
- drive-clients.test.ts: tighten the header-legal bound to <= 0x7E (0x7F DEL is
escaped too, so the test proves the stated claim)
- live integration test: note the unicode-search Array.isArray assert is
deliberately weak, and cross-ref the Playwright spec's OneDrive
access-token-only divergence
- rotate-and-test.mjs: correct the stale-token recovery message (re-mint via
drive:sandbox:mint — the old token is already consumed) and hedge the
"both passed" summary re skip-green ambiguity
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…echanics, local runbook - drive-sandbox-setup.md: the Playwright HTTP-surface layer now covers all four providers and the full server-mode journey (browse → pick → download → drive→S3 → sha256, >5 MiB multipart, unicode filenames, 413/415/streamed-cap policy boundaries, native-Google-Doc pin); fix the stale spec filename (box-dropbox-server-transfer.spec.ts → server-transfer.spec.ts) - setup.md fixture inventory rewritten: three disk fixtures (incl. the unicode name), the generated large fixture (name/size/sha256 pin, regenerated not stored), and the native Google Doc (no-export pin; Drive reports a ~1 KiB quota size); per-provider large-upload mechanics (Box direct ≤50 MB, Dropbox ≤150 MB, GDrive resumable, OneDrive upload session) - setup.md runbook: `pnpm run drive:sandbox` one-shot (rotate → seed → both live layers; MinIO :9100 prereq; `--no-sync-github` opt-out) and an OneDrive token-lineage note (nightly rotation kills the local token → re-mint) - testing.md: drive-sandbox layer row + nightly CI note updated to all four providers, multipart, unicode, policy boundary, native-GDoc pin; spec filename - CLAUDE.md: one surgical sentence extending the nightly Drive-Sandbox bullet — the HTTP-surface layer also proves the >5 MiB multipart path and the policy-boundary rejections Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… NAME)
Deferred optional rider (Task-6 quality review). The native-Google-Doc test
repeated the literal 'upup-sandbox-native-doc' four times (name filter, POST
fileName, and twice in countObjectsWithSuffix). Extract a test-local
`const NAME` and derive the counted suffix as `-${NAME}`, so the transferred
fileName and the counted object suffix are structurally guaranteed to match.
No behavior change (identical string values); a separate commit because rider
Commit A (9da15e7) was already committed — not amended per the review routing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, mint recovery hint provider
keywords (incl. drag-and-drop/dropzone family-wide), homepage, bugs, repository-object on all 9 publishable package.json; core gains description. READMEs restructured as agent landing pages: runnable client-mode example first, canonical docs/quickstart links, core error taxonomy fixed to UpupError/UpupErrorCode, mandatory uploadTokenSecret added to both @upup/next handler examples, server repo URL fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ar, blog removal @easyops-cn/docusaurus-search-local wired; docusaurus-plugin-llms emits llms.txt + llms-full.txt into the build; docs routeBasePath 'docs'->'/' so pages serve at /documentation/<slug> (redirect, footer, search base updated); GitHub/npm navbar links + footer; template blog deleted; config rebranded to the six-framework story; @docusaurus/theme-mermaid registered and pinned 3.7.0 (docusaurus requires exact family version); docs version 0.0.0->2.2.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…omy, retired vocab createUpupHandler examples gain the mandatory uploadTokenSecret (throws without it); error-handling.md rewritten to the real UpupError/UpupErrorCode surface (UploadError/UpupProvider phantoms removed); fictional signUpload replaced with a real AWS-SDK presign matching PresignedUrlResponse; retired 'restrictions' wording replaced with flat maxFiles/maxFileSize/allowedFileTypes; getting-started reframed to six frameworks with quickstart links; code-examples gains a front-matter description (llms.txt index quality); endpoint naming unified on /api/upload-token. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…stants page quickstarts for react/vue/svelte/angular/vanilla/preact/next (client-mode-first, runnable, uploadTokenSecret in every server snippet); fact-checked comparisons vs uppy/filepond/react-dropzone/uploadthing; 'Use upup with AI assistants' page with a paste-ready context block and llms.txt pointers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, llms.txt hero + metadata repositioned to 'one file uploader, every framework'; six-tab framework switcher (APG tabs pattern, zero-dep highlighter); JSON-LD SoftwareApplication + FAQPage; metadataBase; App Router sitemap covering landing + 15 docs URLs (static sitemap files removed - they shadow the route); public/llms.txt per llmstxt.org. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
root README rewritten: six-framework value prop, 9-package install matrix, runnable React example, server-mode teaser with the mandatory uploadTokenSecret (canonical env name); dev.to CDN banner hotlink replaced by committed assets/upup-banner.png; npm badges deferred until first publish; CI badge points at main.yml. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Single exact-substring codemod (@Upup -> @useupup) over every tracked text file; pnpm-lock.yaml regenerated by install. The old scope never reached npm (v1 shipped as upup-react-file-uploader), so no published consumer breaks. Derived-name fallout: ng-packagr names its bundle from the package name, so @useupup/angular now emits dist/fesm2022/useupup-angular.mjs — angular package.json main/module/exports updated (caught by smoke:packages; the size-limit entry survived because it globs dist/**/*.mjs). Riders folded in: publishConfig.access=public on all 9 publishable packages (scoped packages default restricted; changesets access:public only covers changeset publish); '@Upup' added to the retired-vocabulary census with zero exceptions; CLAUDE.md records the scope ruling. 117 touched files carried pre-existing prettier drift (predates this change at HEAD; verified on HEAD blobs) — normalized here because lint-staged requires staged files clean. Gates at this tree: typecheck 32/32, test 27/27, build 15/15, lint 21/21, lint:ox, knip, vocab:check (14 tokens / 1212 files), test:quality (345), test:scripts, size, env:check, audit:prod, prettier-check, smoke:packages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…p publish changeset publish spawns pnpm publish (verified in @changesets/cli 2.30.0 source), which reads auth from the setup-node userconfig. Trusted publishing cannot cover a package's FIRST publish (per docs.npmjs.com, trust is configured on an existing package's settings), so the publish step now carries NODE_AUTH_TOKEN from the NPM_TOKEN repo secret; registry-url is added on a publish-gated setup-node step so install/gate steps never evaluate a token-referencing .npmrc. Once all nine packages exist and trusted publishers are configured, OIDC takes precedence and the secret can be revoked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
420 s died on a cold 2-core CI runner (PR #320: the slowest of six storybook boots never finished, zero tests started) and 660 s died on a loaded dev box (angular webpack hit 100% at the buzzer and never bound the port). The ceiling only delays reporting genuinely wedged boots; warm reuseExistingServer starts remain instant. Warm re-run at this tree: cross-framework 68 passed / 5 skipped in 4.5 m against real MinIO. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…8n guides
Wave 4 publish-prerequisite docs for the @useupup launch.
New pages:
- migration/v1-to-v2.md — full v1->v2 upgrade: package rename, complete prop
map, UpupError/UpupErrorCode taxonomy, client- vs server-mode uploads.
- guides/storage-providers.md — S3-compatible provider matrix + createUpupHandler
recipes (AWS, R2, MinIO, B2, DO Spaces, Wasabi, generic label).
- guides/server-auth.md — @useupup/server HMAC upload-token trust model,
secure-by-default 403s, per-user key scoping, signed size envelope.
- guides/theming.md — theme prop, tokens as CSS vars, slot overrides, DOM hooks.
- guides/headless.md — useUpupUpload hook, driving UpupCore directly, pipeline
opt-ins (webWorker/HEIC/resumable).
Edits:
- localization.md — namespaced-overrides fix, i18n-prop section, locale table,
next-steps nav, sentence-case headings.
- getting-started.md / error-handling.md — relative-link fixes to the new pages.
- guides/{modes,server-mode-setup,error-monitoring}.md — description front
matter for the llms.txt generator.
- migration/v2-to-v2.1.md — sidebar_position bump so v1->v2 sorts first.
Two MDX-hazard fixes: inline-code spans that wrapped a line break (headless
progress-shape, v1->v2 resumable {mode} example) were reflowed to keep the span
atomic — a wrapped span was parsed as a JSX expression and failed the build.
Verified: docusaurus build (onBrokenLinks:'throw') green, 36 docs, llms.txt +
llms-full.txt regenerated; prettier --check green; vocab:check green (migration/
is an intentional retired-token exemption); rendered HTML spot-checked on both
MDX-sensitive pages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consume the 19 pending changesets (P-series contracts, quality-gates strict flags, v2.1 legacy-prop removal, DOM-vocab unfreeze). Every publishable package carries at least one major changeset, so all nine bump uniformly to 3.0.0 — the first @useupup release and a clean signal of the breaking scope rename from @Upup. Private workspace consumers (storybook apps, examples) took their dependent patch bumps. changelog:false in the changeset config, so no CHANGELOG files are generated by design. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Switch the v2 npm scope to the final @upupjs org. @useupup was published as @useupup/*@3.0.0 minutes earlier this session; those packages are superseded and will be deprecated in favor of @upupjs/*@3.0.0. Method: single exact-substring codemod over `git ls-files` (@useupup -> @upupjs, 1520 occurrences across 544 files), plus the ng-packagr derived FESM bundle name useupup-angular -> upupjs-angular in packages/angular/package.json (the derived name is not the scope token, so it is fixed explicitly; smoke:packages is the gate that catches it). pnpm-lock regenerated via install. Deliberately NOT changed: the docs/landing domain useupup.com (85 refs) — the npm scope (@upupjs) and the web domain (useupup.com) are a deliberate split; revisit only if upupjs.com is adopted. Brand, DOM contract strings, and the GitHub repo stay bare `upup`. Gates green at this HEAD: vocab:check (1216 files), prettier-check, typecheck (32/32), build (15/15), smoke:packages (real @upupjs tarball consumer). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All nine @upupjs packages now exist on npm (first published 2026-07-13), so the token-bootstrap phase is over and OIDC trusted publishing is the intended path for future releases. npm OIDC trusted publishing requires Node >=22.14, so the publish-scoped setup-node now pins 22.14.0 (the gate steps above still run on .nvmrc / Node 20 — build output is target-pinned and Node-version-independent). npm bumped 11.5.1 -> 11.16.0; id-token: write was already present. NODE_AUTH_TOKEN (NPM_TOKEN secret) stays as a graceful fallback until a trusted publisher is configured per package on npmjs.com (repo DevinoSolutions/upup, workflow publish.yml). `npm trust` CLI cannot do this with a token (403 — trusted-publisher config is an account change that bypass-2FA tokens are forbidden from); it must be set per package in the npm web UI. Once OIDC is verified in a real release the secret and the env can be dropped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
The v2 integration branch: the full rewrite of upup into a headless core + six native framework packages, followed by a staged foundational audit (22 fix plans), a quality-gates hardening pass (55 commits), and a final convergence fix batch (9 commits). Everything below has been verified locally; this PR exists first and foremost to give the CI workflows their first real execution — they were authored and revised during the audit but have never run on GitHub's runners.
Contents
@upup/coreheadless engine (file state, upload pipeline, web-worker offload, cloud-drive plugins, i18n, theme) + native UI packages for React, Vue, Svelte, Angular, Vanilla, Preact, plus@upup/nextand@upup/server(HMAC-signed server-mode uploads to S3/MinIO).PopupOAuthPlugin), curated public API (@upup/core/internal), i18n engine collapse, release pipeline, docs truth pass.exactOptionalPropertyTypes,noUncheckedIndexedAccess), oxlint, knip, husky pre-commit/pre-push, env drift guard, coverage floors, and the CI jobs this PR is exercising.Local verification (HEAD
e32dd601)What CI should show on this PR
main.yml: prettier → unit suites + coverage floors → typecheck → build → size-limit → prod dependency audit → oxlint → knip → env drift →Status-Checkaggregate gate.e2e.yml: fullpnpm run e2e(MinIO provisioned from the example env) +Smoke-Packages(real npm-tarball consumer).If a job fails here but passed locally, that's exactly the signal this draft exists to surface (runner resources, env provisioning, action versions).
🤖 Generated with Claude Code