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
2 changes: 0 additions & 2 deletions .eslintignore

This file was deleted.

6 changes: 3 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 22
cache: npm
- name: Install dependencies
# Includes @rollup/rollup-linux-x64-gnu, which is an optional dependency required for Vite.
run: npm install @rollup/rollup-linux-x64-gnu --save-dev
- name: Build
run: npm run build:site
run: npx vex build site
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
Expand Down
15 changes: 8 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup node
uses: actions/setup-node@v4
with:
node-version: 22.6.0
cache: npm
- name: install
run: npm ci
- name: lint
run: npm run lint
- name: check format
run: npm run formatcheck
- name: typecheck
run: npm run typecheck
- name: fix
run: npx vex fix --check
- name: test
run: npm run test:ci
run: npx vex test --ci
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ node_modules
__diff_output__
coverage
__tmp_image_snapshots__
.claude
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
legacy-peer-deps=true
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.xml
*.musicxml
dist
11 changes: 0 additions & 11 deletions .vscode/launch.json

This file was deleted.

16 changes: 0 additions & 16 deletions .vscode/settings.json

This file was deleted.

7 changes: 0 additions & 7 deletions .vscode/vexml.code-snippets

This file was deleted.

7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Use the `vex` or command for development. If it's not available, use `npx vex` instead.

After making changes, format, lint, typecheck, and run tests in the packages that were affected.
`vex fix`
`vex test --local`

**IMPORTANT** When developing the `vexml` library, create tests. Encourage the user to verify the test correctness.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@./AGENTS.md
13 changes: 5 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,14 @@ We want minimal friction in the contribution process, so standalone improvements
Before submitting your contribution, ensure the following checks pass:

```sh
# Run ESLint
npm run lint
# Format, lint, and typecheck (writes fixes)
npx vex fix

# Check code formatting
npm run formatcheck

# Run type checking
npm run typecheck
# Or check without fixing (what CI runs)
npx vex fix --check

# Run tests (requires Docker)
npm run test
npx vex test
```

All of these checks are run automatically in CI via GitHub Actions and must pass before your PR can be merged.
Expand Down
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ WORKDIR /vexml
# Install dependencies.
COPY package.json .
COPY package-lock.json .
RUN npm install
COPY .npmrc .
RUN npm ci

# Copy config.
COPY jest.config.js .
Expand All @@ -27,4 +28,4 @@ COPY src src
COPY tests tests

# Run the test by default.
CMD [ "npm", "run", "jest" ]
CMD ["npx", "jest"]
65 changes: 34 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ const formatter = new vexml.DefaultFormatter({ config });

### Prerequisites

You need [Node.js](https://nodejs.org) `>= 22.6` to run the `vex` CLI (it relies on `--experimental-strip-types` to execute TypeScript directly, which was added in [Node.js 22.6.0](https://nodejs.org/en/blog/release/v22.6.0)).

You need [docker](https://docs.docker.com/engine/install) to run the integration tests.

### Installing
Expand All @@ -187,12 +189,29 @@ Before you run any commands, install the dependencies.
npm install
```

### The `vex` CLI

All dev tasks run through the `vex` CLI defined under [`cli/`](cli). After `npm install`, you have two ways to invoke it:

- **`npx vex <command>`** — works out of the box, no extra setup.
- **`vex <command>`** — shorter, but requires a one-time install. Run `npm link` from the repo root to add `vex` to your PATH; run `npm unlink` from the repo root to remove it.

```sh
npx vex --help
# or, after `npm link`:
vex --help
```

The rest of this README uses `npx vex` for the examples; substitute `vex` if you've installed it globally.

The CLI requires Node.js >= 22.6 (it uses `--experimental-strip-types` to run TypeScript directly).

### Running the Dev Server

In order to run a dev server that hot reloads `vexml` changes, run:

```sh
npm run dev
npx vex dev
```

You should be able to "save" MusicXML documents in localstorage using the dev app, which will cause the documents to survive refreshing the page.
Expand All @@ -202,7 +221,7 @@ You should be able to "save" MusicXML documents in localstorage using the dev ap
In order to run tests on x86 architecture, run:

```sh
npm run test
npx vex test
```

If you're running a machine using ARM architecture (such as an M series mac), try setting the default platform before running the command (or set it in your shell profile):
Expand All @@ -211,40 +230,24 @@ If you're running a machine using ARM architecture (such as an M series mac), tr
export DOCKER_DEFAULT_PLATFORM=linux/amd64
```

These commands are just an alias for `jest`, so you use all the [jest CLI options](https://jestjs.io/docs/cli). For example, to run in watch mode:

```
npm run test -- --watchAll
```

To bypass Docker, run:
Extra arguments are forwarded to jest, so you can use all the [jest CLI options](https://jestjs.io/docs/cli). For example, to run in watch mode:

```
npm run jest
```sh
npx vex test --watchAll
```

This will cause snapshots to be saved to `tests/integration/__tmp_image_snapshots__`, which is ignored by git. **It is important that you run it for the first time on a branch without any changes.** Doing this on a dirty branch could cause you to have an incorrect snapshot, which may cause problems when developing.
To skip Docker and run jest directly against your local environment, use `--local` (or `-l`). This is faster, but image snapshots will likely diff against the canonical Docker-rendered ones:

If you suspect issues with the tmp snapshots, run the following command to retake the snapshots (which is scripted to do this at origin/master):

```
npm run resnap
```sh
npx vex test --local
```

### Debugging Tests
If you suspect issues with the tmp snapshots, run the following command to retake the snapshots from `origin/master`:

To run a debugger, run:

```
npm run debug
```sh
npx vex resnap
```

If you're using VSCode, open the debugging tool and launch `Attach to Process`. You can set breakpoints in VSCode or insert `debugger` statements to cause execution to pause.

If you're not using VSCode, open Chrome and visit chrome://inspect. You should see a virtual device that starts with `./node_modules/.bin/jest` with an "inspect" button. Clicking this will allow you to use the Chrome debugger.

If you're still having issues, check the jest [docs](https://jestjs.io/docs/troubleshooting) or file an issue.

### Snapshots

This library uses [americanexpress/jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot) for image-based snapshot tests.
Expand All @@ -255,12 +258,12 @@ You can see diff images in the `__diff_output__` directory (nested under `__imag

#### Updating Snapshots

Rendering varies by platform, so it is important you run tests using the `npm run test*` commands, since that runs tests in a consistent environment (via Docker). This is also the environment that CI will use.
Rendering varies by platform, so it is important you run tests using `npx vex test`, since that runs tests in a consistent environment (via Docker). This is also the environment that CI will use.

When you want to update all snapshots, rerun the test command with the `--updateSnapshot`.
When you want to update all snapshots, rerun the test command with `--updateSnapshot`.

```sh
npm run test -- --updateSnapshot
npx vex test --updateSnapshot
```

If you want to only update a single snapshot from specific test, you can use [`--testNamePattern`](https://jestjs.io/docs/cli#--testnamepatternregex).
Expand All @@ -274,7 +277,7 @@ When removing a test, it is important to remove the corresponding snapshot. Ther
You can publish a `vexml` version by running the release script:

```sh
npm run release [alpha|beta|rc|patch|minor|major]
npx vex release [patch|minor|major]
```

It should create the git tags needed to create a release on GitHub.
Expand Down
2 changes: 2 additions & 0 deletions bin/vex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env -S node --experimental-strip-types --no-warnings
import '../cli/index.ts';
40 changes: 40 additions & 0 deletions cli/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { rmSync } from 'node:fs';
import { run } from './util.ts';

const PROJECT = 'tsconfig.package.json';

async function buildLib(): Promise<void> {
rmSync('dist', { recursive: true, force: true });
buildTypes();
buildCjs();
buildEsm();
}

async function buildSite(): Promise<void> {
run('npx', ['vite', '--config', 'site/vite.config.js', 'build']);
}

async function buildImage(): Promise<void> {
run('docker', ['build', '.', '--tag', 'vexml:latest']);
}

function buildTypes(): void {
run('npx', ['tsc', '--project', PROJECT, '--outDir', 'dist/@types', '--emitDeclarationOnly']);
run('npx', ['tsc-alias', '-p', PROJECT, '--outDir', 'dist/@types']);
}

function buildCjs(): void {
run('npx', ['tsc', '--project', PROJECT, '--outDir', 'dist/cjs', '--module', 'commonjs']);
run('npx', ['tsc-alias', '-p', PROJECT, '--outDir', 'dist/cjs']);
}

function buildEsm(): void {
run('npx', ['tsc', '--project', PROJECT, '--outDir', 'dist/esm', '--module', 'esnext']);
run('npx', ['tsc-alias', '-p', PROJECT, '--outDir', 'dist/esm']);
}

export const build = {
lib: buildLib,
site: buildSite,
image: buildImage,
};
5 changes: 5 additions & 0 deletions cli/dev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { run } from './util.ts';

export async function dev(): Promise<void> {
run('npx', ['vite', '--config', 'site/vite.config.js']);
}
55 changes: 55 additions & 0 deletions cli/fix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { spawnSync } from 'node:child_process';
import chalk from 'chalk';

export async function fix(opts: { check: boolean }): Promise<void> {
const failures = new Array<string>();

if (!format(opts.check)) {
failures.push('format');
}
if (!lint(opts.check)) {
failures.push('lint');
}
if (!typecheck()) {
failures.push('typecheck');
}

if (failures.length > 0) {
throw new Error(`fix failed: ${failures.join(', ')}`);
}
}

function format(check: boolean): boolean {
const args = ['prettier', '.', '--loglevel=warn', check ? '--check' : '--write'];
const ok = exec('npx', args);
console.log(`format: ${ok ? chalk.green('success') : chalk.red('failed')}`);
return ok;
}

function lint(check: boolean): boolean {
const args = ['eslint', '.'];
if (!check) {
args.push('--fix');
}
const ok = exec('npx', args);
console.log(`lint: ${ok ? chalk.green('success') : chalk.red('failed')}`);
return ok;
}

function typecheck(): boolean {
const ok = exec('npx', ['tsc', '--noEmit']);
console.log(`typecheck: ${ok ? chalk.green('success') : chalk.red('failed')}`);
return ok;
}

function exec(command: string, args: string[]): boolean {
console.log(chalk.cyan(`$ ${[command, ...args].join(' ')}`));
const result = spawnSync(command, args, { stdio: 'inherit' });
if (result.error) {
throw result.error;
}
if (result.signal) {
throw new Error(`${command} terminated with signal ${result.signal}`);
}
return result.status === 0;
}
Loading
Loading