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
43 changes: 43 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Line endings are part of this repository's content, not a local preference.
#
# Windows sets `core.autocrlf=true` by default, and the GitHub Windows runner
# inherits it: without this file every text file is checked out with CRLF there
# while ubuntu/macOS get LF. Two concrete breakages that causes here:
#
# * `.nvmrc` arrives as "24\r\n". `actions/setup-node` tolerates it, but the
# manifest guard in tests/manifests/runtime-versions.test.ts asserts the file
# is exactly one clean line — so CI failed on Windows only.
# * `install.sh` is POSIX sh. A CRLF copy breaks at the shebang (`\r` becomes
# part of the interpreter path) and inside every quoted string.
#
# `text=auto eol=lf` normalizes to LF in the index *and* in the working tree on
# all platforms, so the checked-out bytes match the committed bytes everywhere.
* text=auto eol=lf

# Explicit shell/manifest declarations. `text=auto` already infers these, but
# these two are the files whose breakage is silent and platform-specific, so the
# rule is stated rather than inferred.
install.sh text eol=lf
.nvmrc text eol=lf

# If a Windows helper is ever added it must keep CRLF: cmd.exe mis-parses a
# bare-LF batch file (notably `goto` labels and multi-line blocks).
*.cmd text eol=crlf
*.bat text eol=crlf
*.ps1 text eol=crlf

# Binary assets must never be line-ending converted. `text=auto` would detect
# these correctly via its NUL-byte heuristic, but a heuristic misfire silently
# corrupts a committed asset, so the extensions used in this repository are
# declared outright.
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.webp binary
*.ico binary
*.mp4 binary
*.woff binary
*.woff2 binary
*.ttf binary
*.wasm binary
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ body:
id: environment
attributes:
label: Environment
placeholder: macOS 15, Node 20.19, Codex or Claude version
placeholder: macOS 15, Node 24.20, Codex or Claude version
validations:
required: true
- type: checkboxes
Expand Down
51 changes: 45 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,60 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/checkout@v7

# WORKAROUND: https://github.com/actions/setup-node/issues/531
#
# setup-node runs twice on purpose. Corepack writes its shims next to the
# *currently active* node (`dirname(process.execPath)`), and on Windows the
# generated `pnpm.cmd` is early-bound: it invokes the `node.exe` sitting in
# its own directory. A Linux shim goes through `#!/usr/bin/env node` and so
# late-binds to PATH, which is why only the Windows job was affected.
#
# With a single setup-node, Corepack has to run before it (`cache: pnpm`
# shells out to `pnpm store path`, so pnpm must already exist) — but that
# means the shims land beside the runner's *preinstalled* Node 22, and
# setup-node only prepends the toolcache to PATH without removing that
# directory. Windows then ran the whole build on Node 22 while `.nvmrc`
# asked for 24, surfacing as `Unsupported engine: wanted >=24.0.0`.
# Promoting to v7 did not fix it; the reports that it does concern Yarn
# with a bare `corepack enable`.
#
# Splitting the action resolves the chicken/egg in the only order where
# both constraints hold:
# 1. install Node 24 first, with no cache input, so nothing needs pnpm;
# 2. enable Corepack — the shims now bind to Node 24;
# 3. run setup-node again purely for `cache: pnpm`, which can now
# resolve the store because pnpm is on PATH.
#
# `enable pnpm` stays scoped. Bare `corepack enable` also shims npm, and
# that shim refuses to run in a repository whose `packageManager` says
# pnpm — breaking the `npm install --global` inside install.sh that
# test:distribution runs. The pnpm version itself comes from
# `packageManager`, keeping package.json the single source.
- name: Set up Node
uses: actions/setup-node@v7
with:
version: 10.29.3
- uses: actions/setup-node@v4
node-version-file: .nvmrc
- name: Enable Corepack
shell: bash
run: |
corepack enable pnpm
corepack install
node --version
pnpm --version
- name: Restore pnpm cache
uses: actions/setup-node@v7
with:
node-version: 20.19.5
node-version-file: .nvmrc
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm verify
- run: pnpm test:distribution
- run: pnpm test:source-install
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm test:e2e
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
if: failure()
with:
name: playwright-${{ matrix.os }}
Expand Down
43 changes: 38 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,48 @@ jobs:
runs-on: ubuntu-latest
environment: npm
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4

# WORKAROUND: https://github.com/actions/setup-node/issues/531
#
# setup-node runs twice on purpose, mirroring ci.yml so the two setups
# cannot drift. Corepack writes its shims next to the *currently active*
# node (`dirname(process.execPath)`), and on Windows the generated
# `pnpm.cmd` is early-bound to the `node.exe` in its own directory, so a
# Corepack-before-setup-node order pins the build to the runner's
# preinstalled Node instead of `.nvmrc`. This job only runs on ubuntu,
# where shims late-bind through `#!/usr/bin/env node` — the split is kept
# anyway so release and CI exercise an identical toolchain setup.
#
# Order: (1) install Node from .nvmrc with no cache input, so nothing needs
# pnpm yet; (2) enable Corepack, binding the shims to that Node; (3) run
# setup-node again for `cache: pnpm`, which can now resolve the store.
#
# `enable pnpm` stays scoped. Bare `corepack enable` also shims npm, and
# that shim refuses to run in a repository whose `packageManager` says
# pnpm — breaking the `npm install --global` inside install.sh that
# test:distribution runs. The pnpm version itself comes from
# `packageManager`, keeping package.json the single source.
#
# `registry-url` belongs on the *second* call: it writes the .npmrc that
# `npm publish` authenticates with, so it must be the setup that runs last.
- name: Set up Node
uses: actions/setup-node@v7
with:
version: 10.29.3
- uses: actions/setup-node@v4
node-version-file: .nvmrc
- name: Enable Corepack
shell: bash
run: |
corepack enable pnpm
corepack install
node --version
pnpm --version
- name: Restore pnpm cache
uses: actions/setup-node@v7
with:
node-version: 20.19.5
node-version-file: .nvmrc
cache: pnpm
registry-url: https://registry.npmjs.org
- run: pnpm install --frozen-lockfile
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
10 changes: 6 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ Thanks for helping improve the AI-generated pixel asset workflow. Contributions

Requirements:

- Node.js 20.9 or newer
- pnpm 10.29.3 through Corepack
- macOS, Linux, or Windows with the native dependencies supported by Sharp
- Node.js 24 or newer (`.nvmrc` pins the major; run `nvm use` to match)
- pnpm 10.29.3 through Corepack (the version comes from `packageManager` in the root `package.json`)
- macOS, Linux, or Windows — image codecs are WebAssembly, so no native build toolchain is needed

```bash
corepack enable
corepack enable pnpm
pnpm install
pnpm verify
```

Scope Corepack to `pnpm`. Bare `corepack enable` also installs an `npm` shim, and that shim refuses to run inside this repository because `packageManager` is pnpm — which breaks `pnpm test:distribution`, since it installs the packed tarball with the real `npm`.

Install Chromium once before browser tests:

```bash
Expand Down
6 changes: 4 additions & 2 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Frame 1은 160ms, Frame 4는 100ms로 설정해줘.

## 로컬 실행

**Node.js 20.9 이상**이 필요합니다. 별도 웹 서비스에 접속하는 방식이 아니라 내 컴퓨터에서 편집기를 실행합니다.
**Node.js 24 이상**이 필요합니다. 별도 웹 서비스에 접속하는 방식이 아니라 내 컴퓨터에서 편집기를 실행합니다.

```bash
npm install --global editable-pixel
Expand All @@ -82,10 +82,12 @@ npm 설치 폴더에는 `&`를 사용하지 마세요. npm이 생성하는 Windo
<details>
<summary>소스에서 빌드하기 (pnpm 10.29.3 필요)</summary>

Corepack은 `pnpm`으로 범위를 좁혀 활성화합니다. 인자 없는 `corepack enable`은 `npm` 심까지 설치하는데, 이 심은 `packageManager`가 pnpm인 저장소 안에서 실행을 거부합니다.

```bash
git clone https://github.com/NariP/editable-pixel.git
cd editable-pixel
corepack enable
corepack enable pnpm
pnpm install --frozen-lockfile
pnpm build
node packages/pixel-cli/dist/cli.js open
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Set Frame 1 to 160 ms and Frame 4 to 100 ms.

## Run locally

Requires **Node.js 20.9+**. This starts a local editor, not a hosted web service.
Requires **Node.js 24+**. This starts a local editor, not a hosted web service.

```bash
npm install --global editable-pixel
Expand All @@ -78,10 +78,12 @@ Use an npm installation directory without `&`; npm's Windows `.cmd` wrapper cann
<details>
<summary>Build from source (requires pnpm 10.29.3)</summary>

Corepack is scoped to `pnpm` on purpose: bare `corepack enable` also shims `npm`, and that shim refuses to run inside a repository whose `packageManager` is pnpm.

```bash
git clone https://github.com/NariP/editable-pixel.git
cd editable-pixel
corepack enable
corepack enable pnpm
pnpm install --frozen-lockfile
pnpm build
node packages/pixel-cli/dist/cli.js open
Expand Down
6 changes: 3 additions & 3 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@base-ui/react": "1.7.0",
"@base-ui/react": "1.8.0",
"@dnd-kit/core": "6.3.1",
"@dnd-kit/sortable": "10.0.0",
"@dnd-kit/utilities": "3.2.2",
Expand All @@ -31,9 +31,9 @@
},
"devDependencies": {
"@tailwindcss/vite": "4.3.3",
"@testing-library/react": "16.3.2",
"@testing-library/react": "16.3.3",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
"@types/react-dom": "19.2.7",
"@vitejs/plugin-react": "4.7.0",
"jsdom": "26.1.0",
"tailwindcss": "4.3.3"
Expand Down
6 changes: 4 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ Editable Pixel stores complete work in a Pixel Project and uses Pixel Document J
The npm package is not published yet. For the current pre-release, build from this repository:

```bash
corepack enable
corepack enable pnpm
pnpm install --frozen-lockfile
pnpm build
node packages/pixel-cli/dist/cli.js --help
```

Node.js 20.9 or newer is required.
Corepack is scoped to `pnpm`: bare `corepack enable` also shims `npm`, and that shim refuses to run inside a repository whose `packageManager` is pnpm.

Node.js 24 or newer is required. The repository pins the development runtime in `.nvmrc`; with nvm installed, `nvm use` picks it up.

In the examples below, replace `editable-pixel` with `node packages/pixel-cli/dist/cli.js` when running from source. The [README](../README.md#run-locally) also documents the prepared npm and install-script workflows for after publication.

Expand Down
4 changes: 4 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export default tseslint.config(
"**/dist/**",
"**/coverage/**",
"**/node_modules/**",
// Untracked local scratch output (gitignored, absent in CI). It carries its
// own package.json, lockfile and eslint 9 install, so linting it from here
// mixes two ESLint majors and crashes in scope-manager.
"artifacts/**",
"packages/pixel-cli/web/**",
"**/playwright-report/**",
"**/test-results/**"
Expand Down
12 changes: 8 additions & 4 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,23 @@ while [ "$#" -gt 0 ]; do
done

command -v node >/dev/null 2>&1 || {
echo "Editable Pixel requires Node.js 20.9 or newer: https://nodejs.org" >&2
echo "Editable Pixel requires Node.js 24 or newer: https://nodejs.org" >&2
exit 1
}
command -v npm >/dev/null 2>&1 || {
echo "Editable Pixel requires npm, which is included with Node.js." >&2
exit 1
}

# Mirrors the `engines.node` floor in packages/pixel-cli/package.json (>=24.0.0).
# The floor is a whole major, so only the major is compared: a minor term here
# would be dead code that silently rots the next time the floor moves.
# tests/manifests/runtime-versions.test.ts keeps the two in step.
node -e '
const [major, minor] = process.versions.node.split(".").map(Number);
if (major < 20 || (major === 20 && minor < 9)) process.exit(1);
const [major] = process.versions.node.split(".").map(Number);
if (major < 24) process.exit(1);
' || {
echo "Editable Pixel requires Node.js 20.9 or newer; found $(node --version)." >&2
echo "Editable Pixel requires Node.js 24 or newer; found $(node --version)." >&2
exit 1
}

Expand Down
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"packageManager": "pnpm@10.29.3",
"engines": {
"node": ">=20.9.0"
"node": ">=24.0.0"
},
"scripts": {
"build": "pnpm -r --if-present build",
Expand All @@ -20,13 +20,13 @@
"verify": "pnpm build && pnpm lint && pnpm typecheck && pnpm test"
},
"devDependencies": {
"@eslint/js": "9.39.5",
"@playwright/test": "1.62.1",
"@types/node": "24.10.4",
"eslint": "9.39.5",
"globals": "17.11.0",
"@eslint/js": "10.0.1",
"@playwright/test": "1.63.0",
"@types/node": "26.4.1",
"eslint": "10.10.0",
"globals": "17.12.0",
"typescript": "5.9.3",
"typescript-eslint": "8.67.0",
"typescript-eslint": "8.69.0",
"vite": "6.4.3",
"vitest": "3.2.7"
}
Expand Down
6 changes: 3 additions & 3 deletions packages/pixel-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"keywords": ["pixel-art", "sprite", "mcp", "codex", "claude", "local-first"],
"engines": {
"node": ">=20.9.0"
"node": ">=24.0.0"
},
"bin": {
"editable-pixel": "./dist/cli.js",
Expand All @@ -41,9 +41,9 @@
"busboy": "1.6.0",
"commander": "15.0.0",
"cross-spawn": "7.0.6",
"open": "11.0.1",
"open": "11.0.2",
"ws": "8.21.3",
"zod": "4.4.3"
"zod": "4.5.4"
},
"devDependencies": {
"@editable-pixel/converter": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion packages/pixel-cli/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default defineConfig({
},
format: ["esm"],
platform: "node",
target: "node20",
target: "node24",
bundle: true,
define: { __EDITABLE_PIXEL_VERSION__: JSON.stringify(version) },
splitting: false,
Expand Down
2 changes: 1 addition & 1 deletion packages/pixel-mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@editable-pixel/core": "workspace:*",
"@editable-pixel/server": "workspace:*",
"@modelcontextprotocol/server": "2.0.0",
"zod": "4.4.3"
"zod": "4.5.4"
},
"devDependencies": {
"@editable-pixel/document": "workspace:*",
Expand Down
Loading