* 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>
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
quality:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: oven-sh/setup-bun@v2
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: bun install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Lint & format check
|
||||||
|
run: bun run check
|
||||||
|
|
||||||
|
- name: Type check
|
||||||
|
run: bun run check-types
|
||||||
@@ -45,7 +45,6 @@ function loadBuiltinsSync(): void {
|
|||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
const kinds = Array.from(nodeRegistry.entries(), ([k]) => k)
|
const kinds = Array.from(nodeRegistry.entries(), ([k]) => k)
|
||||||
if (typeof console !== 'undefined') {
|
if (typeof console !== 'undefined') {
|
||||||
// biome-ignore lint/suspicious/noConsole: dev-only verification log
|
|
||||||
console.info(
|
console.info(
|
||||||
`[pascal:registry] loaded ${builtinPlugin.id} v${builtinPlugin.apiVersion} (${kinds.length} kinds: ${kinds.join(', ') || '∅'})`,
|
`[pascal:registry] loaded ${builtinPlugin.id} v${builtinPlugin.apiVersion} (${kinds.length} kinds: ${kinds.join(', ') || '∅'})`,
|
||||||
)
|
)
|
||||||
@@ -74,7 +73,6 @@ export async function loadExternalPlugins(): Promise<void> {
|
|||||||
await loadPlugin(plugin)
|
await loadPlugin(plugin)
|
||||||
}
|
}
|
||||||
if (isDev() && externals.length > 0 && typeof console !== 'undefined') {
|
if (isDev() && externals.length > 0 && typeof console !== 'undefined') {
|
||||||
// biome-ignore lint/suspicious/noConsole: dev-only verification log
|
|
||||||
console.info(`[pascal:registry] + ${externals.length} discovered plugin(s)`)
|
console.info(`[pascal:registry] + ${externals.length} discovered plugin(s)`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -883,8 +883,10 @@ export const applyNodeChangesAction = (
|
|||||||
return { nodes: nextNodes, rootNodeIds: resolvedRootIds, collections: nextCollections }
|
return { nodes: nextNodes, rootNodeIds: resolvedRootIds, collections: nextCollections }
|
||||||
})
|
})
|
||||||
|
|
||||||
nodesToMarkDirty.forEach((id) => get().markDirty(id))
|
for (const id of nodesToMarkDirty) {
|
||||||
parentsToMarkDirty.forEach((id) => {
|
get().markDirty(id)
|
||||||
|
}
|
||||||
|
for (const id of parentsToMarkDirty) {
|
||||||
get().markDirty(id)
|
get().markDirty(id)
|
||||||
const parent = get().nodes[id]
|
const parent = get().nodes[id]
|
||||||
if (parent && 'children' in parent && Array.isArray(parent.children)) {
|
if (parent && 'children' in parent && Array.isArray(parent.children)) {
|
||||||
@@ -892,7 +894,7 @@ export const applyNodeChangesAction = (
|
|||||||
get().markDirty(childId as AnyNodeId)
|
get().markDirty(childId as AnyNodeId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateNodesAction = (
|
export const updateNodesAction = (
|
||||||
|
|||||||
@@ -294,7 +294,9 @@ export function buildFirstPersonColliderWorldFromRegistry(): FirstPersonCollider
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mergedGeometry = mergeGeometries(geometries, false)
|
const mergedGeometry = mergeGeometries(geometries, false)
|
||||||
geometries.forEach((geometry) => geometry.dispose())
|
for (const geometry of geometries) {
|
||||||
|
geometry.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
if (!mergedGeometry || mergedGeometry.getAttribute('position') == null) {
|
if (!mergedGeometry || mergedGeometry.getAttribute('position') == null) {
|
||||||
mergedGeometry?.dispose()
|
mergedGeometry?.dispose()
|
||||||
|
|||||||
@@ -250,8 +250,8 @@ export const CeilingTool: React.FC = () => {
|
|||||||
if (points.length === 0) {
|
if (points.length === 0) {
|
||||||
mainLineRef.current.visible = false
|
mainLineRef.current.visible = false
|
||||||
closingLineRef.current.visible = false
|
closingLineRef.current.visible = false
|
||||||
groundMainLineRef.current && (groundMainLineRef.current.visible = false)
|
if (groundMainLineRef.current) groundMainLineRef.current.visible = false
|
||||||
groundClosingLineRef.current && (groundClosingLineRef.current.visible = false)
|
if (groundClosingLineRef.current) groundClosingLineRef.current.visible = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const ceilingY = levelY + CEILING_HEIGHT
|
const ceilingY = levelY + CEILING_HEIGHT
|
||||||
|
|||||||
@@ -133,6 +133,21 @@ export default function ChimneyPanel() {
|
|||||||
}
|
}
|
||||||
}, [selectedId, node, deleteNode, setSelection])
|
}, [selectedId, node, deleteNode, setSelection])
|
||||||
|
|
||||||
|
// Match the current store node against the preset table so the
|
||||||
|
// segmented control highlights "the preset you'd land on if you
|
||||||
|
// applied X again". Compare against the store node, not the live-
|
||||||
|
// override-merged `node`, so the highlight is stable across slider
|
||||||
|
// drags. Null means the user has tweaked away from any preset; the
|
||||||
|
// segmented control will then render with no segment selected.
|
||||||
|
const activePreset = useMemo(() => detectActiveChimneyPreset(storeNode), [storeNode])
|
||||||
|
const applyPreset = useCallback(
|
||||||
|
(key: ChimneyPresetKey) => {
|
||||||
|
commitProp(chimneyPresets[key] as Partial<ChimneyNode>)
|
||||||
|
triggerSFX('sfx:item-pick')
|
||||||
|
},
|
||||||
|
[commitProp],
|
||||||
|
)
|
||||||
|
|
||||||
if (!(node && node.type === 'chimney' && selectedId)) return null
|
if (!(node && node.type === 'chimney' && selectedId)) return null
|
||||||
|
|
||||||
const scenestate = useScene.getState()
|
const scenestate = useScene.getState()
|
||||||
@@ -322,20 +337,6 @@ export default function ChimneyPanel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Match the current store node against the preset table so the
|
// Match the current store node against the preset table so the
|
||||||
// segmented control highlights "the preset you'd land on if you
|
|
||||||
// applied X again". Compare against the store node, not the live-
|
|
||||||
// override-merged `node`, so the highlight is stable across slider
|
|
||||||
// drags. Null means the user has tweaked away from any preset; the
|
|
||||||
// segmented control will then render with no segment selected.
|
|
||||||
const activePreset = useMemo(() => detectActiveChimneyPreset(storeNode), [storeNode])
|
|
||||||
const applyPreset = useCallback(
|
|
||||||
(key: ChimneyPresetKey) => {
|
|
||||||
commitProp(chimneyPresets[key] as Partial<ChimneyNode>)
|
|
||||||
triggerSFX('sfx:item-pick')
|
|
||||||
},
|
|
||||||
[commitProp],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelWrapper
|
<PanelWrapper
|
||||||
icon="/icons/roof.png"
|
icon="/icons/roof.png"
|
||||||
|
|||||||
@@ -50,9 +50,6 @@ type RenderableNode = AnyNode & {
|
|||||||
export const ParametricNodeRenderer = ({ node }: { node: AnyNode }) => {
|
export const ParametricNodeRenderer = ({ node }: { node: AnyNode }) => {
|
||||||
const ref = useRef<Group>(null!)
|
const ref = useRef<Group>(null!)
|
||||||
const n = node as RenderableNode
|
const n = node as RenderableNode
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: useNodeEvents is keyed by
|
|
||||||
// literal kind; the registry path passes a runtime kind union. Routing
|
|
||||||
// through the type cast is safer than widening the hook signature.
|
|
||||||
const handlers = useNodeEvents(node as any, node.type as any)
|
const handlers = useNodeEvents(node as any, node.type as any)
|
||||||
const liveTransform = useLiveTransforms((s) => s.get(node.id as AnyNodeId))
|
const liveTransform = useLiveTransforms((s) => s.get(node.id as AnyNodeId))
|
||||||
// Registry arrow handles (rotation gizmo, position-affecting patches)
|
// Registry arrow handles (rotation gizmo, position-affecting patches)
|
||||||
|
|||||||
Vendored
-3
@@ -78,21 +78,18 @@ interface ThreeJSXElements {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare module 'react' {
|
declare module 'react' {
|
||||||
// biome-ignore lint/style/noNamespace: Required for JSX module augmentation
|
|
||||||
namespace JSX {
|
namespace JSX {
|
||||||
interface IntrinsicElements extends ThreeJSXElements {}
|
interface IntrinsicElements extends ThreeJSXElements {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'react/jsx-runtime' {
|
declare module 'react/jsx-runtime' {
|
||||||
// biome-ignore lint/style/noNamespace: Required for JSX module augmentation
|
|
||||||
namespace JSX {
|
namespace JSX {
|
||||||
interface IntrinsicElements extends ThreeJSXElements {}
|
interface IntrinsicElements extends ThreeJSXElements {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'react/jsx-dev-runtime' {
|
declare module 'react/jsx-dev-runtime' {
|
||||||
// biome-ignore lint/style/noNamespace: Required for JSX module augmentation
|
|
||||||
namespace JSX {
|
namespace JSX {
|
||||||
interface IntrinsicElements extends ThreeJSXElements {}
|
interface IntrinsicElements extends ThreeJSXElements {}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@
|
|||||||
"dependsOn": ["^lint"]
|
"dependsOn": ["^lint"]
|
||||||
},
|
},
|
||||||
"check-types": {
|
"check-types": {
|
||||||
"dependsOn": ["^check-types"]
|
"dependsOn": ["^build", "^check-types"]
|
||||||
},
|
},
|
||||||
"dev": {
|
"dev": {
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
|
|||||||
Reference in New Issue
Block a user