fix(packages): declare runtime imports as production dependencies
@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:
co-authored by
Claude Opus 5.5
parent
d5d4928b9f
commit
fc422b6d8d
@@ -41,7 +41,7 @@ These rules map platform ownership to filesystem and package boundaries. The exe
|
|||||||
general "browser-safe shared packages are fine" relaxation: `api-client`, `auth-client`,
|
general "browser-safe shared packages are fine" relaxation: `api-client`, `auth-client`,
|
||||||
`surface-sdk`, and `module-ui-host` remain rejected inside a module. Both approved packages are
|
`surface-sdk`, and `module-ui-host` remain rejected inside a module. Both approved packages are
|
||||||
browser-safe by construction — `design-tokens` has no dependencies at all and `ui` depends only on
|
browser-safe by construction — `design-tokens` has no dependencies at all and `ui` depends only on
|
||||||
it — so a module UI file that imports them still cannot typecheck `process.env`, proven by a real
|
it and React — so a module UI file that imports them still cannot typecheck `process.env`, proven by a real
|
||||||
compile under the exact browser project a module uses. Both are also pinned at the bottom of the
|
compile under the exact browser project a module uses. Both are also pinned at the bottom of the
|
||||||
presentation stack by `design-packages-depend-downward-only`: neither may reach an implementation
|
presentation stack by `design-packages-depend-downward-only`: neither may reach an implementation
|
||||||
layer, an API/auth client, or the backend contracts barrel, because a design package that could
|
layer, an API/auth client, or the backend contracts barrel, because a design package that could
|
||||||
@@ -134,6 +134,14 @@ These rules map platform ownership to filesystem and package boundaries. The exe
|
|||||||
- Workspace package consumers use declared `exports`, not private subpaths.
|
- Workspace package consumers use declared `exports`, not private subpaths.
|
||||||
- Every internal dependency uses a `workspace:` range and every imported workspace package is
|
- Every internal dependency uses a `workspace:` range and every imported workspace package is
|
||||||
declared by the consumer.
|
declared by the consumer.
|
||||||
|
- Code a workspace package ships (its `src/` and `bin/`, excluding tests and declaration files)
|
||||||
|
declares every runtime import in `dependencies` or `optionalDependencies`. A peer declaration may
|
||||||
|
stay beside it, but never replaces it: 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, and a production install (`pnpm install --prod`, as the production image runs) omits
|
||||||
|
development dependencies. Type-only imports are exempt; a `.tsx`/`.jsx` file counts as importing
|
||||||
|
`react`, because the automatic JSX runtime imports `react/jsx-runtime` implicitly
|
||||||
|
(`runtime-imports-are-production-dependencies`).
|
||||||
- Workspace aliases are forbidden because they obscure package ownership in the dependency graph.
|
- Workspace aliases are forbidden because they obscure package ownership in the dependency graph.
|
||||||
- Workspace package names are unique and shared packages declare a recognized architectural role.
|
- Workspace package names are unique and shared packages declare a recognized architectural role.
|
||||||
- Circular source and workspace-package dependencies are rejected.
|
- Circular source and workspace-package dependencies are rejected.
|
||||||
|
|||||||
@@ -19,12 +19,12 @@
|
|||||||
"typecheck": "tsc --noEmit --project tsconfig.json"
|
"typecheck": "tsc --noEmit --project tsconfig.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ekklesia/contracts": "workspace:*"
|
"@ekklesia/contracts": "workspace:*",
|
||||||
|
"drizzle-orm": "0.45.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.5.3",
|
"@biomejs/biome": "2.5.3",
|
||||||
"@types/node": "24.13.3",
|
"@types/node": "24.13.3",
|
||||||
"drizzle-orm": "0.45.2",
|
|
||||||
"typescript": "7.0.2",
|
"typescript": "7.0.2",
|
||||||
"vitest": "4.1.10"
|
"vitest": "4.1.10"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
"typecheck": "tsc --noEmit --project tsconfig.json && tsc --noEmit --project tsconfig.test.json"
|
"typecheck": "tsc --noEmit --project tsconfig.json && tsc --noEmit --project tsconfig.test.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ekklesia/design-tokens": "workspace:*"
|
"@ekklesia/design-tokens": "workspace:*",
|
||||||
|
"react": "19.2.8"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "19.2.8"
|
"react": "19.2.8"
|
||||||
@@ -31,7 +32,6 @@
|
|||||||
"@types/react": "19.2.18",
|
"@types/react": "19.2.18",
|
||||||
"@types/react-dom": "19.2.4",
|
"@types/react-dom": "19.2.4",
|
||||||
"happy-dom": "20.0.11",
|
"happy-dom": "20.0.11",
|
||||||
"react": "19.2.8",
|
|
||||||
"react-dom": "19.2.8",
|
"react-dom": "19.2.8",
|
||||||
"typescript": "7.0.2",
|
"typescript": "7.0.2",
|
||||||
"vitest": "4.1.10"
|
"vitest": "4.1.10"
|
||||||
|
|||||||
@@ -67,7 +67,11 @@ describe("the shared elements consume design tokens, never hardcoded theme value
|
|||||||
await readFile(path.join(packageDirectory, "package.json"), "utf8"),
|
await readFile(path.join(packageDirectory, "package.json"), "utf8"),
|
||||||
) as { dependencies?: Record<string, string>; peerDependencies?: Record<string, string> };
|
) as { dependencies?: Record<string, string>; peerDependencies?: Record<string, string> };
|
||||||
|
|
||||||
expect(Object.keys(manifest.dependencies ?? {})).toEqual(["@ekklesia/design-tokens"]);
|
// React is both a production dependency and a peer: the peer states that the consumer's React
|
||||||
|
// must be the same one, and the production declaration is what makes the package's own imports
|
||||||
|
// resolve, since a consumer never satisfies a linked workspace package's peer
|
||||||
|
// (`runtime-imports-are-production-dependencies`).
|
||||||
|
expect(Object.keys(manifest.dependencies ?? {})).toEqual(["@ekklesia/design-tokens", "react"]);
|
||||||
expect(Object.keys(manifest.peerDependencies ?? {})).toEqual(["react"]);
|
expect(Object.keys(manifest.peerDependencies ?? {})).toEqual(["react"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Generated
+6
-6
@@ -201,6 +201,9 @@ importers:
|
|||||||
'@ekklesia/contracts':
|
'@ekklesia/contracts':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../contracts
|
version: link:../contracts
|
||||||
|
drizzle-orm:
|
||||||
|
specifier: 0.45.2
|
||||||
|
version: 0.45.2(@types/[email protected])([email protected])([email protected])
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@biomejs/biome':
|
'@biomejs/biome':
|
||||||
specifier: 2.5.3
|
specifier: 2.5.3
|
||||||
@@ -208,9 +211,6 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: 24.13.3
|
specifier: 24.13.3
|
||||||
version: 24.13.3
|
version: 24.13.3
|
||||||
drizzle-orm:
|
|
||||||
specifier: 0.45.2
|
|
||||||
version: 0.45.2(@types/[email protected])([email protected])([email protected])
|
|
||||||
typescript:
|
typescript:
|
||||||
specifier: 7.0.2
|
specifier: 7.0.2
|
||||||
version: 7.0.2
|
version: 7.0.2
|
||||||
@@ -287,6 +287,9 @@ importers:
|
|||||||
'@ekklesia/design-tokens':
|
'@ekklesia/design-tokens':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../design-tokens
|
version: link:../design-tokens
|
||||||
|
react:
|
||||||
|
specifier: 19.2.8
|
||||||
|
version: 19.2.8
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@biomejs/biome':
|
'@biomejs/biome':
|
||||||
specifier: 2.5.3
|
specifier: 2.5.3
|
||||||
@@ -306,9 +309,6 @@ importers:
|
|||||||
happy-dom:
|
happy-dom:
|
||||||
specifier: 20.0.11
|
specifier: 20.0.11
|
||||||
version: 20.0.11
|
version: 20.0.11
|
||||||
react:
|
|
||||||
specifier: 19.2.8
|
|
||||||
version: 19.2.8
|
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: 19.2.8
|
specifier: 19.2.8
|
||||||
version: 19.2.8([email protected])
|
version: 19.2.8([email protected])
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { readdir, readFile, stat } from "node:fs/promises";
|
import { readdir, readFile, stat } from "node:fs/promises";
|
||||||
|
import { builtinModules } from "node:module";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { pathToFileURL } from "node:url";
|
import { pathToFileURL } from "node:url";
|
||||||
import { parse } from "yaml";
|
import { parse } from "yaml";
|
||||||
@@ -506,6 +507,82 @@ function collectImportSpecifiers(sourceText: string): string[] {
|
|||||||
return [...specifiers];
|
return [...specifiers];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a file is code a workspace package ships and executes: anything under its `src/` or
|
||||||
|
* `bin/`, except tests and declaration files. Build scripts, configuration files, and tests run with
|
||||||
|
* development dependencies installed, so they are outside this rule.
|
||||||
|
*/
|
||||||
|
function isRuntimeSourceFile(packageRelativePath: string): boolean {
|
||||||
|
const normalized = normalizePath(packageRelativePath);
|
||||||
|
if (!normalized.startsWith("src/") && !normalized.startsWith("bin/")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const segments = normalized.split("/");
|
||||||
|
if (segments.some((segment) => ["__tests__", "test", "tests"].includes(segment))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const fileName = segments.at(-1) ?? "";
|
||||||
|
return !/\.(?:test|spec)\.[cm]?[jt]sx?$/u.test(fileName) && !/\.d\.[cm]?ts$/u.test(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** `import { type A, type B } from "x"` has no runtime binding: without `verbatimModuleSyntax`, the
|
||||||
|
* compiler erases it exactly like `import type`. A default or namespace binding, or any specifier
|
||||||
|
* without `type`, is a runtime import. */
|
||||||
|
function importClauseIsTypeOnly(clause: string): boolean {
|
||||||
|
const trimmed = clause.trim();
|
||||||
|
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const specifiers = trimmed
|
||||||
|
.slice(1, -1)
|
||||||
|
.split(",")
|
||||||
|
.map((specifier) => specifier.trim())
|
||||||
|
.filter((specifier) => specifier.length > 0);
|
||||||
|
return specifiers.length > 0 && specifiers.every((specifier) => /^type\s/u.test(specifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bare specifiers a source file needs at run time: every static, side-effect, and dynamic import
|
||||||
|
* except type-only ones. A `.tsx`/`.jsx` file also needs `react`, because the platform compiles JSX
|
||||||
|
* with the automatic runtime (`"jsx": "react-jsx"`), which imports `react/jsx-runtime` without that
|
||||||
|
* import appearing in the source.
|
||||||
|
*/
|
||||||
|
function collectRuntimeImportSpecifiers(sourceText: string, extension: string): string[] {
|
||||||
|
const specifiers = new Set<string>();
|
||||||
|
const staticImport = /\b(?:import|export)\s+(type\s+)?([^;]*?)\bfrom\s*["']([^"'\n]+)["']/gu;
|
||||||
|
for (const match of sourceText.matchAll(staticImport)) {
|
||||||
|
const [, typeKeyword, clause = "", specifier] = match;
|
||||||
|
if (specifier && !typeKeyword && !importClauseIsTypeOnly(clause)) {
|
||||||
|
specifiers.add(specifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sideEffectImport = /\bimport\s*["']([^"'\n]+)["']/gu;
|
||||||
|
const dynamicImport = /\b(?:import|require)\s*\(\s*["']([^"'\n]+)["']\s*\)/gu;
|
||||||
|
for (const pattern of [sideEffectImport, dynamicImport]) {
|
||||||
|
for (const match of sourceText.matchAll(pattern)) {
|
||||||
|
if (match[1]) {
|
||||||
|
specifiers.add(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (extension === ".tsx" || extension === ".jsx") {
|
||||||
|
specifiers.add("react/jsx-runtime");
|
||||||
|
}
|
||||||
|
return [...specifiers];
|
||||||
|
}
|
||||||
|
|
||||||
|
const NODE_BUILTIN_MODULES = new Set(builtinModules);
|
||||||
|
|
||||||
|
function isBareSpecifier(specifier: string): boolean {
|
||||||
|
return !(
|
||||||
|
specifier.startsWith(".") ||
|
||||||
|
specifier.startsWith("/") ||
|
||||||
|
specifier.startsWith("#") ||
|
||||||
|
specifier.startsWith("node:") ||
|
||||||
|
/^[a-z][a-z0-9+.-]*:/u.test(specifier)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function resolveSourceImport(
|
async function resolveSourceImport(
|
||||||
fromFile: string,
|
fromFile: string,
|
||||||
specifier: string,
|
specifier: string,
|
||||||
@@ -906,6 +983,49 @@ export async function checkBoundaries(rootDirectory: string): Promise<BoundaryRe
|
|||||||
sourceGraph.set(relativeFile, new Set());
|
sourceGraph.set(relativeFile, new Set());
|
||||||
|
|
||||||
const sourceText = await readFile(codeFile, "utf8");
|
const sourceText = await readFile(codeFile, "utf8");
|
||||||
|
|
||||||
|
// A workspace package is linked into its consumers as a directory, so Node resolves the
|
||||||
|
// package's own imports from the package's own `node_modules`. A peer dependency is never
|
||||||
|
// satisfied there by the consumer, and a production install (`pnpm install --prod`, as the
|
||||||
|
// production image runs) omits development dependencies. Code a package ships must therefore
|
||||||
|
// declare every runtime import as a production dependency. A peer declaration may stay beside it.
|
||||||
|
const runtimePackage = findContainingPackage(codeFile, packagesByDepth);
|
||||||
|
if (runtimePackage && isRuntimeSourceFile(path.relative(runtimePackage.directory, codeFile))) {
|
||||||
|
const reported = new Set<string>();
|
||||||
|
for (const specifier of collectRuntimeImportSpecifiers(
|
||||||
|
sourceText,
|
||||||
|
path.extname(codeFile).toLowerCase(),
|
||||||
|
)) {
|
||||||
|
if (!isBareSpecifier(specifier)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const dependencyName = packageNameFromSpecifier(specifier);
|
||||||
|
const manifest = runtimePackage.manifest;
|
||||||
|
if (
|
||||||
|
reported.has(dependencyName) ||
|
||||||
|
dependencyName === runtimePackage.name ||
|
||||||
|
NODE_BUILTIN_MODULES.has(dependencyName) ||
|
||||||
|
manifest.dependencies?.[dependencyName] !== undefined ||
|
||||||
|
manifest.optionalDependencies?.[dependencyName] !== undefined
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reported.add(dependencyName);
|
||||||
|
const declaredAs =
|
||||||
|
manifest.peerDependencies?.[dependencyName] !== undefined
|
||||||
|
? "only as a peer dependency"
|
||||||
|
: manifest.devDependencies?.[dependencyName] !== undefined
|
||||||
|
? "only as a development dependency"
|
||||||
|
: "not at all";
|
||||||
|
violations.push({
|
||||||
|
from: relativeFile,
|
||||||
|
message: `${runtimePackage.name} imports ${dependencyName} at run time but declares it ${declaredAs}; a runtime import must be in dependencies.`,
|
||||||
|
rule: "runtime-imports-are-production-dependencies",
|
||||||
|
to: dependencyName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const specifier of collectImportSpecifiers(sourceText)) {
|
for (const specifier of collectImportSpecifiers(sourceText)) {
|
||||||
const dependencyName = packageNameFromSpecifier(specifier);
|
const dependencyName = packageNameFromSpecifier(specifier);
|
||||||
if (from.layer === "module" && isModuleUiPackage(dependencyName)) {
|
if (from.layer === "module" && isModuleUiPackage(dependencyName)) {
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ describe("repository dependency boundaries", () => {
|
|||||||
["design-package-depends-upward", "design-packages-depend-downward-only"],
|
["design-package-depends-upward", "design-packages-depend-downward-only"],
|
||||||
["cross-package-relative", "workspace-imports-use-public-exports"],
|
["cross-package-relative", "workspace-imports-use-public-exports"],
|
||||||
["cycle", "no-circular-source-dependencies"],
|
["cycle", "no-circular-source-dependencies"],
|
||||||
|
["runtime-peer-only-dependency", "runtime-imports-are-production-dependencies"],
|
||||||
|
["runtime-dev-only-dependency", "runtime-imports-are-production-dependencies"],
|
||||||
|
["runtime-jsx-implies-react", "runtime-imports-are-production-dependencies"],
|
||||||
|
["runtime-undeclared-dependency", "runtime-imports-are-production-dependencies"],
|
||||||
])("rejects %s", async (fixture, expectedRule) => {
|
])("rejects %s", async (fixture, expectedRule) => {
|
||||||
const result = await checkBoundaries(path.join(fixtures, fixture));
|
const result = await checkBoundaries(path.join(fixtures, fixture));
|
||||||
expect(result.violations.map((violation) => violation.rule)).toContain(expectedRule);
|
expect(result.violations.map((violation) => violation.rule)).toContain(expectedRule);
|
||||||
@@ -106,6 +110,57 @@ describe("repository dependency boundaries", () => {
|
|||||||
expect(uiViolations.map((violation) => violation.message).join(" ")).not.toContain("compat");
|
expect(uiViolations.map((violation) => violation.message).join(" ")).not.toContain("compat");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("accepts runtime imports declared as production dependencies, and everything that is not shipped runtime code", async () => {
|
||||||
|
// Production and optional dependencies, a peer kept beside its production declaration, Node
|
||||||
|
// built-ins, the package's own exports, type-only imports of development dependencies, and
|
||||||
|
// tests or build scripts that run with development dependencies installed.
|
||||||
|
const result = await checkBoundaries(path.join(fixtures, "legal-runtime-dependencies"));
|
||||||
|
|
||||||
|
expect(result.violations).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
[
|
||||||
|
"runtime-peer-only-dependency",
|
||||||
|
"packages/sdk/src/index.ts",
|
||||||
|
"drizzle-orm",
|
||||||
|
"only as a peer dependency",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"runtime-dev-only-dependency",
|
||||||
|
"packages/sdk/src/index.ts",
|
||||||
|
"zod",
|
||||||
|
"only as a development dependency",
|
||||||
|
],
|
||||||
|
// The automatic JSX runtime imports `react/jsx-runtime` without that import appearing in source.
|
||||||
|
[
|
||||||
|
"runtime-jsx-implies-react",
|
||||||
|
"packages/ui/src/index.tsx",
|
||||||
|
"react",
|
||||||
|
"only as a peer dependency",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"runtime-undeclared-dependency",
|
||||||
|
"modules/calendar/src/server/index.ts",
|
||||||
|
"sharp",
|
||||||
|
"not at all",
|
||||||
|
],
|
||||||
|
])("reports %s once, naming the file, the package, and how it is declared", async (fixture, from, to, declaredAs) => {
|
||||||
|
// A workspace package's imports resolve from its own node_modules: a peer is never satisfied
|
||||||
|
// there by the consumer, and a production install omits development dependencies, so each of
|
||||||
|
// these imports fails at run time in the production image even though development works.
|
||||||
|
const result = await checkBoundaries(path.join(fixtures, fixture));
|
||||||
|
|
||||||
|
expect(result.violations).toEqual([
|
||||||
|
{
|
||||||
|
from,
|
||||||
|
message: expect.stringContaining(`declares it ${declaredAs}`),
|
||||||
|
rule: "runtime-imports-are-production-dependencies",
|
||||||
|
to,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("still rejects module UI source that reaches into Core internals", async () => {
|
it("still rejects module UI source that reaches into Core internals", async () => {
|
||||||
// Allowing `.tsx` inside a module changed which *formats* a module may contain. It did not
|
// Allowing `.tsx` inside a module changed which *formats* a module may contain. It did not
|
||||||
// change which layers a module may import: module UI is subject to the identical layer rules
|
// change which layers a module may import: module UI is subject to the identical layer rules
|
||||||
|
|||||||
+29
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
import { build } from "esbuild";
|
||||||
|
|
||||||
|
await build({ entryPoints: ["src/index.ts"] });
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import { describe, it } from "vitest";
|
||||||
|
|
||||||
|
describe("sdk", () => {
|
||||||
|
it("runs with development dependencies installed", () => undefined);
|
||||||
|
});
|
||||||
+13
@@ -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 };
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
export function View() {
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
import { build } from "esbuild";
|
||||||
|
|
||||||
|
export const builder = build;
|
||||||
+2
-1
@@ -3,7 +3,8 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ekklesia/module-ui-host": "workspace:*"
|
"@ekklesia/module-ui-host": "workspace:*",
|
||||||
|
"react": "19.2.8"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
|
|||||||
+3
@@ -2,6 +2,9 @@
|
|||||||
"name": "@ekklesia/module-announcements",
|
"name": "@ekklesia/module-announcements",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"react": "19.2.8"
|
||||||
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
},
|
},
|
||||||
|
|||||||
+12
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -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>;
|
||||||
|
}
|
||||||
+13
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
import { pgTableCreator } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
export const createTable = pgTableCreator((name) => name);
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "@ekklesia/module-calendar",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"ekklesia": {
|
||||||
|
"layer": "module"
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
export async function loadSharp() {
|
||||||
|
return await import("sharp");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user