diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..654c6d4 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets). + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md). diff --git a/.changeset/boundary-try.md b/.changeset/boundary-try.md new file mode 100644 index 0000000..93d0827 --- /dev/null +++ b/.changeset/boundary-try.md @@ -0,0 +1,5 @@ +--- +"faultline": minor +--- + +Add `boundary.try(error)` — a non-throwing variant of a boundary that returns `Result` instead of throwing on an unmapped tag. Boundaries usually sit at a transport edge where an uncaught throw is costly; `.try()` lets you handle an unexpected (untyped/foreign) error tag as a value. Exposes the `BoundaryViolationError` type. diff --git a/.changeset/cli-node-publishable.md b/.changeset/cli-node-publishable.md new file mode 100644 index 0000000..884818f --- /dev/null +++ b/.changeset/cli-node-publishable.md @@ -0,0 +1,5 @@ +--- +"faultline-cli": minor +--- + +The CLI is now publishable to npm and runs on plain Node.js (>=18). The `bin` points at a built `dist/index.js` with a Node shebang (previously a raw `.ts` file with a `bun` shebang), `typescript` is declared as a runtime dependency, and the package ships a `files` allowlist, README, LICENSE, and full repository metadata. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..ff76529 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": ["faultline-vscode"] +} diff --git a/.changeset/eslint-plugin-publish-fixes.md b/.changeset/eslint-plugin-publish-fixes.md new file mode 100644 index 0000000..792fcff --- /dev/null +++ b/.changeset/eslint-plugin-publish-fixes.md @@ -0,0 +1,16 @@ +--- +"eslint-plugin-faultline": minor +--- + +Rule improvements and publish-readiness fixes. + +Rules: +- `throw-type-mismatch` now inspects all overload signatures, not just the first — overloaded functions are fully checked for transitive error propagation. +- `throw-type-mismatch` reports `throw ` when the surrounding function declares a `TypedPromise` error type and the thrown variable's type has no resolvable `_tag`. +- `uncovered-catch` no longer misattributes throws inside async closures (e.g. `setTimeout(async () => ...)`) to the outer try's catch. +- Published `LIMITATIONS.md` documenting patterns the rules can't detect, plus an 11-pattern torture-fixture corpus guarding the rules. + +Packaging: +- Ship the previously-missing `README.md` (it was already listed in `files`). +- Derive `meta.version` from `package.json` at build time so it can no longer drift from the published version (was hardcoded `0.1.0`). +- Run tests as part of `prepublishOnly`. diff --git a/.changeset/redaction-globstar.md b/.changeset/redaction-globstar.md new file mode 100644 index 0000000..15ad9fd --- /dev/null +++ b/.changeset/redaction-globstar.md @@ -0,0 +1,7 @@ +--- +"faultline": minor +--- + +Redaction: support a `**` globstar segment in `redactPaths` to redact a key at any depth (e.g. `**.password`). Redaction paths are matched against the serialized error structure and a non-matching path is a silent no-op (it fails open), so `**.` is now the recommended form for sensitive keys — it can't silently miss a nested occurrence the way a brittle exact path can. Documented this behavior in the README and SECURITY.md. + +Also clarified (docs only, no behavior change): `attemptAsync`/`TaskResult` cancellation is cooperative — aborting short-circuits *subsequent* steps and stops the caller waiting, but does not preempt in-flight work unless your function threads the `AbortSignal` through. And `defineBoundary` throws `System.BoundaryViolation` (preserving the original as cause) when an unmapped error tag reaches it. diff --git a/.changeset/taskresult-cancellation.md b/.changeset/taskresult-cancellation.md new file mode 100644 index 0000000..eee3754 --- /dev/null +++ b/.changeset/taskresult-cancellation.md @@ -0,0 +1,13 @@ +--- +"faultline": minor +--- + +TaskResult cancellation, rejection safety, and API pruning. + +Breaking changes: +- `TaskResult.toPromise()` removed. Use `.run()` instead — same signature. +- TaskResult chain methods (`.map`, `.andThen`, `.catchTag`, etc.) now widen their error union to include `System.Cancelled`. Exhaustive `match()` over a TaskResult needs a `'System.Cancelled'` handler or a `_` branch. +- `TaskResult.fromPromise` return type now widens to include `System.Unexpected`. Factory rejections are no longer unhandled — they are caught and wrapped as `System.Unexpected` with the original throw preserved as `.cause`. + +Added: +- TaskResult chain steps check `context.signal?.aborted` between every step. Aborting the signal mid-pipeline short-circuits the remaining steps to `System.Cancelled` instead of running them to completion. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..a2c8a91 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,54 @@ +name: Bug report +description: Report a bug in a faultline package +labels: [bug] +body: + - type: dropdown + id: package + attributes: + label: Package + description: Which package is affected? + options: + - faultline + - eslint-plugin-faultline + - faultline-cli + - faultline-vscode + - not sure + validations: + required: true + - type: input + id: version + attributes: + label: Package version + placeholder: e.g. 0.2.0 + validations: + required: true + - type: textarea + id: what-happened + attributes: + label: What happened? + description: A clear description of the bug and what you expected instead. + validations: + required: true + - type: textarea + id: repro + attributes: + label: Minimal reproduction + description: The smallest code snippet (or repo link) that reproduces the issue. For ESLint rules, include the source and your ESLint config. + render: ts + validations: + required: true + - type: input + id: env + attributes: + label: Environment + description: Node/Bun version, OS, TypeScript version, ESLint version (if relevant). + placeholder: e.g. Node 20.11, macOS 14, TypeScript 5.9 + validations: + required: false + - type: checkboxes + id: checks + attributes: + label: Checks + options: + - label: For an ESLint false negative/positive, I checked LIMITATIONS.md first. + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..88e4b91 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability + url: https://github.com/danfry1/faultline/security/advisories/new + about: Please report security issues privately, not as public issues. See SECURITY.md. + - name: Question / discussion + url: https://github.com/danfry1/faultline/discussions + about: Ask questions and discuss ideas here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..318d621 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,25 @@ +name: Feature request +description: Suggest an idea or improvement +labels: [enhancement] +body: + - type: textarea + id: problem + attributes: + label: Problem + description: What problem are you trying to solve? What's the use case? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: What would you like to happen? API sketches welcome. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Other approaches you've thought about, including existing workarounds. + validations: + required: false diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..39f6f2f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,27 @@ + + +## Summary + + + +## Type of change + +- [ ] Bug fix +- [ ] New feature +- [ ] Breaking change +- [ ] Docs / internal only (no changeset needed) + +## Affected packages + +- [ ] `faultline` +- [ ] `eslint-plugin-faultline` +- [ ] `faultline-cli` +- [ ] `faultline-vscode` + +## Checklist + +- [ ] `bun run typecheck` passes +- [ ] `bun run test` passes (new code paths are covered) +- [ ] `bun run lint` passes +- [ ] Added a changeset (`bun run changeset`) for user-facing changes +- [ ] Updated docs / `LIMITATIONS.md` / type assertions where relevant diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e747e35..8e0581c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,5 +15,33 @@ jobs: - run: bun install --frozen-lockfile - run: bun run lint - run: bun run typecheck - - run: bun run test - - run: bun --filter 'faultline' build + - run: bun run test # enforces the coverage gate in packages/faultline/bunfig.toml + - name: Build publishable packages + run: bun --filter 'faultline' --filter 'eslint-plugin-faultline' --filter 'faultline-cli' build + - name: Validate faultline package exports + working-directory: packages/faultline + run: npm pack && npx @arethetypeswrong/cli ./faultline-*.tgz --ignore-rules no-resolution + + node-compat: + needs: quality + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: [18, 20, 22] + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + - run: bun install --frozen-lockfile + - name: Build publishable packages + run: bun --filter 'faultline' --filter 'eslint-plugin-faultline' --filter 'faultline-cli' build + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - name: Smoke-test built artifacts on Node ${{ matrix.node-version }} + run: | + set -euo pipefail + node -e "const f=require('./packages/faultline/dist/index.cjs'); if(typeof f.defineErrors!=='function') throw new Error('CJS load failed'); console.log('faultline CJS ok')" + node --input-type=module -e "const f=await import('./packages/faultline/dist/index.js'); if(typeof f.defineErrors!=='function') throw new Error('ESM load failed'); console.log('faultline ESM ok')" + node -e "const p=require('./packages/eslint-plugin-faultline/dist/index.js'); if(!p.rules||!p.rules['no-raw-throw']) throw new Error('plugin load failed'); console.log('eslint-plugin ok')" + node packages/faultline-cli/dist/index.js catalog test/fixtures/sample-app | grep -q 'User.NotFound' && echo 'faultline-cli ok' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe577e7..5b3972c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,16 +1,21 @@ name: Release +# Changesets flow: +# - A push to main with pending changesets opens (or updates) a "Version Packages" PR. +# - Merging that PR bumps versions + changelogs, then this workflow publishes to npm. on: push: - tags: ['v*', 'eslint-plugin-v*'] + branches: [main] + +concurrency: ${{ github.workflow }}-${{ github.ref }} permissions: contents: write + pull-requests: write id-token: write jobs: - publish-core: - if: startsWith(github.ref, 'refs/tags/v') && !startsWith(github.ref, 'refs/tags/eslint-plugin-v') + release: runs-on: ubuntu-latest environment: npm steps: @@ -24,43 +29,20 @@ jobs: - run: bun install --frozen-lockfile - run: bun run typecheck - run: bun run test - - name: Build - run: bun --filter 'faultline' build - - name: Validate package exports - working-directory: packages/faultline - run: npm pack && npx @arethetypeswrong/cli ./faultline-*.tgz --ignore-rules no-resolution - - name: Publish to npm - working-directory: packages/faultline - run: npm publish --provenance --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + - name: Validate package exports (faultline) + run: | + bun --filter 'faultline' build + cd packages/faultline + npm pack + npx @arethetypeswrong/cli ./faultline-*.tgz --ignore-rules no-resolution + - name: Create Release PR or Publish + uses: changesets/action@v1 with: - generate_release_notes: true - - publish-eslint-plugin: - if: startsWith(github.ref, 'refs/tags/eslint-plugin-v') - runs-on: ubuntu-latest - environment: npm - steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - - uses: actions/setup-node@v4 - with: - node-version: 22 - registry-url: https://registry.npmjs.org - - run: npm i -g npm@latest - - run: bun install --frozen-lockfile - - run: bun run typecheck - - name: Build - run: bun --filter 'eslint-plugin-faultline' build - - name: Publish to npm - working-directory: packages/eslint-plugin-faultline - run: npm publish --provenance --access public + version: bun run version-packages + publish: bun run release + commit: 'chore: version packages' + title: 'chore: version packages' env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - generate_release_notes: true + NPM_CONFIG_PROVENANCE: 'true' diff --git a/.gitignore b/.gitignore index e751e60..64d67ff 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ node_modules dist out *.tsbuildinfo -bun.lock .claude/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 83011ce..e9683e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +> Versioning and per-package changelogs are managed with [Changesets](https://github.com/changesets/changesets). +> Each published package keeps its own `CHANGELOG.md` (`packages/*/CHANGELOG.md`), generated at release time. +> Unreleased changes live as files under [`.changeset/`](./.changeset). This file retains the pre-Changesets history. + ## 0.1.0 Initial release. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d9e9615 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,41 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes +- Focusing on what is best for the overall community + +Examples of unacceptable behavior: + +- The use of sexualized language or imagery, and sexual attention or advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the maintainers responsible for enforcement via the contact on the maintainer's GitHub profile. All complaints will be reviewed and investigated promptly and fairly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e2e3456 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,76 @@ +# Contributing to faultline + +Thanks for your interest in improving faultline! This document covers the workflow for the monorepo. + +## Prerequisites + +- [Bun](https://bun.sh) (the version is pinned in `package.json` under `packageManager`) +- Node.js >= 18 (the published packages target Node 18+; CI verifies on 18/20/22) + +## Getting started + +```bash +git clone https://github.com/danfry1/faultline.git +cd faultline +bun install +``` + +## Repository layout + +| Package | Published as | Status | +|---|---|---| +| `packages/faultline` | `faultline` | stable | +| `packages/eslint-plugin-faultline` | `eslint-plugin-faultline` | stable | +| `packages/faultline-cli` | `faultline-cli` | beta | +| `packages/faultline-vscode` | — (Marketplace, not npm) | experimental | + +`examples/`, `playground/`, and `test/fixtures/` are development-only and are not published. + +## Development commands + +Run from the repo root: + +```bash +bun run typecheck # tsc --noEmit across every package +bun run test # all tests; packages/faultline enforces a coverage gate +bun run lint # oxlint +bun run build # build all packages +``` + +Per-package: `bun --filter ''