diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d90d339 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + node: + name: Build ${{ matrix.project }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + project: [pg-manual, pg-node, pg-sveltekit] + defaults: + run: + working-directory: ${{ matrix.project }} + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 22 + cache: npm + cache-dependency-path: ${{ matrix.project }}/package-lock.json + + - name: Install from lockfile + run: npm ci + + - name: Build + run: npm run build --if-present + + - name: Check + run: npm run check --if-present + + - name: Lint + run: npm run lint --if-present + + dotnet: + name: Build pg-dotnet + runs-on: ubuntu-latest + defaults: + run: + working-directory: pg-dotnet + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-dotnet@v6 + with: + dotnet-version: | + 8.0.x + 10.0.x + + - name: Restore from lockfile + run: dotnet restore --locked-mode + + - name: Build + run: dotnet build --no-restore diff --git a/.github/workflows/sdk-canary.yml b/.github/workflows/sdk-canary.yml new file mode 100644 index 0000000..549e842 --- /dev/null +++ b/.github/workflows/sdk-canary.yml @@ -0,0 +1,111 @@ +name: SDK canary + +# Builds every example against the LATEST published PostGuard SDKs instead of +# the pinned lockfile versions, so we hear about a breaking SDK release before +# a reader of docs.postguard.eu does. + +on: + schedule: + - cron: '0 6 * * 1' + workflow_dispatch: + +permissions: + contents: read + +jobs: + node: + name: Canary ${{ matrix.project }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - project: pg-manual + package: '@e4a/pg-wasm' + - project: pg-node + package: '@e4a/pg-js' + - project: pg-sveltekit + package: '@e4a/pg-js' + defaults: + run: + working-directory: ${{ matrix.project }} + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 22 + + - name: Install latest ${{ matrix.package }} + run: npm install "${{ matrix.package }}@latest" + + - name: Report resolved version + run: node -p "require('./node_modules/${{ matrix.package }}/package.json').version" + + - name: Build + run: npm run build --if-present + + - name: Check + run: npm run check --if-present + + - name: Lint + run: npm run lint --if-present + + dotnet: + name: Canary pg-dotnet + runs-on: ubuntu-latest + defaults: + run: + working-directory: pg-dotnet + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-dotnet@v6 + with: + dotnet-version: | + 8.0.x + 10.0.x + + - name: Install latest E4A.PostGuard + run: dotnet add package E4A.PostGuard + + - name: Build + run: dotnet build + + report: + name: Report failure + runs-on: ubuntu-latest + needs: [node, dotnet] + if: failure() + permissions: + contents: read + issues: write + steps: + - name: Open or update the canary issue + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + set -euo pipefail + + gh label create sdk-canary \ + --description 'Opened by the weekly SDK canary' \ + --color D93F0B --force + + title='SDK canary failed against the latest published SDKs' + body=$(cat <enable enable a6cc2768-9070-459f-8eed-ea999be6e096 + true diff --git a/pg-dotnet/packages.lock.json b/pg-dotnet/packages.lock.json new file mode 100644 index 0000000..002a96e --- /dev/null +++ b/pg-dotnet/packages.lock.json @@ -0,0 +1,223 @@ +{ + "version": 1, + "dependencies": { + "net10.0": { + "E4A.PostGuard": { + "type": "Direct", + "requested": "[0.4.1, )", + "resolved": "0.4.1", + "contentHash": "Uo2I1+WJ1JjXHzT51wXIiuTCgR8InNpat0G+H+Fp1g2bDW9cC9ou0unxT2gtIXP0PuDXpUGv98AZqs7y/h623g==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "bVGqctAfPGfTxJvNp8pMshtvpsUj6r6JkeiCNVIGVYO5gBxuxdN0Lbr25kEvE/zXdctkEc44g8HssnPgDnFGVA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "6XTfFOnf27WY8kEeZkTZ4YNn0t+imgvdQ0YaAdR4vgURKATo9bCaVJ1KB71IOJAQtJP7Elb53VHlTNXg2CtSsA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "I63esIFbL3h5pSt7gXpXOlmcwDmYBUoYNEglKfDPFUqtYvSV84f2l28hO2lfVXsV0wdlplgAM7IVz16matapSg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "1g9mzuu8gIHkjYb0jLxOTQVl/QDG5nn0b0JzgT/gbgNKr6gXZzxOHRAsdYRc1eDApB7LdHR8uK5vQrNjIQdRrQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "KLtAZ6A38s1pIfCO2ns6aG14NNGMYNZ4PBYfFK4M+R4A+xuSc6oklhqDcpHZxvDpyBWeFtR5C8iQBw2ng8tUHQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "U+oquaPxFdY8lYeEIWO/AD7jDIl9sPW6aVWMQRHU/pZ/SWpLcOrAj2fcLe1HwXl4sYw1ONI56K/eELT3xr4RRQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "GkPvQe6IdidLu6Q3Lw6+B8NJpW8feW8czZ5mBKt5rXM/x8MvZfEp5WvAsjznzDGd23chIDrW0b2mmt+ScnEgiw==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileSystemGlobbing": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "IUQet3SY51xIFcFZKtAB6a54/Zdxs7T3SQ84kJtOD6yeXfZgiOMksACWD5qtTmXGQGFH4QYGBOT0KIO8Uy/dJw==" + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "OBPo4nYhMyIbtueoC10CBm6AGAbo/A9IV8QQ/6ryZS7VvmqpGT7hunazeHLxFawRzn3oLOq4jhqhpBX4tfswWQ==" + } + }, + "net8.0": { + "E4A.PostGuard": { + "type": "Direct", + "requested": "[0.4.1, )", + "resolved": "0.4.1", + "contentHash": "Uo2I1+WJ1JjXHzT51wXIiuTCgR8InNpat0G+H+Fp1g2bDW9cC9ou0unxT2gtIXP0PuDXpUGv98AZqs7y/h623g==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "bVGqctAfPGfTxJvNp8pMshtvpsUj6r6JkeiCNVIGVYO5gBxuxdN0Lbr25kEvE/zXdctkEc44g8HssnPgDnFGVA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "6XTfFOnf27WY8kEeZkTZ4YNn0t+imgvdQ0YaAdR4vgURKATo9bCaVJ1KB71IOJAQtJP7Elb53VHlTNXg2CtSsA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "I63esIFbL3h5pSt7gXpXOlmcwDmYBUoYNEglKfDPFUqtYvSV84f2l28hO2lfVXsV0wdlplgAM7IVz16matapSg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "1g9mzuu8gIHkjYb0jLxOTQVl/QDG5nn0b0JzgT/gbgNKr6gXZzxOHRAsdYRc1eDApB7LdHR8uK5vQrNjIQdRrQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "KLtAZ6A38s1pIfCO2ns6aG14NNGMYNZ4PBYfFK4M+R4A+xuSc6oklhqDcpHZxvDpyBWeFtR5C8iQBw2ng8tUHQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "U+oquaPxFdY8lYeEIWO/AD7jDIl9sPW6aVWMQRHU/pZ/SWpLcOrAj2fcLe1HwXl4sYw1ONI56K/eELT3xr4RRQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "GkPvQe6IdidLu6Q3Lw6+B8NJpW8feW8czZ5mBKt5rXM/x8MvZfEp5WvAsjznzDGd23chIDrW0b2mmt+ScnEgiw==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileSystemGlobbing": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "IUQet3SY51xIFcFZKtAB6a54/Zdxs7T3SQ84kJtOD6yeXfZgiOMksACWD5qtTmXGQGFH4QYGBOT0KIO8Uy/dJw==" + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "OBPo4nYhMyIbtueoC10CBm6AGAbo/A9IV8QQ/6ryZS7VvmqpGT7hunazeHLxFawRzn3oLOq4jhqhpBX4tfswWQ==" + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "STVNTIVd+UrYvSo31D2tOOTs19IThdjGDN14FS3/NZb4PWsVakAg/VT4sq+JYWGP/GtazzvXwebWL6LPBAwnFQ==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "HvFIJXM/CMTRu6PBQmjukQZ/O32Vx5fOEYZs0kq0OD5s9vJQ19KHLWCVmnfh3gNC7pbYomm06tzOTgJBflr/nQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "/fysUDkD7oFGaRPoA7IaFs0wRoO3GlwlCNq2P+xWZqxLy1R4cktRSKfMjJDy9ymS4grL7IDVdt8de8L9a0z55Q==", + "dependencies": { + "System.IO.Pipelines": "10.0.8", + "System.Text.Encodings.Web": "10.0.8" + } + } + } + } +} \ No newline at end of file diff --git a/pg-manual/check/sdk-exports.js b/pg-manual/check/sdk-exports.js new file mode 100644 index 0000000..b3014cf --- /dev/null +++ b/pg-manual/check/sdk-exports.js @@ -0,0 +1,17 @@ +// Static-import probe for the @e4a/pg-wasm exports the examples rely on. +// +// examples/string.js and examples/file.js reach the SDK through a dynamic +// `import('@e4a/pg-wasm')` and destructure at runtime, which webpack cannot +// analyse — a removed or renamed export still compiles cleanly. Importing the +// same names statically here puts them back under webpack's export analysis, +// so `npm run check` fails when the SDK drops one. +// +// Keep this list in step with what the examples destructure. A new name goes in +// both places below: webpack elides an unreferenced import, so adding one to the +// import alone probes nothing and leaves the check passing. +import { seal, sealStream, Unsealer, StreamUnsealer } from '@e4a/pg-wasm' + +// Referencing the bindings keeps them from being elided before the check runs. +for (const [name, binding] of Object.entries({ seal, sealStream, Unsealer, StreamUnsealer })) { + if (binding === undefined) throw new Error(`@e4a/pg-wasm no longer exports ${name}`) +} diff --git a/pg-manual/check/webpack.config.js b/pg-manual/check/webpack.config.js new file mode 100644 index 0000000..5a384b7 --- /dev/null +++ b/pg-manual/check/webpack.config.js @@ -0,0 +1,22 @@ +// Build config for `npm run check`. Separate from webpack.config.js so the +// probe never lands in dist/ and CleanWebpackPlugin never runs against it. +const path = require('path') + +const root = path.resolve(__dirname, '..') + +module.exports = { + name: 'sdk-exports', + mode: 'development', + entry: path.resolve(__dirname, 'sdk-exports.js'), + output: { + path: path.resolve(root, 'node_modules/.cache/sdk-exports'), + filename: 'sdk-exports.js', + }, + experiments: { + asyncWebAssembly: true, + topLevelAwait: true, + }, + resolve: { + modules: [path.resolve(root, 'node_modules')], + }, +} diff --git a/pg-manual/package.json b/pg-manual/package.json index d1fa3a2..fa33d66 100644 --- a/pg-manual/package.json +++ b/pg-manual/package.json @@ -30,7 +30,8 @@ "util": "^0.12.5" }, "scripts": { - "build": "webpack", + "build": "webpack --fail-on-warnings", + "check": "webpack --config check/webpack.config.js --fail-on-warnings", "dev": "npx webpack serve" }, "overrides": { diff --git a/pg-node/package.json b/pg-node/package.json index feb05d8..131bc43 100644 --- a/pg-node/package.json +++ b/pg-node/package.json @@ -8,7 +8,8 @@ "scripts": { "start": "node --env-file-if-exists=.env index.mjs", "send": "node --env-file-if-exists=.env index.mjs", - "upload": "node --env-file-if-exists=.env index.mjs --upload-only" + "upload": "node --env-file-if-exists=.env index.mjs --upload-only", + "check": "node --check index.mjs && node --input-type=module -e \"await import('./src/encryption.mjs')\"" }, "dependencies": { "@e4a/pg-js": "^1.10.0"