Files
editor/apps/editor/lib/bootstrap.ts
T
8f2e5678e8 ci: add lint and typecheck workflow (closes #147) (#369)
* 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>
2026-06-06 09:05:16 -04:00

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()