fix(packages): declare runtime imports as production dependencies
CI / verify (pull_request) Successful in 47s
CI / infrastructure-smoke (pull_request) Successful in 21s
CI / migration-smoke (pull_request) Successful in 1m45s

@ekklesia/module-sdk imports drizzle-orm at run time but declared it
only as a peer and a development dependency. A workspace package is
linked into its consumers as a directory, so its imports resolve from
its own node_modules, where a consumer never satisfies a peer; a
production install omits development dependencies. In the production
image this made every module with a schema fail: the API exited at
startup and Doctor reported NOT READY.

- Move drizzle-orm to module-sdk's dependencies, keeping the peer.
- Move react to @ekklesia/ui's dependencies, keeping the peer. The new
  guard flags it the same way; it is bundled at build time today, so
  it was not a live failure. Both lockfile entries keep the identical
  resolution, so nothing new is installed and the shell bundles still
  contain one React.
- Add the boundary rule runtime-imports-are-production-dependencies:
  code a package ships (src/, bin/, excluding tests and .d.ts) must
  declare every runtime import in dependencies or optionalDependencies.
  Type-only imports are exempt; .tsx/.jsx implies react (automatic
  JSX runtime). Fixtures cover peer-only, dev-only, JSX, undeclared,
  and the accepted cases.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ekklesia
2026-10-01 19:29:35 -05:00
co-authored by Claude Opus 5.5
parent d5d4928b9f
commit fc422b6d8d
23 changed files with 322 additions and 13 deletions
@@ -0,0 +1,29 @@
{
"name": "@ekklesia/sdk",
"version": "0.0.0",
"type": "module",
"dependencies": {
"drizzle-orm": "0.45.2",
"react": "19.2.8",
"polyfill": "1.0.0"
},
"peerDependencies": {
"drizzle-orm": "0.45.2",
"react": "19.2.8"
},
"optionalDependencies": {
"fsevents": "2.3.3"
},
"devDependencies": {
"esbuild": "0.28.1",
"type-only": "1.0.0",
"vitest": "4.1.10"
},
"exports": {
".": "./src/index.ts",
"./schema": "./src/index.ts"
},
"ekklesia": {
"layer": "module-sdk"
}
}
@@ -0,0 +1,3 @@
import { build } from "esbuild";
await build({ entryPoints: ["src/index.ts"] });
@@ -0,0 +1,5 @@
import { describe, it } from "vitest";
describe("sdk", () => {
it("runs with development dependencies installed", () => undefined);
});
@@ -0,0 +1,13 @@
import { readFile } from "fs/promises";
import path from "node:path";
import "polyfill";
import { pgTableCreator } from "drizzle-orm/pg-core";
import type { Config } from "type-only";
import { type Options, type Settings } from "type-only/settings";
export type { Shape } from "type-only/shape";
export { createSchema } from "@ekklesia/sdk/schema";
export async function load(config: Config, options: Options, settings: Settings) {
const watcher = await import("fsevents");
return { config, options, path, readFile, settings, table: pgTableCreator, watcher };
}
@@ -0,0 +1,3 @@
export function View() {
return <div />;
}
@@ -0,0 +1,3 @@
import { build } from "esbuild";
export const builder = build;
@@ -3,7 +3,8 @@
"version": "0.0.0",
"type": "module",
"dependencies": {
"@ekklesia/module-ui-host": "workspace:*"
"@ekklesia/module-ui-host": "workspace:*",
"react": "19.2.8"
},
"exports": {
".": "./src/index.ts"
@@ -2,6 +2,9 @@
"name": "@ekklesia/module-announcements",
"version": "0.0.0",
"type": "module",
"dependencies": {
"react": "19.2.8"
},
"exports": {
".": "./src/index.ts"
},
@@ -0,0 +1,12 @@
{
"name": "@ekklesia/sdk",
"version": "0.0.0",
"type": "module",
"devDependencies": { "zod": "4.0.0" },
"exports": {
".": "./src/index.ts"
},
"ekklesia": {
"layer": "module-sdk"
}
}
@@ -0,0 +1,3 @@
import { z } from "zod";
export const identifier = z.string();
@@ -0,0 +1,13 @@
{
"name": "@ekklesia/ui",
"version": "0.0.0",
"type": "module",
"peerDependencies": { "react": "19.2.8" },
"devDependencies": { "react": "19.2.8" },
"exports": {
".": "./src/index.ts"
},
"ekklesia": {
"layer": "ui"
}
}
@@ -0,0 +1,3 @@
export function Badge(props: { readonly label: string }) {
return <span>{props.label}</span>;
}
@@ -0,0 +1,13 @@
{
"name": "@ekklesia/sdk",
"version": "0.0.0",
"type": "module",
"peerDependencies": { "drizzle-orm": "0.45.2" },
"devDependencies": { "drizzle-orm": "0.45.2" },
"exports": {
".": "./src/index.ts"
},
"ekklesia": {
"layer": "module-sdk"
}
}
@@ -0,0 +1,3 @@
import { pgTableCreator } from "drizzle-orm/pg-core";
export const createTable = pgTableCreator((name) => name);
@@ -0,0 +1,12 @@
{
"name": "@ekklesia/module-calendar",
"version": "0.0.0",
"type": "module",
"exports": {
".": "./src/index.ts"
},
"ekklesia": {
"layer": "module"
}
}
@@ -0,0 +1,3 @@
export async function loadSharp() {
return await import("sharp");
}