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
3 changes: 3 additions & 0 deletions .github/.release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.2.2"
}
17 changes: 17 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule: { interval: weekly }
# wait for a release to age before adopting it, security updates are not delayed
cooldown: { default-days: 7 }
commit-message: { prefix: "build(deps)" }
groups:
js: { patterns: ["*"], update-types: [minor, patch] }
- package-ecosystem: github-actions
directory: /
schedule: { interval: weekly }
cooldown: { default-days: 7 }
commit-message: { prefix: "ci(deps)" }
groups:
actions: { patterns: ["*"] }
12 changes: 12 additions & 0 deletions .github/release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"bootstrap-sha": "dbb1f33f9abdf1a09bf36a1172ea7e7aaf366289",
"bump-minor-pre-major": true,
"packages": {
".": {
"release-type": "node",
"include-component-in-tag": false,
"pull-request-title-pattern": "chore: release ${version}"
}
}
}
51 changes: 51 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: CI

on:
pull_request:
push:
branches: [master]

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
node-version: [22, 24, 26]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
# dependency install scripts are never run
- run: pnpm install --frozen-lockfile --ignore-scripts
- run: pnpm typecheck
- run: pnpm test
- run: pnpm build
- name: Verify the package contents
run: npm pack --dry-run --ignore-scripts

# Fails a pull request that adds a dependency with a known vulnerability.
dependency-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
fail-on-severity: moderate
38 changes: 38 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: CodeQL

on:
pull_request:
push:
branches: [master]
schedule:
- cron: '23 6 * * 1'

permissions:
contents: read

concurrency:
group: codeql-${{ github.ref }}
cancel-in-progress: true

jobs:
analyze:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [javascript-typescript, actions]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
with:
languages: ${{ matrix.language }}
build-mode: none
- uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
with:
category: /language:${{ matrix.language }}
32 changes: 32 additions & 0 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: PR title

# PRs are squash-merged, so the PR title becomes the commit on master and
# must follow Conventional Commits for release-please to version it.
on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
pull-requests: read

jobs:
conventional-commit:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
fix
docs
style
refactor
perf
test
build
ci
chore
revert
58 changes: 58 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Release

# Opens a release PR from the conventional-commit titles on master. Merging it
# tags the version and creates the GitHub release, which is then published to npm.
on:
push:
branches: [master]

permissions: {}

concurrency:
group: release
cancel-in-progress: false

jobs:
release-please:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
pull-requests: write
issues: write
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
steps:
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
id: release
with:
config-file: .github/release-please-config.json
manifest-file: .github/.release-please-manifest.json

publish:
needs: release-please
if: needs.release-please.outputs.release_created == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
# The npm trusted publisher is restricted to this environment.
environment: npm
permissions:
contents: read
# npm trusted publishing and provenance, there is no npm token to leak.
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.release-please.outputs.tag_name }}
persist-credentials: false
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
# No dependency cache here, so that a poisoned cache cannot reach a release.
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 26
registry-url: 'https://registry.npmjs.org'
- run: pnpm install --frozen-lockfile --ignore-scripts
- run: pnpm typecheck
- run: pnpm test
- run: npm publish
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ id_rsa_userhost
!.yarn/sdks
!.yarn/versions
# yarn v1 node_modules
node_modules/
node_modules/
# registry credentials
.npmrc
1 change: 0 additions & 1 deletion .npmignore

This file was deleted.

34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
# fsrouter
Filesystem based router for express.js

# Installation

```sh
npm install @parallelworks/fsrouter
```

# Usage

```typescript
import express, { RequestHandler } from 'express'
import { initFsRouting } from '@parallelworks/fsrouter'
import path from 'path'
const port = 3000
const app = express()

const routesPath = path.join(__dirname, '_routes')

Expand Down Expand Up @@ -81,6 +88,29 @@ To make a route public, add the following to the endpoint file:
export const guestAccess = true
```

A route cannot be both public and admin only, so exporting both is an error.

To restrict a method to users with certain roles, export the roles for each method. The user needs at least one of the roles listed:

```typescript
export const roles = {
POST: ['org:admin', 'org:settings'],
}
```

The roles of the user who made the request come from the `rolesResolver` that is passed to `initFsRouting`:

```typescript
const router = await initFsRouting({
ensureAdmin,
ensureAuthenticated,
routesPath,
rolesResolver: req => req.user.roles,
})
```

The router refuses to start when these exports cannot be applied, e.g. when roles are set for a method that the file does not export a handler for.

# **Validation**

When you’re using the @parallelworks/fsrouter package, validation is handled with a special export. The [full JSON-schema spec](https://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.10.3.2.1) is available in these objects, so we can create some advanced validation if necessary.
Expand Down Expand Up @@ -129,3 +159,7 @@ export const GET: Endpoint<{jid: string}, {}, {}, {}> = (req, res) => {
...
}
```

# **Releasing**

Pull requests are squash-merged, and their titles follow [Conventional Commits](https://www.conventionalcommits.org). From those titles, [release-please](https://github.com/googleapis/release-please) keeps a release pull request open. Merging the release pull request tags the version and publishes it to [npm](https://www.npmjs.com/package/@parallelworks/fsrouter).
37 changes: 27 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,48 @@
{
"name": "@parallelworks/fsrouter",
"type": "commonjs",
"version": "0.1.8",
"version": "0.2.2",
"description": "Filesystem based router for Express.js",
"author": "Michael McQuade",
"license": "Apache-2.0",
"main": "build/index.js",
"types": "build/index.d.ts",
"repository": "git@github.com:parallelworks/fsrouter.git",
"files": [
"build"
],
"repository": {
"type": "git",
"url": "git+https://github.com/parallelworks/fsrouter.git"
},
"homepage": "https://github.com/parallelworks/fsrouter#readme",
"bugs": "https://github.com/parallelworks/fsrouter/issues",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"packageManager": "pnpm@10.33.4",
"scripts": {
"compile": "tsc",
"test": "jest"
"clean": "node -e \"require('fs').rmSync('build', { recursive: true, force: true })\"",
"build": "pnpm clean && tsc -p tsconfig.build.json",
"typecheck": "tsc --noEmit",
"test": "jest",
"prepack": "pnpm build"
},
"license": "UNLICENSED",
"devDependencies": {
"@types/express": "^4.17.25",
"@types/express-serve-static-core": "^4.19.8",
"@types/express-serve-static-core": "^4.19.9",
"@types/jest": "^29.5.14",
"express": "^4.22.1",
"@types/node": "^22.20.4",
"express": "^4.22.3",
"jest": "^29.7.0",
"ts-jest": "^29.4.6",
"typescript": "^4.9.5"
"ts-jest": "^29.4.14",
"typescript": "^5.9.3"
},
"peerDependencies": {
"express": "^4.17.1"
},
"dependencies": {
"ajv": "^8.18.0",
"ajv": "^8.20.0",
"ajv-errors": "^3.0.0",
"glob": "^9.3.5",
"json-schema-to-ts": "^2.12.0"
Expand Down
Loading
Loading