ReviewOS

stacks/postline

Migration plan: adopt framework-as-dependencies (from vendored workspace)

#14
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Context

Stacks is moving from a vendored framework (storage/framework/ as workspace packages) to consuming it as published @stacksjs/* npm packages (the newer workflow; also enables buddy extension:* browser-extension builds).

postline is on the vendored-workspace model (like bench-review), and the most entangled of the apps assessed - its core @stacksjs/* are live symlinks into the vendored tree, so storage/framework/ is actively load-bearing, not dead weight.

Current state (assessed 2026-07-20)

  • Framework = vendored workspace, actively symlinked: node_modules/@stacksjs/{orm,router,actions,database,validation} and node_modules/stacks are symlinks into storage/framework/core/*. stacks: "workspace:*", workspaces: ['storage/framework', 'storage/framework/core/*', ...], storage/framework/ = ~55M (89 core packages), package name stacks-framework@0.70.40.
  • Mixed resolution: @stacksjs/stx is a real npm package (stx repo), while the core framework resolves to the vendored tree.
  • tsconfig inherits the vendored framework: "extends": "./storage/framework/core/tsconfig.json".
  • build:reset builds the vendored tree (cd storage/framework && bun run build).
  • No config/crosswind.ts. App version 0.0.0.

Migration surface

  1. package.json - stacks: "workspace:*" → published ^0.70.x (vendored is 0.70.40; published meta ~0.70.73); remove the workspaces: ['storage/framework/**', ...] block.
  2. tsconfig.json - replace extends: "./storage/framework/core/tsconfig.json" with a self-contained config resolving @stacksjs/* from node_modules and excluding storage/framework/ (mirror bughq's / ghostanalytics's target tsconfig).
  3. package.json build:reset - remove the cd storage/framework && bun run build step.
  4. bun install - replaces the symlinks-into-storage/framework with real npm packages of stacks + the core @stacksjs/*.
  5. Delete storage/framework/ (~55M) - only after step 4. Keep storage/{logs,keys,app,cloud}.

Plan

  1. Confirm a working baseline: buddy dev runs today (+ /stacks-browse QA), so a regression is attributable.
  2. Branch chore/adopt-framework-deps.
  3. package.json: stacks → published version; remove the storage/framework/** workspaces.
  4. tsconfig.json: swap the vendored extends for a self-contained config (copy the sibling apps').
  5. Remove the vendored-build step from build:reset.
  6. bun install - the pivotal step. Confirm node_modules/@stacksjs/{orm,router,...} and node_modules/stacks are now real dirs, not symlinks into storage/framework.
  7. mv storage/framework storage/framework.bak - only after step 5 (see risks).
  8. Verify end-to-end: buddy dev + /stacks-browse browser QA, buddy build, buddy lint, buddy test.
  9. Green → rm -rf storage/framework.bak.

Risks / watch-outs (highest coupling of the apps assessed)

  • Order-critical: the core @stacksjs/* + stacks are live symlinks into storage/framework. Removing the tree before the npm re-install (step 5) breaks orm/router/actions/database/validation/stacks. Rewire + bun install first, mv second.
  • Version drift: vendored stacks-framework@0.70.40 → published stacks@~0.70.73; the app (v0.0.0) was built against 0.70.40 - expect to reconcile API/version drift.
  • tsconfig replacement is load-bearing (the whole TS/build setup is inherited from the vendored tree).
  • Verify in the running app with /stacks-browse, not isolated renderView (a recent framework sidebar change passed a headless render but was broken live).
  • Sequence: pilot the near-drop-in apps (bughq/ghostanalytics) first; do the vendored-workspace apps (bench-review, postline) last - postline is the most entangled.

Rollback

Nothing destructive until step 8 - mv storage/framework.bak storage/framework + git checkout restores everything.

9 days ago

Correction: HOLD — postline is an SSR/API app, blocked on framework Phase 1

My earlier plan on this issue said to execute the migration now (rewire deps → bun install → remove the vendored tree). The bughq / ghostanalytics pilot findings invalidate that for postline. See stacksjs/bughq#8 (framework plan) and stacksjs/ghostanalytics#2.

The vendored storage/framework/ is live framework source, and the latest framework (0.70.145) still hardcodes its paths:

  • @stacksjs/server scans defaults/{functions,app/Models,app/Controllers} at boot → removing the tree ENOENTs the API boot
  • buddy build/serve/dev dispatch into storage/framework/core/actions/src/** → the full app build breaks

Upgrading alone does not fix this (0.70.145 has more hardcoded refs than 0.70.73, not fewer). The unblock is a framework change — a package-first resolver with a vendored fallback — which hasn't shipped.

postline is squarely in the affected archetype (SSR/API), not the static-SSG exception

  • build = ./buddy build; dev/serve/deploy = ./buddy …. No build.ts / preview.ts / netlify.toml / stx.config.ts.
  • has routes/api.ts + app/Actions → boots @stacksjs/server
  • its buddy script is bun ./storage/framework/core/buddy/src/cli.ts — the vendored CLI directly, with no node_modules fallback

So removing postline's vendored tree today breaks both its API boot and buddy build. Do not execute the migration yet.

Revised status

  • Blocked on framework Phase 1 (the package-first resolver on bughq#8). Phase-3 rollout target.
  • Sequence: framework Phase 1 ships → pilot bughq green through a real deploy → then ghostanalytics + postline on the same verified recipe (browser QA as the source of truth; SSR-200 is not enough).
  • The postline-specific mechanics from the earlier plan still hold for when it's unblocked — the core @stacksjs/* are workspace-symlinked into the tree (rewire + bun install before removing it), stacks-framework@0.70.40 vendored → published, build:reset references the tree, tsconfig extends the vendored config — but they are on hold, not actionable now.

Contrast

bench-review (#73) — a static-SSG app — was migrated green today, because its @stacksjs/stx buildApp → dist path never touches the server-boot or full-build code paths. postline has no such static path, so it waits for the framework.

9 days ago

Correction — verified against the actual 0.70.145, not the stale 0.70.73

Retracting the "blocked on unshipped framework Phase 1" framing above. That conclusion was inherited from the bughq/ghostanalytics analysis, which runs @stacksjs/*@0.70.73. I re-checked against the installed 0.70.145 (bench-review's node_modules), and the framework has moved well past that:

  • @stacksjs/server boot is no longer ENOENT-fatal on a missing tree. 0.70.145's server/dist/imports.js wraps its directory scans in try/catch (7 guards); the 0.70.73 server doesn't even ship that file layout (the package was restructured since). So "the API won't boot" is a 0.70.73 artifact.
  • @stacksjs/buddy dispatches actions via import("@stacksjs/actions") and actions resolves the build path from the package — verified live: bench-review's ./buddy build runs green (37/37) on 0.70.145.
  • The dev orchestration (actions/dist/dev/views.js) already resolves views/layouts/partials via a package-first firstExistingPath(...). One straggler line remains: componentsDir is still hardcoded to storage/framework/defaults/resources/components, so buddy dev's stx frontend has no components dir without the vendored tree. That's a one-line upstream fix, not a missing subsystem — and it only affects local HMR dev, not the build.

Revised status: pilot candidate, not blocked. The resolver is substantially shipped in 0.70.145. postline's production path (buddy build + buddy serve as an SSR app) is simply unproven as deps and needs a real pilot — the way bench-review's static path was piloted green (#73) — rather than an indefinite wait. Known snag to expect: the componentsDir straggler above for buddy dev.

9 days ago

Done — piloted end-to-end, it migrated (retracting the earlier "hold")

I ran the migration instead of theorizing about it. It works, in ~20 minutes, with one real break. Branch chore/adopt-framework-deps (commit 9dcbfad, local, not pushed):

  • package.json: stacks: workspace:* → ^0.70.145; added @stacksjs/stx + bun-plugin-stx (bunfig references bun-plugin-stx, and the stacks meta doesn't pull it); removed the storage/framework/** workspaces; pointed the buddy script + build:reset off the vendored core
  • tsconfig.json: self-contained (resolve @stacksjs/* from node_modules)
  • bunfig.toml: preload a local resources/plugins/preloader.ts
  • build.ts (new, 5 lines) — the one real breakage. Without a project-local build.ts, @stacksjs/actions build/views falls back to building the vendored storage/framework/views/web, which the migration removes (Bun.spawn ENOENT on the missing cwd). Adding buildApp() from @stacksjs/stx makes buddy build take the project-local branch.
  • removed the ~55M vendored tree (kept generated types/ + auto-imports/)

Verified: bun install → 427 published packages; bun build.ts → 12/12 static pages, 0 failed; served dist renders the login/composer UI (Crosswind CSS intact).

Correcting the record: my earlier "blocked on unshipped framework Phase 1" was wrong — it came from the other agents' analysis of 0.70.73. Against the actual 0.70.145, buddy dispatches actions from the package and the server tolerates a missing tree; this is a mechanical recipe + a short fix-what-breaks list, not a blocked item.

Not yet exercised: the API/server side (routes/api.ts, buddy serve). The frontend static build is green; if postline deploys as a server, that path wants its own shakeout.

9 days ago

Done — migrated and verified through the SSR/API path (PR #15)

Correcting the earlier "it migrated, it works" comment: that verification was static-build-only (bun build.ts → 12/12 pages). The static path never touches the server boot, the ORM/auth, or any /postline/* action — which is Postline's entire feature surface. Exercising it surfaced seven real breakages, all from features that had been implemented as in-place edits to framework packages (now gone with the vendored tree):

  1. app/Routes.ts re-exported the deleted defaults/app/Routes → API wouldn't boot
  2. @stacksjs/queue → missing @stacksjs/bun-queue transitive dep
  3. Bluesky driver enhancements (reply threads, facets, image blob upload, engagement metrics) were edits to vendored @stacksjs/socials — the published package has none of them; re-homed as an app-owned driver
  4. Instagram/Threads media type mismatch vs the published drivers
  5. cookie→bearer bridge was a vendored dev-proxy edit → browser /api calls 401'd; re-homed to a layout fetch wrapper
  6. login 500: 0.70.145's useAuth selects two_factor_* columns the 0.70.40-era users migration predates
  7. defaults/app/Models (incl. User) deleted with nothing to repopulate it on clone

The componentsDir straggler flagged earlier is real in 0.70.145 but harmless for this app.

Verified end-to-end on deps: buddy build 12/12, API boots, full register/login/queue/analytics/blog flow, 16 tests, tsc + pickier clean, and browser QA green (auth gate, all 5 providers, real data, sign-out — 0 console errors).

Key lesson for the remaining apps: the migration is a superset of "swap deps + delete tree." Any feature that patched a framework package in place must be re-homed into app code, and storage/framework/defaults/ must survive on disk (committed here; a postinstall sync is the cleaner long-term option). SSR/API apps must be validated through the running server + browser, not the static build.

Sign in to comment on this issue.