Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ updates:
labels:
- dependencies
groups:
# Lockstep families: these must move together. A minor bump of `vitest`
# without `@vitest/browser` (or `typescript-eslint` without `eslint`)
# produces a PR that fails CI on version skew. Grouped across patch AND
# minor; listed before patch-only so they win the match.
vitest:
patterns:
- 'vitest'
- '@vitest/*'
eslint:
patterns:
- 'eslint'
- '@eslint/*'
- 'typescript-eslint'
- 'eslint-plugin-*'
- 'eslint-config-*'
patch-only:
update-types:
- patch
Expand Down
69 changes: 69 additions & 0 deletions .github/workflows/_ci-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ jobs:
- name: Lint
run: pnpm lint:all

# Prettier is enforced by the pre-push hook (verify:release), but hooks can
# be bypassed (--no-verify) and merged PRs were never format-gated, so
# formatting drifted across the v0.13 PRs and blocked the v0.13.0 tag push.
# This is the CI backstop. Auto-fixable: a failure means `pnpm format`.
- name: Format check
run: pnpm format:check

unit-tests:
name: Unit Tests (Coverage)
needs: [changes]
Expand Down Expand Up @@ -387,11 +394,73 @@ jobs:
- name: Extension package dry runs
run: pnpm --filter "@codexo/exojs-particles" --filter "@codexo/exojs-tilemap" --filter "@codexo/exojs-tiled" pack --dry-run

# publint validates the published package.json itself (exports map, files,
# types resolution, module/main coherence) — the layer attw does NOT cover
# (attw checks .d.ts resolution; publint checks the manifest contract).
# Pinned + via dlx (no permanent dependency), matching the attw pattern.
# Runs against the built dist of each package (core + extensions built above).
- name: Validate published package manifests (publint)
run: pnpm verify:publint

# Verify commit-tracked API docs match a fresh generation from source.
# (dry-run check that leaves the working tree unchanged.)
- name: Check API doc sync
run: pnpm docs:api:check

release-dry-run:
name: Release dry run
# PR-only shift-left of the build-once release `prepare` (pack -> attw ->
# external consumers). Every tooling bug that broke the v0.13.0 release —
# tilemap missing from the pack set, attw output-format drift — would have
# failed HERE on a cheap PR instead of mid-flight on an irreversible tag.
# `--skip-zip` avoids the site build + 220 MB archive. It does NOT run the
# real publish (provenance / dist-tag need real OIDC), so the
# repository-field/provenance prerequisite is covered by verify:release-matrix
# instead. The tag/release context skips this — release.yml runs the real
# prepare there. Path-filtered to release-relevant changes so most PRs skip it.
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Filter release-relevant changes
id: filter
uses: dorny/paths-filter@v3
with:
filters: |
release:
- 'scripts/release/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'packages/*/package.json'
- '.github/workflows/release.yml'

- name: Setup pnpm
if: steps.filter.outputs.release == 'true'
uses: pnpm/action-setup@v6
with:
version: ${{ inputs.pnpm-version }}
run_install: false

- name: Setup Node
if: steps.filter.outputs.release == 'true'
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}
check-latest: true
cache: pnpm
cache-dependency-path: pnpm-lock.yaml

- name: Install dependencies
if: steps.filter.outputs.release == 'true'
run: pnpm install --frozen-lockfile --ignore-scripts

- name: Release prepare (build-once, no publish, no full-zip)
if: steps.filter.outputs.release == 'true'
run: pnpm release:prepare --build --skip-zip

site-build:
name: Site build
# Runs whenever site/examples/packages changed. Still ordered after
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/workflow-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v6

# Pinned, not :latest — a tool whose output gates CI must not silently
# change behaviour on a new upstream release (the same lesson as attw@latest).
- name: Run actionlint
run: docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:latest
run: docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:1.7.12
27 changes: 24 additions & 3 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,13 @@ export default defineConfig([

// Build-time constants intentionally follow ecosystem-style ALL_CAPS names.
{
files: ['src/build-constants.d.ts', 'src/typings.d.ts', 'packages/exojs-particles/src/typings.d.ts', 'packages/exojs-tilemap/src/typings.d.ts', 'packages/exojs-tiled/src/typings.d.ts'],
files: [
'src/build-constants.d.ts',
'src/typings.d.ts',
'packages/exojs-particles/src/typings.d.ts',
'packages/exojs-tilemap/src/typings.d.ts',
'packages/exojs-tiled/src/typings.d.ts',
],
rules: {
'@typescript-eslint/naming-convention': 'off',
},
Expand Down Expand Up @@ -825,7 +831,14 @@ export default defineConfig([

// Extension tilemap core — geometry and data-path relaxations.
{
files: ['packages/exojs-tilemap/src/chunkGeometry.ts', 'packages/exojs-tilemap/src/TileChunk.ts', 'packages/exojs-tilemap/src/TileSet.ts', 'packages/exojs-tilemap/src/TileMapView.ts', 'packages/exojs-tilemap/src/TileLayer.ts', 'packages/exojs-tilemap/src/tilemapExtension.ts'],
files: [
'packages/exojs-tilemap/src/chunkGeometry.ts',
'packages/exojs-tilemap/src/TileChunk.ts',
'packages/exojs-tilemap/src/TileSet.ts',
'packages/exojs-tilemap/src/TileMapView.ts',
'packages/exojs-tilemap/src/TileLayer.ts',
'packages/exojs-tilemap/src/tilemapExtension.ts',
],
rules: {
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-unnecessary-condition': 'off',
Expand All @@ -846,7 +859,11 @@ export default defineConfig([

// Extension particle modules with intentionally empty lifecycle stubs.
{
files: ['packages/exojs-particles/src/modules/DeathModule.ts', 'packages/exojs-particles/src/modules/SpawnModule.ts', 'packages/exojs-particles/src/modules/UpdateModule.ts'],
files: [
'packages/exojs-particles/src/modules/DeathModule.ts',
'packages/exojs-particles/src/modules/SpawnModule.ts',
'packages/exojs-particles/src/modules/UpdateModule.ts',
],
rules: {
'@typescript-eslint/no-empty-function': 'off',
},
Expand Down Expand Up @@ -934,6 +951,10 @@ export default defineConfig([
],
'no-console': 'off',
'max-lines': 'off',
// Match the src/packages convention: T[] for simple types, Array<T> for
// complex element types (unions, inline object literals). The base config
// forces always-[] otherwise, which reads poorly for Array<{ ... }>.
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
},
},

Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"build:watch": "tsx node_modules/rollup/dist/bin/rollup -cw --environment EXOJS_ENV:development",
"verify:exports": "tsx ./scripts/verify-exports.ts",
"verify:package-policy": "tsx ./scripts/verify-package-policy.ts",
"verify:publint": "pnpm dlx publint@0.3.21 --strict . && pnpm --filter \"@codexo/exojs-particles\" --filter \"@codexo/exojs-tilemap\" --filter \"@codexo/exojs-tiled\" exec pnpm dlx publint@0.3.21 --strict .",
"verify:package": "pnpm build && pnpm verify:exports && pnpm pack",
"verify:lockstep": "tsx ./scripts/verify-lockstep-versions.ts",
"verify:release-matrix": "tsx ./scripts/verify-release-matrix.ts",
Expand Down Expand Up @@ -152,7 +153,7 @@
"@vitest/browser-playwright": "^4.1.7",
"@vitest/coverage-istanbul": "^4.1.7",
"@webgpu/types": "^0.1.69",
"eslint": "^10.4.1",
"eslint": "~10.4.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-security": "^4.0.0",
"eslint-plugin-simple-import-sort": "^13.0.0",
Expand All @@ -170,8 +171,8 @@
"rollup-plugin-string": "^3.0.0",
"tslib": "^2.8.1",
"tsx": "^4.21.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.60.0",
"typescript": "~6.0.3",
"typescript-eslint": "~8.60.0",
"vitest": "^4.1.7"
}
}
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,11 @@ allowBuilds:
'@parcel/watcher': true
esbuild: true
sharp: true

# Supply-chain defense: refuse to install any package version younger than this
# (minutes → 3 days). The real npm compromises of recent memory published a
# malicious version and yanked it within hours; this quarantines brand-new
# versions on every install — local AND CI — complementing the Dependabot
# cooldown (which only governs Dependabot's own PRs, not manual installs).
# `--frozen-lockfile` installs of already-pinned versions are unaffected.
minimumReleaseAge: 4320
Loading
Loading