From 8385cea64032ee332b9589140c95364e4048eb1f Mon Sep 17 00:00:00 2001 From: Marc Villain Date: Fri, 2 Oct 2026 23:59:58 +0200 Subject: [PATCH] feat: npm package @crudy/pgmorbac ships the extension SQL The package carries the install and upgrade scripts, built from src/ on prepack (sql/ is not committed), with extensionSql(), upgradeScripts(), version and sqlDir. --- .gitignore | 3 +++ CHANGELOG.md | 1 + README.md | 18 +++++++++++++ index.d.ts | 16 +++++++++++ index.js | 32 ++++++++++++++++++++++ package.json | 51 ++++++++++++++++++++++++++++++++--- scripts/build-package-sql.mjs | 23 ++++++++++++++++ 7 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 index.d.ts create mode 100644 index.js create mode 100644 scripts/build-package-sql.mjs diff --git a/.gitignore b/.gitignore index 96e5bd6..d67a80f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ Thumbs.db node_modules/ dist/ package-lock.json + +# npm package build output (scripts/build-package-sql.mjs) +/sql/ diff --git a/CHANGELOG.md b/CHANGELOG.md index d14b108..7956f9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0] - 2026-07-25 ### Added +- npm package `@crudy/pgmorbac`: the install and upgrade scripts with `extensionSql()`, `upgradeScripts()`, `version` and `sqlDir` - First stable release of the Multi-OrBAC PostgreSQL extension - Complete Multi-OrBAC implementation based on CNRS research paper - Core features: diff --git a/README.md b/README.md index f6f6689..3386909 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,24 @@ sudo cp pgmorbac--1.0.0.sql $(pg_config --sharedir)/extension/ psql -d mydb -c "CREATE EXTENSION pgmorbac;" ``` +### From Node.js (npm) + +`@crudy/pgmorbac` ships the same install and upgrade scripts for projects that +apply SQL through a migration runner instead of `CREATE EXTENSION`: + +```bash +npm install @crudy/pgmorbac +``` + +```js +import { extensionSql, upgradeScripts, version, sqlDir } from '@crudy/pgmorbac'; + +await client.query(extensionSql()); // pgmorbac--.sql +for (const u of upgradeScripts()) { // pgmorbac----.sql, in order + await client.query(u.sql); +} +``` + ## Quick Start ```sql diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..16449cf --- /dev/null +++ b/index.d.ts @@ -0,0 +1,16 @@ +/** Absolute path to pgmorbac.control. */ +export declare const controlFile: string; +/** Absolute path to the directory holding the install and upgrade scripts. */ +export declare const sqlDir: string; +/** The extension version (default_version in pgmorbac.control). */ +export declare const version: string; +/** The install script pgmorbac--.sql, as CREATE EXTENSION would run it. */ +export declare function extensionSql(): string; +export interface UpgradeScript { + from: string; + to: string; + filename: string; + sql: string; +} +/** Upgrade scripts pgmorbac----.sql, in version order. */ +export declare function upgradeScripts(): UpgradeScript[]; diff --git a/index.js b/index.js new file mode 100644 index 0000000..0d741bf --- /dev/null +++ b/index.js @@ -0,0 +1,32 @@ +import { readFileSync, readdirSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const root = dirname(fileURLToPath(import.meta.url)); + +export const controlFile = join(root, 'pgmorbac.control'); +export const sqlDir = join(root, 'sql'); + +const control = readFileSync(controlFile, 'utf8').match(/default_version\s*=\s*'([^']+)'/); +if (!control) throw new Error('[pgmorbac] pgmorbac.control has no default_version'); +export const version = control[1]; + +/** The install script pgmorbac--.sql, as CREATE EXTENSION would run it. */ +export function extensionSql() { + return readFileSync(join(sqlDir, `pgmorbac--${version}.sql`), 'utf8'); +} + +const semver = (v) => v.split('.').map(Number); +const byFrom = (a, b) => { + const [x, y] = [semver(a.from), semver(b.from)]; + return x[0] - y[0] || x[1] - y[1] || x[2] - y[2]; +}; + +/** Upgrade scripts pgmorbac----.sql, in version order. */ +export function upgradeScripts() { + return readdirSync(sqlDir) + .map((filename) => ({ filename, m: /^pgmorbac--(\d+\.\d+\.\d+)--(\d+\.\d+\.\d+)\.sql$/.exec(filename) })) + .filter(({ m }) => m) + .map(({ filename, m }) => ({ from: m[1], to: m[2], filename, sql: readFileSync(join(sqlDir, filename), 'utf8') })) + .sort(byFrom); +} diff --git a/package.json b/package.json index 555f709..00ace10 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,58 @@ { - "name": "pgmorbac-release-tools", + "name": "@crudy/pgmorbac", "version": "1.0.0", - "private": true, "type": "module", - "description": "Release tooling for the pgmorbac PostgreSQL extension (build + sign + publish the PGXN distribution).", + "description": "Multi-OrBAC for PostgreSQL: the pgmorbac extension SQL, packaged for Node.js projects and migration runners", + "keywords": [ + "pgmorbac", + "morbac", + "orbac", + "rbac", + "authorization", + "access-control", + "postgresql", + "postgresql-extension", + "row-level-security", + "multi-tenant" + ], + "author": "Marc Villain", + "license": "MIT", + "homepage": "https://pgmorbac.crudy.fr", + "repository": { + "type": "git", + "url": "https://github.com/crudylabs/pgmorbac.git" + }, + "bugs": { + "url": "https://github.com/crudylabs/pgmorbac/issues" + }, + "exports": { + ".": { + "types": "./index.d.ts", + "default": "./index.js" + } + }, + "main": "./index.js", + "types": "./index.d.ts", + "files": [ + "index.js", + "index.d.ts", + "sql", + "pgmorbac.control", + "README.md", + "CHANGELOG.md", + "LICENSE" + ], + "engines": { + "node": ">=18" + }, "scripts": { + "prepack": "node scripts/build-package-sql.mjs", "dist": "node scripts/build-dist.mjs", "release": "node scripts/build-dist.mjs && node scripts/gitea-release.mjs" }, + "publishConfig": { + "access": "public" + }, "devDependencies": { "jose": "^5" } diff --git a/scripts/build-package-sql.mjs b/scripts/build-package-sql.mjs new file mode 100644 index 0000000..fcf0165 --- /dev/null +++ b/scripts/build-package-sql.mjs @@ -0,0 +1,23 @@ +#!/usr/bin/env node +// Builds sql/ for the npm package: the install script from src/ (tools/build.sh) +// plus any upgrade scripts. Runs on prepack; sql/ is never committed. +import { execFileSync } from 'node:child_process'; +import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = join(dirname(fileURLToPath(import.meta.url)), '..'); +const control = readFileSync(join(root, 'pgmorbac.control'), 'utf8').match(/default_version\s*=\s*'([^']+)'/); +if (!control) throw new Error('pgmorbac.control has no default_version'); +const version = control[1]; +const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')); +if (pkg.version !== version) throw new Error(`package.json ${pkg.version} != pgmorbac.control ${version}`); + +const sqlDir = join(root, 'sql'); +rmSync(sqlDir, { recursive: true, force: true }); +mkdirSync(sqlDir); +execFileSync(join(root, 'tools', 'build.sh'), ['src', join(sqlDir, `pgmorbac--${version}.sql`)], { cwd: root, stdio: 'inherit' }); +const upgrades = join(root, 'src', 'upgrades'); +if (existsSync(upgrades)) { + for (const f of readdirSync(upgrades).filter((n) => n.endsWith('.sql'))) copyFileSync(join(upgrades, f), join(sqlDir, f)); +}