feat(deploy): production deployment setup and fixes
- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma binaryTargets for openssl 3.0 - nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal cron, http->https redirect - prisma: add origin_goods.delisted migration, sync missing schema (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz - api: CORS wildcard reflection, helmet CORP cross-origin, price backfill in persistProductDetail, categoryIcon ancestor fallback, mediaByColor per-color gallery in public goods detail - admin: /admin base path (vite + router) - import-data.mjs: udt_name casting, serial sequence advance fix
This commit is contained in:
@@ -1,265 +1,265 @@
|
||||
# CI Workflows
|
||||
|
||||
## Basic CI
|
||||
|
||||
```yaml
|
||||
# .github/workflows/ci.yml
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm lint
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm typecheck
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm test
|
||||
```
|
||||
|
||||
## Matrix Testing
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node: [20, 22, 24]
|
||||
include:
|
||||
- os: macos-latest
|
||||
node: 24
|
||||
- os: windows-latest
|
||||
node: 24
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm test
|
||||
```
|
||||
|
||||
## Skip Docs-Only Changes
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
changed:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.check.outputs.only_changed == 'true' }}
|
||||
steps:
|
||||
- uses: tj-actions/changed-files@v47
|
||||
id: check
|
||||
with:
|
||||
files: |
|
||||
docs/**
|
||||
**.md
|
||||
|
||||
test:
|
||||
needs: changed
|
||||
if: needs.changed.outputs.should_skip != 'true'
|
||||
# ... rest of job
|
||||
```
|
||||
|
||||
## Auto-fix Commits
|
||||
|
||||
```yaml
|
||||
- run: pnpm lint:fix
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
if: github.event_name == 'push'
|
||||
with:
|
||||
commit_message: 'chore: lint fix'
|
||||
```
|
||||
|
||||
## Release on Tag (Token-based)
|
||||
|
||||
```yaml
|
||||
# .github/workflows/release.yml
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
registry-url: https://registry.npmjs.org
|
||||
- run: pnpm install
|
||||
- run: pnpm build
|
||||
- run: pnpm publish --access public --no-git-checks
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
```
|
||||
|
||||
## Release on Tag (OIDC - Recommended)
|
||||
|
||||
No NPM_TOKEN needed. Uses GitHub OIDC for tokenless auth with provenance.
|
||||
|
||||
```yaml
|
||||
name: Release
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
actions: read
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
|
||||
jobs:
|
||||
wait-for-ci:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: lewagon/wait-on-check-action@v1.3.4
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
check-name: ci
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
|
||||
release:
|
||||
needs: wait-for-ci
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24 # Required: npm 11.5.1+
|
||||
cache: pnpm
|
||||
registry-url: https://registry.npmjs.org
|
||||
- run: pnpm install
|
||||
- run: pnpm build
|
||||
- run: pnpm dlx changelogithub
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- run: pnpm publish --access public --no-git-checks --provenance
|
||||
```
|
||||
|
||||
### OIDC Setup Steps
|
||||
|
||||
1. Open `https://www.npmjs.com/package/<PACKAGE_NAME>/access`
|
||||
2. Scroll to "Publishing access" section
|
||||
3. Click "Add GitHub Actions" under Trusted Publishers
|
||||
4. Fill: Owner, Repository, Workflow file (`release.yml`), Environment (empty)
|
||||
5. Click "Add"
|
||||
|
||||
### OIDC Requirements
|
||||
|
||||
1. **Node.js 24+** (npm 11.5.1+ required - Node 22 has npm 10.x which fails)
|
||||
2. **Permissions**: `id-token: write`
|
||||
3. **Publish flag**: `--provenance`
|
||||
4. **package.json**: must have `repository` field
|
||||
5. **npm 2FA**: "Require 2FA or granular access token" (allows OIDC)
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
| Error | Cause | Fix |
|
||||
| ------------------------------------- | -------------------- | ----------------------------------------- |
|
||||
| "Access token expired" E404 | npm too old | Use Node.js 24 |
|
||||
| ENEEDAUTH | Missing registry-url | Add `registry-url` to setup-node |
|
||||
| "repository.url is empty" E422 | Missing field | Add `repository` to package.json |
|
||||
| "not configured as trusted publisher" | Config mismatch | Check owner, repo, workflow match exactly |
|
||||
|
||||
## Monorepo Matrix
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
package: [core, utils, cli]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm --filter ${{ matrix.package }} test
|
||||
```
|
||||
|
||||
## Concurrency Control
|
||||
|
||||
Cancel outdated runs:
|
||||
|
||||
```yaml
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
```
|
||||
|
||||
## pkg-pr-new for PRs
|
||||
|
||||
```yaml
|
||||
# .github/workflows/pkg-pr-new.yml
|
||||
name: Publish PR
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm build
|
||||
- run: pnpm dlx pkg-pr-new publish --compact --pnpm
|
||||
```
|
||||
|
||||
## Package Validation in CI
|
||||
|
||||
```yaml
|
||||
- run: pnpm build
|
||||
- run: pnpm dlx publint
|
||||
- run: pnpm dlx @arethetypeswrong/cli --pack .
|
||||
```
|
||||
# CI Workflows
|
||||
|
||||
## Basic CI
|
||||
|
||||
```yaml
|
||||
# .github/workflows/ci.yml
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm lint
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm typecheck
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm test
|
||||
```
|
||||
|
||||
## Matrix Testing
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node: [20, 22, 24]
|
||||
include:
|
||||
- os: macos-latest
|
||||
node: 24
|
||||
- os: windows-latest
|
||||
node: 24
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm test
|
||||
```
|
||||
|
||||
## Skip Docs-Only Changes
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
changed:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.check.outputs.only_changed == 'true' }}
|
||||
steps:
|
||||
- uses: tj-actions/changed-files@v47
|
||||
id: check
|
||||
with:
|
||||
files: |
|
||||
docs/**
|
||||
**.md
|
||||
|
||||
test:
|
||||
needs: changed
|
||||
if: needs.changed.outputs.should_skip != 'true'
|
||||
# ... rest of job
|
||||
```
|
||||
|
||||
## Auto-fix Commits
|
||||
|
||||
```yaml
|
||||
- run: pnpm lint:fix
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
if: github.event_name == 'push'
|
||||
with:
|
||||
commit_message: 'chore: lint fix'
|
||||
```
|
||||
|
||||
## Release on Tag (Token-based)
|
||||
|
||||
```yaml
|
||||
# .github/workflows/release.yml
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
registry-url: https://registry.npmjs.org
|
||||
- run: pnpm install
|
||||
- run: pnpm build
|
||||
- run: pnpm publish --access public --no-git-checks
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
```
|
||||
|
||||
## Release on Tag (OIDC - Recommended)
|
||||
|
||||
No NPM_TOKEN needed. Uses GitHub OIDC for tokenless auth with provenance.
|
||||
|
||||
```yaml
|
||||
name: Release
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
actions: read
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
|
||||
jobs:
|
||||
wait-for-ci:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: lewagon/wait-on-check-action@v1.3.4
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
check-name: ci
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
|
||||
release:
|
||||
needs: wait-for-ci
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24 # Required: npm 11.5.1+
|
||||
cache: pnpm
|
||||
registry-url: https://registry.npmjs.org
|
||||
- run: pnpm install
|
||||
- run: pnpm build
|
||||
- run: pnpm dlx changelogithub
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- run: pnpm publish --access public --no-git-checks --provenance
|
||||
```
|
||||
|
||||
### OIDC Setup Steps
|
||||
|
||||
1. Open `https://www.npmjs.com/package/<PACKAGE_NAME>/access`
|
||||
2. Scroll to "Publishing access" section
|
||||
3. Click "Add GitHub Actions" under Trusted Publishers
|
||||
4. Fill: Owner, Repository, Workflow file (`release.yml`), Environment (empty)
|
||||
5. Click "Add"
|
||||
|
||||
### OIDC Requirements
|
||||
|
||||
1. **Node.js 24+** (npm 11.5.1+ required - Node 22 has npm 10.x which fails)
|
||||
2. **Permissions**: `id-token: write`
|
||||
3. **Publish flag**: `--provenance`
|
||||
4. **package.json**: must have `repository` field
|
||||
5. **npm 2FA**: "Require 2FA or granular access token" (allows OIDC)
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
| Error | Cause | Fix |
|
||||
| ------------------------------------- | -------------------- | ----------------------------------------- |
|
||||
| "Access token expired" E404 | npm too old | Use Node.js 24 |
|
||||
| ENEEDAUTH | Missing registry-url | Add `registry-url` to setup-node |
|
||||
| "repository.url is empty" E422 | Missing field | Add `repository` to package.json |
|
||||
| "not configured as trusted publisher" | Config mismatch | Check owner, repo, workflow match exactly |
|
||||
|
||||
## Monorepo Matrix
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
package: [core, utils, cli]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm --filter ${{ matrix.package }} test
|
||||
```
|
||||
|
||||
## Concurrency Control
|
||||
|
||||
Cancel outdated runs:
|
||||
|
||||
```yaml
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
```
|
||||
|
||||
## pkg-pr-new for PRs
|
||||
|
||||
```yaml
|
||||
# .github/workflows/pkg-pr-new.yml
|
||||
name: Publish PR
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm build
|
||||
- run: pnpm dlx pkg-pr-new publish --compact --pnpm
|
||||
```
|
||||
|
||||
## Package Validation in CI
|
||||
|
||||
```yaml
|
||||
- run: pnpm build
|
||||
- run: pnpm dlx publint
|
||||
- run: pnpm dlx @arethetypeswrong/cli --pack .
|
||||
```
|
||||
|
||||
@@ -1,180 +1,180 @@
|
||||
# Release Workflow
|
||||
|
||||
## Tools
|
||||
|
||||
| Tool | Purpose |
|
||||
| ----------- | --------------------------------- |
|
||||
| bumpp | Interactive version bumping |
|
||||
| changelogen | Changelog generation from commits |
|
||||
| pkg-pr-new | PR preview packages |
|
||||
|
||||
## bumpp (Version Bumping)
|
||||
|
||||
```bash
|
||||
pnpm add -D bumpp
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"release": "bumpp"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Interactive prompt for patch/minor/major. Options:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"release": "bumpp --commit --tag --push"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For monorepos:
|
||||
|
||||
```bash
|
||||
bumpp -r # Recursive
|
||||
bumpp packages/*/package.json # Specific packages
|
||||
```
|
||||
|
||||
## changelogen (Changelog)
|
||||
|
||||
```bash
|
||||
pnpm add -D changelogen
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"changelog": "changelogen --release"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Combined workflow:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"release": "changelogen --release && bumpp"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Full Release Flow
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"release": "pnpm lint && pnpm test && changelogen --release && bumpp --commit --tag --push"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
CI publishes to npm on tag push.
|
||||
|
||||
## pkg-pr-new (PR Previews)
|
||||
|
||||
For publishable packages. Creates install links on PRs.
|
||||
|
||||
```yaml
|
||||
# .github/workflows/pkg-pr-new.yml
|
||||
name: Publish PR
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm build
|
||||
- run: pnpm dlx pkg-pr-new publish --compact --pnpm
|
||||
```
|
||||
|
||||
For monorepos:
|
||||
|
||||
```bash
|
||||
pnpm dlx pkg-pr-new publish --compact --pnpm './packages/*'
|
||||
```
|
||||
|
||||
PR comment shows:
|
||||
|
||||
```
|
||||
pnpm add https://pkg.pr.new/your-org/your-package@123
|
||||
```
|
||||
|
||||
## Conventional Commits
|
||||
|
||||
For changelogen to work:
|
||||
|
||||
```
|
||||
feat: add dark mode support
|
||||
fix: resolve memory leak in parser
|
||||
docs: update README
|
||||
chore: update dependencies
|
||||
```
|
||||
|
||||
## npm Publishing
|
||||
|
||||
### Token-based (legacy)
|
||||
|
||||
```yaml
|
||||
- run: pnpm publish --access public --no-git-checks
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
```
|
||||
|
||||
### OIDC (Recommended)
|
||||
|
||||
No token needed. See ci-workflows.md for full setup.
|
||||
|
||||
```yaml
|
||||
- run: pnpm publish --access public --no-git-checks --provenance
|
||||
```
|
||||
|
||||
## Monorepo Publishing
|
||||
|
||||
With pnpm:
|
||||
|
||||
```bash
|
||||
pnpm -r publish --access public
|
||||
```
|
||||
|
||||
With bumpp:
|
||||
|
||||
```bash
|
||||
bumpp -r && pnpm -r publish
|
||||
```
|
||||
|
||||
## Pre-release Versions
|
||||
|
||||
```bash
|
||||
bumpp --preid beta # 1.0.0 -> 1.0.1-beta.0
|
||||
bumpp --preid alpha # 1.0.0 -> 1.0.1-alpha.0
|
||||
```
|
||||
|
||||
## Package.json Requirements
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@scope/package",
|
||||
"version": "1.0.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/org/repo.git"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`repository` required for npm provenance.
|
||||
# Release Workflow
|
||||
|
||||
## Tools
|
||||
|
||||
| Tool | Purpose |
|
||||
| ----------- | --------------------------------- |
|
||||
| bumpp | Interactive version bumping |
|
||||
| changelogen | Changelog generation from commits |
|
||||
| pkg-pr-new | PR preview packages |
|
||||
|
||||
## bumpp (Version Bumping)
|
||||
|
||||
```bash
|
||||
pnpm add -D bumpp
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"release": "bumpp"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Interactive prompt for patch/minor/major. Options:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"release": "bumpp --commit --tag --push"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For monorepos:
|
||||
|
||||
```bash
|
||||
bumpp -r # Recursive
|
||||
bumpp packages/*/package.json # Specific packages
|
||||
```
|
||||
|
||||
## changelogen (Changelog)
|
||||
|
||||
```bash
|
||||
pnpm add -D changelogen
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"changelog": "changelogen --release"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Combined workflow:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"release": "changelogen --release && bumpp"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Full Release Flow
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"release": "pnpm lint && pnpm test && changelogen --release && bumpp --commit --tag --push"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
CI publishes to npm on tag push.
|
||||
|
||||
## pkg-pr-new (PR Previews)
|
||||
|
||||
For publishable packages. Creates install links on PRs.
|
||||
|
||||
```yaml
|
||||
# .github/workflows/pkg-pr-new.yml
|
||||
name: Publish PR
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm build
|
||||
- run: pnpm dlx pkg-pr-new publish --compact --pnpm
|
||||
```
|
||||
|
||||
For monorepos:
|
||||
|
||||
```bash
|
||||
pnpm dlx pkg-pr-new publish --compact --pnpm './packages/*'
|
||||
```
|
||||
|
||||
PR comment shows:
|
||||
|
||||
```
|
||||
pnpm add https://pkg.pr.new/your-org/your-package@123
|
||||
```
|
||||
|
||||
## Conventional Commits
|
||||
|
||||
For changelogen to work:
|
||||
|
||||
```
|
||||
feat: add dark mode support
|
||||
fix: resolve memory leak in parser
|
||||
docs: update README
|
||||
chore: update dependencies
|
||||
```
|
||||
|
||||
## npm Publishing
|
||||
|
||||
### Token-based (legacy)
|
||||
|
||||
```yaml
|
||||
- run: pnpm publish --access public --no-git-checks
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
```
|
||||
|
||||
### OIDC (Recommended)
|
||||
|
||||
No token needed. See ci-workflows.md for full setup.
|
||||
|
||||
```yaml
|
||||
- run: pnpm publish --access public --no-git-checks --provenance
|
||||
```
|
||||
|
||||
## Monorepo Publishing
|
||||
|
||||
With pnpm:
|
||||
|
||||
```bash
|
||||
pnpm -r publish --access public
|
||||
```
|
||||
|
||||
With bumpp:
|
||||
|
||||
```bash
|
||||
bumpp -r && pnpm -r publish
|
||||
```
|
||||
|
||||
## Pre-release Versions
|
||||
|
||||
```bash
|
||||
bumpp --preid beta # 1.0.0 -> 1.0.1-beta.0
|
||||
bumpp --preid alpha # 1.0.0 -> 1.0.1-alpha.0
|
||||
```
|
||||
|
||||
## Package.json Requirements
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@scope/package",
|
||||
"version": "1.0.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/org/repo.git"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`repository` required for npm provenance.
|
||||
|
||||
@@ -1,201 +1,201 @@
|
||||
# Testing
|
||||
|
||||
## Vitest Setup
|
||||
|
||||
```bash
|
||||
pnpm add -D vitest
|
||||
```
|
||||
|
||||
### Basic Config
|
||||
|
||||
```typescript
|
||||
// vitest.config.ts
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
include: ['test/**/*.test.ts'],
|
||||
testTimeout: 30_000,
|
||||
reporters: 'dot',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### With Coverage
|
||||
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
include: ['src/**/*.ts'],
|
||||
exclude: ['src/types.ts'],
|
||||
reporter: ['text', 'lcovonly', 'html'],
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Workspace Projects
|
||||
|
||||
For monorepos, test packages separately:
|
||||
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
test: {
|
||||
projects: [
|
||||
'packages/*/vitest.config.ts',
|
||||
{
|
||||
extends: './vitest.config.ts',
|
||||
test: { name: 'unit', environment: 'node' },
|
||||
},
|
||||
{
|
||||
extends: './vitest.config.ts',
|
||||
test: { name: 'browser', browser: { enabled: true } },
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Fixture-Based Testing
|
||||
|
||||
Test transforms with file fixtures:
|
||||
|
||||
```typescript
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { transform } from '../src'
|
||||
|
||||
const fixtures = import.meta.glob('./fixtures/*.ts', { as: 'raw' })
|
||||
|
||||
describe('transform', () => {
|
||||
for (const [path, getContent] of Object.entries(fixtures)) {
|
||||
it(path, async () => {
|
||||
const content = await getContent()
|
||||
const result = await transform(content)
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Idempotency Testing
|
||||
|
||||
Ensure transforms are stable:
|
||||
|
||||
```typescript
|
||||
it('transform is idempotent', async () => {
|
||||
const pass1 = (await transform(fixture))?.code ?? fixture
|
||||
expect(pass1).toMatchSnapshot()
|
||||
|
||||
const pass2 = (await transform(pass1))?.code ?? pass1
|
||||
expect(pass2).toBe(pass1) // Should not change
|
||||
})
|
||||
```
|
||||
|
||||
## Type-Level Testing
|
||||
|
||||
Test TypeScript types:
|
||||
|
||||
```typescript
|
||||
// vitest.config.ts
|
||||
export default defineConfig({
|
||||
test: {
|
||||
typecheck: { enabled: true },
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
```typescript
|
||||
// test/types.test-d.ts
|
||||
import { describe, expectTypeOf, it } from 'vitest'
|
||||
import type { Input, Output } from '../src'
|
||||
|
||||
describe('types', () => {
|
||||
it('infers input correctly', () => {
|
||||
expectTypeOf<Input<typeof schema>>().toEqualTypeOf<{ id: string }>()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Multi-TS Version Testing
|
||||
|
||||
Test across TypeScript versions (TanStack pattern):
|
||||
|
||||
```yaml
|
||||
# .github/workflows/ci.yml
|
||||
jobs:
|
||||
test-types:
|
||||
strategy:
|
||||
matrix:
|
||||
ts: ['5.0', '5.2', '5.4', '5.6', '5.8']
|
||||
steps:
|
||||
- run: pnpm add -D typescript@${{ matrix.ts }}
|
||||
- run: pnpm typecheck
|
||||
```
|
||||
|
||||
## Package Validation
|
||||
|
||||
Validate published package:
|
||||
|
||||
```bash
|
||||
# Check exports are correct
|
||||
pnpm dlx publint
|
||||
|
||||
# Check types work in different moduleResolutions
|
||||
pnpm dlx @arethetypeswrong/cli --pack .
|
||||
```
|
||||
|
||||
Add to tsdown config:
|
||||
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
attw: { profile: 'esm-only' }, // or 'node16'
|
||||
})
|
||||
```
|
||||
|
||||
## Test Scripts
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test": "vitest",
|
||||
"test:run": "vitest run",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"test:types": "vitest typecheck"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Mocking
|
||||
|
||||
```typescript
|
||||
import { vi } from 'vitest'
|
||||
|
||||
vi.mock('fs', () => ({
|
||||
readFileSync: vi.fn(() => 'mocked content'),
|
||||
}))
|
||||
|
||||
// Spy on method
|
||||
const spy = vi.spyOn(console, 'log')
|
||||
expect(spy).toHaveBeenCalledWith('expected')
|
||||
```
|
||||
|
||||
## Testing Plugins
|
||||
|
||||
Dogfood your own plugin in tests:
|
||||
|
||||
```typescript
|
||||
// vitest.config.ts
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import MyPlugin from './src/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
MyPlugin({ /* options */ }),
|
||||
],
|
||||
test: {
|
||||
include: ['test/**/*.test.ts'],
|
||||
},
|
||||
})
|
||||
```
|
||||
# Testing
|
||||
|
||||
## Vitest Setup
|
||||
|
||||
```bash
|
||||
pnpm add -D vitest
|
||||
```
|
||||
|
||||
### Basic Config
|
||||
|
||||
```typescript
|
||||
// vitest.config.ts
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
include: ['test/**/*.test.ts'],
|
||||
testTimeout: 30_000,
|
||||
reporters: 'dot',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### With Coverage
|
||||
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
include: ['src/**/*.ts'],
|
||||
exclude: ['src/types.ts'],
|
||||
reporter: ['text', 'lcovonly', 'html'],
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Workspace Projects
|
||||
|
||||
For monorepos, test packages separately:
|
||||
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
test: {
|
||||
projects: [
|
||||
'packages/*/vitest.config.ts',
|
||||
{
|
||||
extends: './vitest.config.ts',
|
||||
test: { name: 'unit', environment: 'node' },
|
||||
},
|
||||
{
|
||||
extends: './vitest.config.ts',
|
||||
test: { name: 'browser', browser: { enabled: true } },
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Fixture-Based Testing
|
||||
|
||||
Test transforms with file fixtures:
|
||||
|
||||
```typescript
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { transform } from '../src'
|
||||
|
||||
const fixtures = import.meta.glob('./fixtures/*.ts', { as: 'raw' })
|
||||
|
||||
describe('transform', () => {
|
||||
for (const [path, getContent] of Object.entries(fixtures)) {
|
||||
it(path, async () => {
|
||||
const content = await getContent()
|
||||
const result = await transform(content)
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Idempotency Testing
|
||||
|
||||
Ensure transforms are stable:
|
||||
|
||||
```typescript
|
||||
it('transform is idempotent', async () => {
|
||||
const pass1 = (await transform(fixture))?.code ?? fixture
|
||||
expect(pass1).toMatchSnapshot()
|
||||
|
||||
const pass2 = (await transform(pass1))?.code ?? pass1
|
||||
expect(pass2).toBe(pass1) // Should not change
|
||||
})
|
||||
```
|
||||
|
||||
## Type-Level Testing
|
||||
|
||||
Test TypeScript types:
|
||||
|
||||
```typescript
|
||||
// vitest.config.ts
|
||||
export default defineConfig({
|
||||
test: {
|
||||
typecheck: { enabled: true },
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
```typescript
|
||||
// test/types.test-d.ts
|
||||
import { describe, expectTypeOf, it } from 'vitest'
|
||||
import type { Input, Output } from '../src'
|
||||
|
||||
describe('types', () => {
|
||||
it('infers input correctly', () => {
|
||||
expectTypeOf<Input<typeof schema>>().toEqualTypeOf<{ id: string }>()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Multi-TS Version Testing
|
||||
|
||||
Test across TypeScript versions (TanStack pattern):
|
||||
|
||||
```yaml
|
||||
# .github/workflows/ci.yml
|
||||
jobs:
|
||||
test-types:
|
||||
strategy:
|
||||
matrix:
|
||||
ts: ['5.0', '5.2', '5.4', '5.6', '5.8']
|
||||
steps:
|
||||
- run: pnpm add -D typescript@${{ matrix.ts }}
|
||||
- run: pnpm typecheck
|
||||
```
|
||||
|
||||
## Package Validation
|
||||
|
||||
Validate published package:
|
||||
|
||||
```bash
|
||||
# Check exports are correct
|
||||
pnpm dlx publint
|
||||
|
||||
# Check types work in different moduleResolutions
|
||||
pnpm dlx @arethetypeswrong/cli --pack .
|
||||
```
|
||||
|
||||
Add to tsdown config:
|
||||
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
attw: { profile: 'esm-only' }, // or 'node16'
|
||||
})
|
||||
```
|
||||
|
||||
## Test Scripts
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test": "vitest",
|
||||
"test:run": "vitest run",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"test:types": "vitest typecheck"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Mocking
|
||||
|
||||
```typescript
|
||||
import { vi } from 'vitest'
|
||||
|
||||
vi.mock('fs', () => ({
|
||||
readFileSync: vi.fn(() => 'mocked content'),
|
||||
}))
|
||||
|
||||
// Spy on method
|
||||
const spy = vi.spyOn(console, 'log')
|
||||
expect(spy).toHaveBeenCalledWith('expected')
|
||||
```
|
||||
|
||||
## Testing Plugins
|
||||
|
||||
Dogfood your own plugin in tests:
|
||||
|
||||
```typescript
|
||||
// vitest.config.ts
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import MyPlugin from './src/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
MyPlugin({ /* options */ }),
|
||||
],
|
||||
test: {
|
||||
include: ['test/**/*.test.ts'],
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user