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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
79 changes: 49 additions & 30 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
# GitHub Copilot Instructions for Markdown-Transform

This repository is **Accord Project markdown-transform** — a JavaScript npm-workspaces monorepo for parsing and transforming Markdown, CommonMark, CiceroMark, and TemplateMark.
This repository is **Accord Project markdown-transform** — a TypeScript npm-workspaces monorepo for parsing and transforming Markdown, CommonMark, CiceroMark, and TemplateMark.

## Project context

- Runtime: Node.js `>=18`
- Runtime: Node.js `>=22`
- Package manager: `npm` (workspace root + package-level scripts)
- Languages: JavaScript (CommonJS)
- Linting: ESLint (`eslint:recommended` + strict local rules)
- Testing: Jest (most packages), Mocha (notably `packages/markdown-transform`)
- CI: GitHub Actions matrix on Ubuntu, macOS, and Windows
- Language: **TypeScript** (target `ES2020`, `module: commonjs`). Source lives in `packages/*/src/`; compiled `.js` + `.d.ts` are emitted to `packages/*/lib/`.
- Build: `tsc` per package (config extends `tsconfig.base.json`).
- Linting: ESLint with `@typescript-eslint` (4-space indent, single quotes, semicolons).
- Unit testing: **Jest 29 + ts-jest** across every package. The legacy mocha+chai suites were removed during the TS migration.
- Browser E2E: **Playwright** under `e2e/` exercises the UMD bundles in headless Chromium.
- Bundling: `webpack 5` produces UMD bundles for `markdown-html`, `markdown-template`, `markdown-transform` (the three user-facing entry points). The other packages are CommonJS library deps consumed via bundlers.
- CI: GitHub Actions matrix on Ubuntu, macOS, and Windows for unit tests; Ubuntu-only for Playwright e2e.

## Repository layout

- Root workspace packages live under `packages/`
- Primary packages include:
- `packages/` — eight publishable packages:
- `markdown-common`
- `markdown-cicero`
- `markdown-template`
- `markdown-html`
- `markdown-template` *(also UMD)*
- `markdown-html` *(also UMD)*
- `markdown-it-cicero`
- `markdown-it-template`
- `markdown-cli`
- `markdown-transform`
- Utility scripts are under `scripts/`
- `markdown-transform` *(umbrella, also UMD)*
- `e2e/` — browser end-to-end tests (Playwright). Not published.
- `scripts/` — repo-level utilities (model generation, version bumping, coverage aggregation).
- `tsconfig.base.json` — shared compiler options inherited by every package.

Concerto models for CommonMark/CiceroMark/TemplateMark are downloaded by `scripts/external/getExternalModels.js` (run via `npm run models:get` / triggered as `postinstall`) and emitted as TS into `packages/markdown-common/src/externalModels/`. Treat those files as generated.

## Non-negotiable contribution requirements

Expand All @@ -35,31 +41,32 @@ This repository is **Accord Project markdown-transform** — a JavaScript npm-wo
- Examples seen in this repo:
- `fix: update broken CI badge to use GitHub Actions workflow URL`
- `chore(deps): update package dependencies`
- `chore(actions): publish v0.16.25 to npm`
- `chore(actions): publish v1.0.0 to npm`

3. **Do not skip tests for behavior changes**
- Add or update unit tests when changing logic.
- Add or update unit tests when changing logic; add a Playwright e2e test if the change affects the browser bundle surface.

## Coding standards (repo-specific)

- Use **4-space indentation**.
- Use **single quotes**.
- Use **semicolons**.
- Avoid `var`; use `const`/`let`.
- Keep braces for control flow (`curly` rule).
- Keep strict equality (`eqeqeq`).
- Add JSDoc for classes, methods, and function declarations (`require-jsdoc`).
- TypeScript only for new code. Avoid reintroducing `.js` files in `src/`.
- 4-space indentation, single quotes, semicolons.
- Prefer `const`/`let` (no `var`), keep braces (`curly`), strict equality (`eqeqeq`).
- The TS config is pragmatic — `strict: false`, `noImplicitAny: false` — so visitor/AST code uses `any` liberally. That is intentional: don't tighten types in unrelated files while fixing something else.
- Don't add JSDoc that simply restates the function signature; reserve comments for non-obvious *why*.
- Prefer minimal, surgical diffs; avoid unrelated formatting churn.

## Build and test workflow

When changing code, run checks in this order:

1. `npm run build --workspaces --if-present`
2. `npm test`
3. If needed for coverage diagnostics: `npm run coverage`
1. `npm run build` — runs `tsc` per workspace (also rebuilds before tests via each package's `pretest`).
2. `npm test` — runs the full Jest suite across every package.
3. `npm run -w markdown-transform-e2e test` — Playwright browser tests; only needed if you changed source that ends up in a UMD bundle.
4. `npm run coverage` — coverage aggregation (only if investigating coverage).

For package-level iteration, `cd packages/<name>` and run `npm run build`, `npm test`, etc. directly. For the umbrella package, also run `npm run webpack` after `npm run build` to refresh the UMD bundle.

For package-level iteration, run the package scripts directly inside the package folder (for example lint/test in `packages/markdown-common` or `packages/markdown-transform`).
When migrating Concerto: `@accordproject/concerto-core` is on **v4**. `new ModelManager({ strict: true })` is no longer valid — drop the option, don't cast to `any`. The model manager defaults are equivalent in v4.

## Dependency management rules (critical)

Expand All @@ -73,7 +80,7 @@ These are based on merged PR review feedback in this repository:
- If downgrading is required, explain why in PR description and comments.

3. **Avoid adding new dependencies without clear rationale**
- Reviewers repeatedly asked Why the new deps? across multiple package manifests.
- Reviewers repeatedly asked "Why the new deps?" across multiple package manifests.
- Prefer updating existing dependencies over adding new ones.

4. **For core Accord dependencies, prefer exact versions when the repo already pins exact versions**
Expand All @@ -83,6 +90,15 @@ These are based on merged PR review feedback in this repository:
5. **Keep workspace dependency versions consistent across packages**
- If bumping a shared dependency, align all affected package manifests and lockfiles in one change.

6. **Browser polyfills only when strictly needed**
- The webpack configs use `webpack.ProvidePlugin({ process: 'process/browser' })` and `resolve.alias = { jsdom: false }` to keep UMD bundles slim. Don't add Node polyfills unless a real test fails without them.

## Publishing & npm packages

- `package.json` `files` field for every publishable package is `["lib"]` (or `["lib", "umd"]` for the three UMD packages). `src/`, tests, snapshots, jest config, eslint config, and tsconfig stay out of the tarball.
- `main: "lib/index.js"`, `types: "lib/index.d.ts"`. The three UMD packages also set `browser: "umd/markdown-X.js"` so bundlers serving browser targets pick the UMD bundle automatically.
- Source maps (`*.js.map`) **are** shipped — keep `sourceMap: true` in `tsconfig.base.json` so consumer stack traces stay useful.

## AI review behavior (adapted from best-practice guidance)

Copilot suggestions should follow a **human-in-the-loop**, high-signal workflow:
Expand All @@ -100,7 +116,7 @@ Copilot suggestions should follow a **human-in-the-loop**, high-signal workflow:
- Do not trade security for convenience.

4. **Continuous learning loop**
- If a review pattern repeats (e.g., why new dependency?”, “why downgrade?), treat it as a standing rule for future changes.
- If a review pattern repeats (e.g., "why new dependency?", "why downgrade?"), treat it as a standing rule for future changes.
- Prefer repository-established patterns over generic defaults.

5. **Human validation remains required**
Expand All @@ -111,18 +127,21 @@ Copilot suggestions should follow a **human-in-the-loop**, high-signal workflow:
Before proposing a PR-ready change:

- [ ] Change scope is minimal and focused
- [ ] New/updated behavior has tests
- [ ] New/updated behavior has tests (unit and, where relevant, Playwright e2e)
- [ ] Lint/build/tests pass
- [ ] `npm pack --dry-run` for any package whose contents changed shows only `lib/` (+ optional `umd/`) — no tests, snapshots, or configs leaking
- [ ] Dependency changes are justified and minimal
- [ ] No accidental downgrades or unnecessary added packages
- [ ] Commit(s) use DCO sign-off
- [ ] PR description clearly explains **why** the change is needed

## Common pitfalls in this repo

- Mixing `.js` and `.ts` in `src/` — the source tree is TypeScript only.
- Forgetting to rebuild UMD bundles (`npm run webpack -w …`) after source changes; the Playwright e2e tests will then test stale code.
- Adding broad type tightening (`noImplicitAny`, `strict`) in unrelated files while fixing a small bug — out of scope, expand `any` only where the change is needed.
- Adding many dependency changes in one sweep without explaining each one.
- Switching from exact to ranged versions for core dependencies without team agreement.
- Introducing dependency downgrades as side effects of automated tooling.
- Making large unrelated edits while addressing a small issue.
- Re-introducing `npm install`-time `prepare`/`build` scripts. Build is a separate explicit step now (`npm run build`), keeping `npm install` fast and resilient to broken intermediate states.

When in doubt, prefer small, explicit, well-tested changes that match existing package patterns.
56 changes: 56 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,62 @@ jobs:
parallel: true
fail-on-error: false

e2e:
name: Browser E2E (Chromium)
runs-on: ubuntu-latest
needs:
- build

steps:
- name: git checkout
uses: actions/checkout@v4

- name: Use Node.js 22.x
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build packages (dep order)
run: npm run build

- name: Build UMD bundles
run: npm run -w markdown-transform-e2e build:bundles

- name: Get Playwright version
id: playwright-version
run: |
echo "version=$(node -p "require('./e2e/node_modules/@playwright/test/package.json').version")" >> $GITHUB_OUTPUT

- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}

- name: Install Playwright Chromium
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: npx --no-install --prefix e2e playwright install --with-deps chromium

- name: Install Playwright system deps only (cache hit)
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx --no-install --prefix e2e playwright install-deps chromium

- name: Run browser e2e tests
run: npm test -w markdown-transform-e2e -- --reporter=list

- name: Upload Playwright report on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: e2e/playwright-report
retention-days: 7

notify:
needs:
- build
Expand Down
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

A transformation and parsing framework for converting markdown content to HTML and other structured document object models (DOMs).

The codebase is written in TypeScript; each package ships compiled JavaScript alongside `.d.ts` type declarations.

![Transformations](./packages/markdown-transform/transformations.png)

## Structure of the Code Repository
Expand All @@ -37,23 +39,23 @@ Top level repository (markdown-transform), with sub packages. Each sub-package i

The CommonMark DOM is a model for the elements of CommonMark (the specification for markdown text), expressed as a [Concerto schema](https://github.com/accordproject/concerto), and serialized as a JSON graph.

The schema is defined here: https://models.accordproject.org/markdown/commonmark@0.2.0.html
The schema is defined here: https://models.accordproject.org/markdown/commonmark@0.5.0.html

### CiceroMark DOM

CiceroMark defines markdown documents with embedded clauses, where each clause is an instance of a template, specified using TemplateMark.

The CiceroMark DOM extends the CommonMark DOM, defining nodes for `Clause`, `Variable` and `Formula` etc.
The CiceroMark DOM extends the CommonMark DOM, defining nodes for `Clause`, `Variable`, `FormattedVariable`, `EnumVariable`, `Conditional`, `Optional`, `Formula` and `ListBlock`.

The schema is defined here: https://models.accordproject.org/markdown/ciceromark@0.3.0.html
The schema is defined here: https://models.accordproject.org/markdown/ciceromark@0.6.0.html

### TemplateMark DOM

TemplateMark defines markdown documents with syntax for embedded variables, optional blocks, formulas etc. It is used to define Accord Project templates.

The TemplateMark DOM extends the CommonMark DOM, defining nodes for `ClauseDefinition`, `VariableDefinition` and `ForumulaDefinition` etc.
The TemplateMark DOM extends the CommonMark DOM, defining nodes for `ClauseDefinition`, `ContractDefinition`, `VariableDefinition`, `FormattedVariableDefinition`, `EnumVariableDefinition`, `ConditionalDefinition`, `OptionalDefinition`, `WithDefinition`, `JoinDefinition`, `ListBlockDefinition` and `FormulaDefinition`.

The schema is defined here: https://models.accordproject.org/markdown/templatemark.html
The schema is defined here: https://models.accordproject.org/markdown/templatemark@0.5.0.html

## Installation

Expand All @@ -76,13 +78,14 @@ markus --help
npm install
```

Then run:
Then build all packages and run their tests:

```
npm run build
npm run test
```

This command uses npm workspaces to run the tests for each package in the monorepo.
These commands use npm workspaces. `build` runs `tsc` for each package, producing JavaScript and `.d.ts` declarations into `packages/*/lib/`. `test` runs Jest. Requires Node 22 or later.

---

Expand Down
4 changes: 4 additions & 0 deletions e2e/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
test-results/
playwright-report/
playwright/.cache/
41 changes: 41 additions & 0 deletions e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Browser End-to-End Tests

[Playwright](https://playwright.dev) tests that load the UMD bundles for `markdown-html`, `markdown-template` and `markdown-transform` into a real headless Chromium and call the public API. These tests exist to catch packaging/bundling regressions that unit tests miss — for example, accidentally pulling Node-only modules like `jsdom` into the browser bundle.

## Run

From the repository root:

```bash
npm install --workspaces
npm run -w markdown-transform-e2e test
```

`npm test` from the e2e directory runs `pretest` first, which:
1. Builds each TS package (`tsc`)
2. Builds each UMD bundle (`webpack`)
3. Installs the Chromium browser used by Playwright (cached after first run)

## What's covered

| Spec | Asserts |
|------|---------|
| `markdown-html.spec.ts` | `HtmlTransformer` exported on the global; `toHtml`/`toCiceroMark` work using the native `DOMParser` (jsdom is **not** in the browser bundle) |
| `markdown-template.spec.ts` | `TemplateMarkTransformer` exported; `toTokens` and `normalizeNLs` work |
| `markdown-transform.spec.ts` | `transform`, `formatDescriptor`, `generateTransformationDiagram`, `TransformEngine` exported; markdown → commonmark and markdown → html transformations succeed |

## Adding a test

Each UMD bundle exports its API onto `window['<package-name>']` (e.g. `window['markdown-html']`). Spec pattern:

```ts
await page.setContent('<!doctype html><html><body></body></html>');
await page.addScriptTag({ path: path.resolve(__dirname, '../../packages/<pkg>/umd/<pkg>.js') });

const result = await page.evaluate(() => {
const { Something } = (window as any)['<pkg>'];
return new Something().doStuff();
});

expect(result).toBe(/* … */);
```
14 changes: 14 additions & 0 deletions e2e/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "markdown-transform-e2e",
"version": "1.0.0",
"private": true,
"description": "End-to-end tests that load the UMD browser bundles into a real headless Chromium and exercise the public API.",
"scripts": {
"build:bundles": "npm run -w @accordproject/markdown-html webpack && npm run -w @accordproject/markdown-template webpack && npm run -w @accordproject/markdown-transform webpack",
"pretest": "npm run build:bundles && npx --yes playwright install chromium",
"test": "playwright test"
},
"devDependencies": {
"@playwright/test": "^1.49.0"
}
}
26 changes: 15 additions & 11 deletions packages/markdown-cicero/index.js → e2e/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@
* limitations under the License.
*/

'use strict';
import { defineConfig, devices } from '@playwright/test';

/**
* Export the framework and plugins
* @module markdown-transform
*/

module.exports.CiceroMarkTransformer = require('./lib/CiceroMarkTransformer');
module.exports.FromCiceroEditVisitor = require('./lib/FromCiceroEditVisitor');
module.exports.ToCommonMarkVisitor = require('./lib/ToCommonMarkVisitor');

module.exports.Decorators = require('./lib/Decorators');
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'list',
use: {
trace: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
],
});
Loading
Loading