* fix: resolve lint and type errors to enable CI - Remove stale biome-ignore suppressions in bootstrap.ts, r3f.d.ts, and parametric-node-renderer.tsx (rules no longer fire). - Replace forEach callbacks that return values with for...of loops in node-actions.ts and build-collider-world.ts (lint/suspicious/useIterableCallbackReturn). - Rewrite assign-in-expression guards in ceiling/tool.tsx to explicit if-statements (lint/suspicious/noAssignInExpressions). - Hoist useMemo/useCallback above early return in chimney/panel.tsx (lint/correctness/useHookAtTopLevel). - Add `^build` to turbo check-types dependsOn so packages/core dist is up-to-date before type checking — this resolves all 32 type errors which were caused by stale dist, not missing symbols. * ci: add lint and typecheck workflow on push/PR Adds a quality gate that runs `bun run check` (Biome) and `bun run check-types` (TypeScript) on every push and PR to main. Uses concurrency groups to cancel stale runs. Closes #147. --------- Co-authored-by: Pascal <open@pascal.app>
82 lines
3.0 KiB
TypeScript
82 lines
3.0 KiB
TypeScript
import {
|
|
type AnyNodeDefinition,
|
|
discoverPlugins,
|
|
loadPlugin,
|
|
nodeRegistry,
|
|
registerNode,
|
|
} from '@pascal-app/core'
|
|
import { builtinPlugin } from '@pascal-app/nodes'
|
|
|
|
// Idempotency guards: HMR can reload this module, but `registerNode`
|
|
// throws on duplicate kinds. Flags live in the module closure so they
|
|
// reset on a hard reload but survive within a session.
|
|
let builtinsLoaded = false
|
|
let externalsKickedOff = false
|
|
|
|
function isDev(): boolean {
|
|
const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process
|
|
?.env
|
|
return env?.NODE_ENV !== 'production'
|
|
}
|
|
|
|
/**
|
|
* Synchronously register every built-in node kind. Runs as a side
|
|
* effect at module import time so the registry is populated *before*
|
|
* any downstream React tree renders — the previous async kick-off
|
|
* (`void loadBuiltinNodes()`) only registered in a microtask, letting
|
|
* the first SSR / hydration pass see an empty registry. The mismatch
|
|
* surfaced as a hydration error at the `<html>` element and every
|
|
* `NodeRenderer` resolving to `null` until later renders.
|
|
*
|
|
* `discoverPlugins()` (which may hit the network for external packs)
|
|
* stays async and runs separately via `loadExternalPlugins()`.
|
|
*/
|
|
function loadBuiltinsSync(): void {
|
|
if (builtinsLoaded) return
|
|
builtinsLoaded = true
|
|
for (const def of builtinPlugin.nodes ?? []) {
|
|
// Skip kinds the registry already has. The module-closure flag
|
|
// above resets on HMR, but the registry singleton (in @pascal-app/core)
|
|
// persists — without this guard we'd throw on the first duplicate.
|
|
if (nodeRegistry.has((def as AnyNodeDefinition).kind)) continue
|
|
registerNode(def as AnyNodeDefinition)
|
|
}
|
|
|
|
if (isDev()) {
|
|
const kinds = Array.from(nodeRegistry.entries(), ([k]) => k)
|
|
if (typeof console !== 'undefined') {
|
|
console.info(
|
|
`[pascal:registry] loaded ${builtinPlugin.id} v${builtinPlugin.apiVersion} (${kinds.length} kinds: ${kinds.join(', ') || '∅'})`,
|
|
)
|
|
}
|
|
// Expose the registry on globalThis for ad-hoc dev inspection. In
|
|
// prod the registry is reachable through @pascal-app/core's
|
|
// exports only.
|
|
if (typeof globalThis !== 'undefined') {
|
|
;(globalThis as { __pascalNodeRegistry?: typeof nodeRegistry }).__pascalNodeRegistry =
|
|
nodeRegistry
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Phase 6 plugin discovery hook — runs once, asynchronously, after the
|
|
* synchronous builtins are already registered. Apps that ship external
|
|
* node packs override the discovery via `setPluginDiscovery(...)`
|
|
* before this module loads. See `wiki/architecture/plugin-authoring.md`.
|
|
*/
|
|
export async function loadExternalPlugins(): Promise<void> {
|
|
if (externalsKickedOff) return
|
|
externalsKickedOff = true
|
|
const externals = await discoverPlugins()
|
|
for (const plugin of externals) {
|
|
await loadPlugin(plugin)
|
|
}
|
|
if (isDev() && externals.length > 0 && typeof console !== 'undefined') {
|
|
console.info(`[pascal:registry] + ${externals.length} discovered plugin(s)`)
|
|
}
|
|
}
|
|
|
|
loadBuiltinsSync()
|
|
void loadExternalPlugins()
|