A comprehensive template for AI-driven JavaScript/TypeScript development with full CI/CD pipeline support.
This repository publishes the real test package
@link-foundation/example-package-name so the template release pipeline is
validated end to end with npm trusted publishing.
This repository is also a static laws browser for legal agents and people,
published at https://link-assistant.github.io/law/. Every law it knows how
to download is listed in data/registry.lino (Links
Notation), together with the legal basis that decides how it may be shown:
- mirror: the publisher's terms or the law itself allow copying, so CI
downloads the official text, converts it to the unified law model and
publishes it as
laws/<key>.json.gzplus anchored static HTML, always linking back to the original; - dynamic (live): copying is not allowed, so nothing is stored here; the viewer downloads the original in your browser and converts it with the same converter;
- blocked: the source cannot be downloaded automatically (robots.txt, bot protection or PDF-only texts); the catalog says why and links to it.
The registry covers the United States (Constitution, U.S. Code, eCFR), the
European Union (treaties and key regulations in all 24 official languages),
China, India, Russia and Georgia. The conversion algorithm in src/law/ is
isomorphic: the same code runs in CI/CD, locally (law CLI) and in the
browser bundle (src/web/viewer.js).
npm run law:validate # check data/registry.lino
node bin/law.js list # documents and their modes
node bin/law.js fetch ru/constitution/ru # one law as unified JSON
node bin/law.js probe # robots.txt, CORS, HTTP status
node bin/law.js build --only us/,eu/gdpr/ # static site in build/site
npx http-server build/site # browse it locallyThe laws.yml workflow validates the registry on pull requests and builds a
subset; on main, daily and on demand it rebuilds the full site, reuses the
previously published copy when a source is temporarily unreachable, deploys
GitHub Pages and commits the refreshed data/catalog.lino.
The research behind the registry is in
docs/case-studies/issue-1.
- Multi-runtime support: Works with Bun, Node.js, and Deno
- Universal testing: Uses test-anywhere for cross-runtime tests
- Automated releases: Changesets-based versioning with GitHub Actions
- Optional Docker Hub publishing: Docker images can be published after the matching npm version is visible
- Universal app example: React UI for the package API with GitHub Pages, Electron, and Capacitor build paths
- Code quality: ESLint + Prettier with pre-commit hooks via Husky
- Package manager agnostic: Works with bun, npm, yarn, pnpm, and deno
- Broken link checks: Automated link validation with lychee and Web Archive fallback suggestions
- Click "Use this template" on GitHub to create a new repository
- Clone your new repository
- Update
package.jsonwith your package name and description - Install dependencies:
bun install - Start developing!
# Install dependencies
bun install
# Run tests
bun test --timeout 30000
# Or with other runtimes:
npm test
deno test -A --parallel
# Lint code
bun run lint
# Format code
bun run format
# Check all (lint + format + file size)
bun run check
# Build the universal React example app
npm install --prefix examples/universal-app
npm run example:web:build
npm run example:desktop:package
# Try the CLI locally
node bin/example-package-name.js add 2 3.
├── .changeset/ # Changeset configuration
├── .github/workflows/ # GitHub Actions CI/CD
├── .husky/ # Git hooks (pre-commit)
├── bin/law.js # Laws CLI (validate, list, fetch, probe, build)
├── data/registry.lino # Laws, sources and legal basis (Links Notation)
├── data/catalog.lino # Build results, written by laws.yml
├── examples/ # Usage examples
│ └── universal-app/ # React + GitHub Pages + Electron + Capacitor app
├── scripts/ # Build and release scripts
├── src/ # Source code
│ ├── index.js # Main entry point
│ ├── law/ # Isomorphic download and conversion pipeline
│ ├── web/ # Browser viewer for mirrored and live laws
│ └── index.d.ts # TypeScript definitions
├── tests/ # Test files
├── .eslintrc.js # ESLint configuration
├── .prettierrc # Prettier configuration
├── bunfig.toml # Bun configuration
├── deno.json # Deno configuration
└── package.json # Node.js package manifest
This template is designed to work seamlessly with all major JavaScript runtimes:
- Bun: Primary runtime with highest performance, uses native test support (
bun test) - Node.js: Alternative runtime, uses built-in test runner (
node --test) - Deno: Secure runtime with built-in TypeScript support (
deno test)
The test-anywhere framework provides a unified testing API that works identically across all runtimes.
While package.json is the source of truth for dependencies, the template supports:
- bun: Primary choice, uses
bun.lockb - npm: Uses
package-lock.json - yarn: Uses
yarn.lock - pnpm: Uses
pnpm-lock.yaml - deno: Uses
deno.jsonfor configuration
Note: package-lock.json is not committed by default to allow any package manager.
The template includes examples/universal-app, a Vite React app that imports
add and multiply from src/index.js and renders a visual calculator UI.
The same static build is used by:
- GitHub Pages (
npm run example:web:build) - Electron desktop packaging (
npm run example:desktop:package) - Capacitor Android/iOS sync (
npm run example:mobile:sync)
The example app has its own package.json and lockfile so template users can
opt into the frontend stack without adding React, Electron, or Capacitor to the
library package itself.
See examples/universal-app/README.md for local web, desktop, Android, and iOS testing instructions.
- ESLint: Configured with recommended rules + Prettier integration
- Prettier: Consistent code formatting
- Husky + lint-staged: Pre-commit hooks ensure code quality
- File size limit: Files must stay under 1500 lines for maintainability (enforced via ESLint and CI)
The release workflow uses Changesets for version management:
- Creating a changeset: Run
bun run changesetto document changes - PR validation: CI checks for valid changeset in each PR
- Automated versioning: Merging to
maintriggers version bump - npm publishing: Automated via OIDC trusted publishing (no tokens needed)
- Optional Docker Hub publishing: When configured, waits for the exact npm version and tags the Docker image with that version
- GitHub releases: Auto-created with formatted release notes
First release of a brand-new package: OIDC trusted publishing cannot create a package that does not exist yet (the first publish fails with
E404, because a trusted publisher can only be configured for an existing package). To bootstrap, add a repository secret namedNPM_TOKEN(a granular/automation token with publish access). The release workflow passes it asNODE_AUTH_TOKENautomatically. Once the package exists and OIDC trusted publishing is configured on npmjs.com, the token becomes optional and can be removed.
Two manual release modes are available via GitHub Actions:
- Instant release: Immediately bump version and publish
- Changeset PR: Create a PR with changeset for review
The GitHub Actions workflow (.github/workflows/release.yml) implements a fast-fail pipeline:
Fast checks (~7-30s each, run first for fastest feedback):
- Test compilation: Syntax-checks all
.mjsfiles withnode --check - Lint, format & secrets scan: ESLint, Prettier, jscpd, and secretlint for credential leak detection
- File line limits: Enforces the 1500-line limit on JavaScript (
.js,.mjs,.cjs) and Markdown (.md) files plusrelease.yml - Changeset check: Validates PR has exactly one changeset (added by that PR)
- Version check: Blocks manual version changes in
package.json - Documentation validation: Checks required doc files (doc line limits are enforced by the file line limits check)
Slow checks (only run after all fast checks pass):
- Test matrix: 3 runtimes × 3 OS = 9 test combinations
- Broken link checks: Validates all links in Markdown/HTML files (separate workflow)
Release (on merge to main):
- Changeset merge: Combines multiple pending changesets at release time
- Release: Automated versioning and npm publishing
- Optional Docker publish: Publishes Docker Hub
latestand npm-version tags after the npm package is visible
Every CI job declares an explicit timeout-minutes so hung steps fail
in minutes instead of reaching the GitHub Actions default of six hours.
Fast checks use 5-10 minute caps, release jobs use 50 minutes, and the
link checker uses 10 minutes for external network variance.
That cap is a backstop, never the deadline: GitHub reports a job it
kills as cancelled, not failed. Long steps therefore own an
explicit budget via scripts/run-with-budget-warning.sh, which warns at
70% of the budget and fails the step with exit code 124 when it expires.
See CI-TIMEOUT-BUDGETS.md.
Individual tests are also capped inside supported runners:
npm test runs node --test --test-timeout=30000, and the CI Bun
runner uses bun test --timeout 30000. Both bound a single test, not
the suite, which is why the suite budget above exists. Deno does not
provide a single global per-test timeout flag, so Deno tests are
protected by their step budget and the matrix job backstop.
See BEST-PRACTICES.md for detailed explanations of each practice.
The CI/CD pipeline is designed to handle concurrent PRs gracefully:
-
PR Validation: Only validates changesets added by the current PR, not pre-existing ones from other merged PRs. This prevents false failures when multiple PRs merge before a release cycle completes.
-
Release-time Merging: If multiple changesets exist when releasing, they are automatically merged into a single changeset with:
- The highest version bump type (major > minor > patch)
- All descriptions preserved in chronological order
This design decouples PR validation from the need to pull changes from the default branch, reducing conflicts and ensuring that even if CI/CD fails, all unpublished changesets will still get published when the error is resolved.
GitHub Pages hosts the laws browser, deployed by the laws.yml workflow; the
example-app.yml workflow builds the universal example app as a downloadable
artifact. Before the first run on main in a new repository, open
Settings → Pages and set Source = GitHub Actions. This is a one-time
manual step and cannot be configured from a workflow because the Pages source
defaults to Deploy from a branch. Without it, the pages-deploy job fails
on actions/deploy-pages with Get Pages site failed /
Failed to create deployment. Committing data/catalog.lino back to main
needs the RELEASE_PR_TOKEN secret; without it the catalog-commit job
fails with an explicit message.
The same example-app.yml workflow contains a preview-regen job that boots
the built example app in a headless Chromium via
browser-commander +
Playwright and writes fresh screenshots to
docs/screenshots/example-app/example-app-{locale}-{theme}.png on every
push to main (and on workflow_dispatch). Any drift is committed back to
main so README/site images never go stale between releases. Screenshot-only
pushes do not match this workflow's path filter, while a protected-branch
fallback PR remains eligible for its required checks. The job runs in the
official Playwright container with the browser already installed, avoiding CI
stalls from live Chromium downloads.
The same script is available locally:
npm install --prefix examples/universal-app
npm run example:web:preview-images
# Verbose probe of <html data-theme>, <html lang>, and PNG signatures:
PREVIEW_VERBOSE=1 npm run example:web:preview-imagesThe matrix defaults to {en, ru} × {light, dark}. The shipped example app
has no localization or theme toggle yet, so every cell currently renders
the same UI — when a fork adds either, the matrix produces real per-cell
variants without script edits.
The link checker workflow (.github/workflows/links.yml) validates all links in Markdown and HTML files:
- Detection: Uses lychee to scan all
*.mdand*.htmlfiles - Web Archive fallback: For any broken links found, automatically checks the Wayback Machine for archived versions
- Actionable suggestions: Reports one of three outcomes for each broken link:
- Archived: Suggests the Web Archive URL as a replacement
- Not archived: Clearly reports the link is unrecoverable
- Scheduled checks: Runs weekly to catch links that break over time (even if no files changed)
- Issue creation: On scheduled runs, creates a GitHub Issue with the full broken links report
Add regex patterns to .lycheeignore to exclude URLs from checks (e.g., local dev URLs, example.com, known rate-limited sites).
After creating a repository from this template, update the package name in:
package.json: replace"@link-foundation/example-package-name"with your package name.changeset/config.json: Package references
Release scripts derive the package name from package.json at runtime, so no
script-level package-name constants need to be edited during template adoption.
If main requires pull requests and the Pipeline Status check, the release
and instant-release jobs can use their built-in GITHUB_TOKEN to open a release
PR. Enable Allow GitHub Actions to create and approve pull requests in
repository Actions settings. The helper verifies that the version commit is a
direct child of the validated workflow SHA and contains only package metadata
and consumed changesets. It then creates a successful Pipeline Status check
on that exact commit, linked to the validating run, waits for every required
check, and merges the PR only after they pass. The release jobs alone receive
checks: write. Set RELEASE_METADATA_PATHS to a comma-separated list of
additional exact metadata paths for other package layouts.
An optional RELEASE_PR_TOKEN secret keeps the existing dedicated-token path
and takes precedence when set. Give that actor Contents and Pull requests write
access and Checks read access. The manual changeset-PR and generated-preview
paths still require the dedicated token. A short-lived GitHub App installation
token can be wired to the same inputs instead of a PAT.
Docker publishing is disabled by default. To enable it for a project that ships
a Docker image, add a Dockerfile and configure these GitHub Actions settings:
| Setting | Type | Description |
|---|---|---|
DOCKERHUB_IMAGE |
Variable | Docker Hub image name, for example namespace/image. This enables Docker publishing. |
DOCKERHUB_USERNAME |
Variable | Docker Hub username used by docker/login-action. |
DOCKERHUB_TOKEN |
Secret | Docker Hub access token used for registry authentication. |
DOCKER_CONTEXT |
Variable, optional | Docker build context. Defaults to .. |
DOCKERFILE |
Variable, optional | Dockerfile path. Defaults to ./Dockerfile. |
When enabled, the release workflow waits until the exact published npm version
is visible in the npm registry, then publishes Docker Hub tags for latest and
that same version. The Docker build also receives NPM_PACKAGE_VERSION as a
build argument so Dockerfiles can install the matching published package.
Customize ESLint in eslint.config.js. Current configuration:
- ES Modules support
- Prettier integration
- No console restrictions (common in CLI tools)
- Strict equality enforcement
- Async/await best practices
- Strict unused variables rule: No exceptions - all unused variables, arguments, and caught errors must be removed (no
_prefix exceptions)
Configured in .prettierrc:
- Single quotes
- Semicolons
- 2-space indentation
- 80-character line width
- ES5 trailing commas
- LF line endings
| Script | Description |
|---|---|
bun test --timeout 30000 |
Run tests with Bun and a 30s per-test cap |
npm test |
Run tests with Node.js and a 30s per-test cap |
bun run lint |
Check code with ESLint |
bun run lint:fix |
Fix ESLint issues automatically |
bun run format |
Format code with Prettier |
bun run format:check |
Check formatting without changing files |
bun run check |
Run all checks (lint + format) |
npm run example:web:dev |
Start the universal app Vite dev server |
npm run example:web:build |
Build the universal app static web bundle |
npm run example:web:preview-images |
Regenerate preview screenshots via browser-commander |
npm run example:desktop:package |
Package the Electron desktop app locally |
npm run example:mobile:sync |
Build and sync the app bundle into Capacitor projects |
bun run changeset |
Create a new changeset |
npm run law:validate |
Validate data/registry.lino |
npm run law:probe |
Probe every law source (robots.txt, CORS, status) |
npm run law:build |
Build the static laws site into build/site |
See CONTRIBUTING.md for detailed contribution guidelines.
Quick steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes
- Create a changeset:
bun run changeset - Commit your changes (pre-commit hooks will run automatically)
- Push and create a Pull Request
This template implements CI/CD best practices for AI-driven development. See BEST-PRACTICES.md for details on:
- File size limits for AI readability
- Automated formatting and linting
- Multi-runtime and cross-platform testing
- Changeset-based versioning
- Concurrency control for CI/CD pipelines
Unlicense - Public Domain