CI / verify (pull_request) Successful in 1m2s
CI / infrastructure-smoke (pull_request) Successful in 28s
CI / migration-smoke (pull_request) Successful in 1m52s
CI / standalone-module (pull_request) Successful in 26s
CI / image-build (pull_request) Successful in 30s
CI / deploy-smoke (same-origin) (pull_request) Successful in 50s
CI / deploy-smoke (split) (pull_request) Successful in 1m42s
Makes the public module-development package set usable from a module repository outside the monorepo. Nothing is published. - Curated @ekklesia/module-sdk: an explicit author API, no contracts barrel, no identity/authorization internals, no Drizzle and no legacy ModuleDatabase type. defineModuleRuntime returns the SDK's own named interfaces so a module's emitted declarations never reach into @ekklesia/contracts. defineModuleSchema is gone; createModuleTable and defineModuleSchemaMetadata live in @ekklesia/module-database. - Drizzle: no longer a dependency of module-sdk or contracts. Core keeps the legacy Drizzle schema validator (moved to core/src/modules) and its legacy runtime support; drizzle-orm is an optional peer only. - React is a peer (plus devDependency) of @ekklesia/ui; the boundary checker allows exactly that consumer-selected peer. - @ekklesia/typescript-config is publishable; module-ui-host is private. Every public package carries "ekklesia.distribution": "module-development" and no monorepo-root prepack. - module-pack reads the Node.js range from the module's own engines.node and recognizes Ekklesia packages by name plus that marker, so registry-installed packages are incorporated, SDK-compatibility checked, and never credited as third-party. - scripts/pack-public-packages.ts (pnpm packages:pack) rebuilds, packs and inspects the nine public tarballs; scripts/test-standalone-module.sh (pnpm test:standalone-module, new CI job) installs them into a fixture module outside the repository and proves it typechecks, builds, packages, and verifies. - Public-package boundary tests and module-development docs. Co-Authored-By: Claude Opus 5.5 <[email protected]>
94 lines
3.9 KiB
TypeScript
94 lines
3.9 KiB
TypeScript
import { readdir, readFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { ekklesiaDesignTokens } from "@ekklesia/design-tokens";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
EKKLESIA_UI_STYLE_ATTRIBUTE,
|
|
EKKLESIA_UI_STYLESHEET,
|
|
installEkklesiaUiStyles,
|
|
} from "../src/index.js";
|
|
|
|
const packageDirectory = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
|
|
async function sourceFiles(): Promise<readonly string[]> {
|
|
const directory = path.join(packageDirectory, "src");
|
|
return (await readdir(directory))
|
|
.filter((entry) => entry.endsWith(".ts") || entry.endsWith(".tsx"))
|
|
.map((entry) => path.join(directory, entry));
|
|
}
|
|
|
|
/** Any literal that would be a theme decision rather than a layout one. */
|
|
const THEME_LITERAL_PATTERN =
|
|
/#[0-9a-f]{3,8}\b|\brgba?\s*\(|\bhsla?\s*\(|\boklch\s*\(|\bsystem-ui\b|\bsans-serif\b/giu;
|
|
|
|
describe("the shared elements consume design tokens, never hardcoded theme values", () => {
|
|
it("expresses every theme value in the stylesheet as a token reference", () => {
|
|
expect(EKKLESIA_UI_STYLESHEET.match(THEME_LITERAL_PATTERN)).toBeNull();
|
|
expect(EKKLESIA_UI_STYLESHEET).toContain("var(--ekklesia-");
|
|
});
|
|
|
|
it("references only registered tokens", () => {
|
|
const referenced = [...EKKLESIA_UI_STYLESHEET.matchAll(/var\((--ekklesia-[a-z0-9-]+)\)/gu)].map(
|
|
([, variable]) => String(variable),
|
|
);
|
|
const registered = new Set(ekklesiaDesignTokens.list().map((token) => token.cssVariable));
|
|
|
|
expect(referenced.length).toBeGreaterThan(0);
|
|
expect([...new Set(referenced)].filter((variable) => !registered.has(variable))).toEqual([]);
|
|
});
|
|
|
|
it("never defines a global Ekklesia theme variable of its own", () => {
|
|
// Referencing `--ekklesia-*` is the contract; *declaring* one would invent a global theme
|
|
// variable outside the registry, which is precisely what the design-token contract forbids.
|
|
expect(EKKLESIA_UI_STYLESHEET).not.toMatch(/--ekklesia-[a-z0-9-]+\s*:/u);
|
|
});
|
|
|
|
it("hardcodes no theme value in component source either", async () => {
|
|
for (const file of await sourceFiles()) {
|
|
if (path.basename(file) === "styles.ts") {
|
|
continue;
|
|
}
|
|
expect(await readFile(file, "utf8")).not.toMatch(THEME_LITERAL_PATTERN);
|
|
}
|
|
});
|
|
|
|
it("never names a Node built-in or reads the environment", async () => {
|
|
for (const file of await sourceFiles()) {
|
|
const source = await readFile(file, "utf8");
|
|
|
|
expect(source).not.toContain('"node:');
|
|
expect(source).not.toContain("process.env");
|
|
}
|
|
});
|
|
|
|
it("depends only on the design tokens and React", async () => {
|
|
const manifest = JSON.parse(
|
|
await readFile(path.join(packageDirectory, "package.json"), "utf8"),
|
|
) as {
|
|
dependencies?: Record<string, string>;
|
|
devDependencies?: Record<string, string>;
|
|
peerDependencies?: Record<string, string>;
|
|
};
|
|
|
|
// React is a peer only (ADR 0053): the consumer's React is the one in every bundle these
|
|
// elements are built into. The development dependency resolves the package's own imports for its
|
|
// builds and tests; nothing loads it from this workspace in a production Node.js process
|
|
// (`runtime-imports-are-production-dependencies` allows exactly this peer).
|
|
expect(Object.keys(manifest.dependencies ?? {})).toEqual(["@ekklesia/design-tokens"]);
|
|
expect(manifest.devDependencies?.react).toBeDefined();
|
|
expect(Object.keys(manifest.peerDependencies ?? {})).toEqual(["react"]);
|
|
});
|
|
|
|
it("installs its stylesheet into a document root exactly once", () => {
|
|
const remove = installEkklesiaUiStyles(document);
|
|
installEkklesiaUiStyles(document);
|
|
|
|
expect(document.querySelectorAll(`style[${EKKLESIA_UI_STYLE_ATTRIBUTE}]`).length).toBe(1);
|
|
|
|
remove();
|
|
remove();
|
|
expect(document.querySelectorAll(`style[${EKKLESIA_UI_STYLE_ATTRIBUTE}]`).length).toBe(0);
|
|
});
|
|
});
|