diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 00000000..9ba0e0ce --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,7 @@ +--- +exclude_paths: + - "docs/**" + - "coverage/**" + - "dist/**" + - ".yarn/**" + - "node_modules/**" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 241c91d3..36855d48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,9 @@ -name: CI & Quality Gate - on: workflow_dispatch: push: branches: - main + - develop paths: - 'packages/**' - 'bin/**' @@ -14,7 +13,10 @@ on: - 'turbo.json' - '.github/workflows/ci.yml' pull_request: - types: [opened, synchronize, reopened] + types: [opened, synchronize, reopened, labeled] + branches: + - main + - develop paths: - 'packages/**' - 'bin/**' @@ -112,7 +114,7 @@ jobs: name: Publish Independent Packages needs: sonarcloud runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + if: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') permissions: contents: write packages: write @@ -173,15 +175,44 @@ jobs: run: | git config --global user.name "github-actions[bot]" git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" - git pull --rebase origin main + BRANCH="${GITHUB_REF_NAME:-main}" + git pull --rebase origin "$BRANCH" --autostash - name: Publish all changed packages - run: yarn publish:all + run: | + if [ "${{ github.ref }}" = "refs/heads/develop" ]; then + yarn publish:all --tag beta --beta + else + yarn publish:all + fi + + - name: Sync with remote to prevent push rejection + run: | + BRANCH="${GITHUB_REF_NAME:-main}" + git pull --rebase origin "$BRANCH" --autostash - name: Commit version bumps & hashes + id: auto_commit uses: stefanzweifel/git-auto-commit-action@v5 # NOSONAR with: commit_message: "chore: release versions & update hashes [skip ci]" commit_options: '--no-verify' + - name: Create and push temporal release tag on main + if: github.ref == 'refs/heads/main' && steps.auto_commit.outputs.changes_detected == 'true' + run: | + git fetch --tags origin + DATE_TAG="v$(date -u +'%Y.%m.%d')" + TAG="$DATE_TAG" + COUNTER=1 + while git rev-parse "$TAG" >/dev/null 2>&1; do + TAG="${DATE_TAG}.${COUNTER}" + COUNTER=$((COUNTER + 1)) + done + echo "Creating temporal release tag: $TAG" + git tag -a "$TAG" -m "Release $TAG [skip ci]" + git push origin "$TAG" + + + diff --git a/.github/workflows/docs-pipeline.yml b/.github/workflows/docs-pipeline.yml index 1bb60a3d..2fe30dd3 100644 --- a/.github/workflows/docs-pipeline.yml +++ b/.github/workflows/docs-pipeline.yml @@ -5,6 +5,14 @@ on: push: branches: - main + - develop + paths: + - 'docs/**' + - 'scripts/build-docs.ts' + pull_request: + branches: + - main + - develop paths: - 'docs/**' - 'scripts/build-docs.ts' @@ -30,6 +38,7 @@ jobs: fetch-depth: 0 - name: Log in to the Container registry + if: github.event_name != 'pull_request' uses: docker/login-action@v3 # NOSONAR with: registry: ${{ env.REGISTRY }} @@ -57,8 +66,8 @@ jobs: with: context: . file: docs/ContainerFile - platforms: linux/amd64,linux/arm64 - push: true + platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }} + push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha diff --git a/.github/workflows/publish-dev.yml b/.github/workflows/publish-dev.yml index b18014a3..7a7a3765 100644 --- a/.github/workflows/publish-dev.yml +++ b/.github/workflows/publish-dev.yml @@ -61,8 +61,8 @@ jobs: exit 1 fi - - name: Publish all changed packages (latest-dev) - run: yarn node bin/publish_all.js --tag latest-dev + - name: Publish all changed packages (beta) + run: yarn node bin/publish_all.js --tag beta --beta - name: Sync with remote to prevent push rejection run: | diff --git a/.gitignore b/.gitignore index 3425c1c2..2544bf4b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ packages/**/.*.txt !bin/**/*.js !**/bin/**/*.js !/*.js +!docs/*.js +!docs/pages/_meta.js *.tsbuildinfo # Generated App @@ -37,5 +39,9 @@ packages/**/.*.txt .turbo/ **/.turbo/ +docs/.next/ +docs/out/ +docs/.yarn/ firestore-debug.log firebase-debug.log +.node-persist/ diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 20daccbc..a6bd9957 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -1,7 +1,10 @@ # .markdownlint.yaml # Tell markdownlint to allow 2 spaces after a list marker, matching Prettier's style. MD030: - ul_single: 2 - ol_single: 2 - ul_multi: 2 - ol_multi: 2 + ul_single: 2 + ol_single: 2 + ul_multi: 2 + ol_multi: 2 + +# Disable line length rule for markdown files +MD013: false diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 00000000..097760b5 --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1,6 @@ +# Documentation build artifacts and generated pages +docs/** +coverage/** +dist/** +node_modules/** +.yarn/** diff --git a/.version_hashes.json b/.version_hashes.json index 3eb6d615..34df7ad3 100644 --- a/.version_hashes.json +++ b/.version_hashes.json @@ -1,23 +1,23 @@ { "@quatrain/auth-firebase": { - "version": "1.2.9", - "hash": "f7lurMdVwemvga4U8TzUvi2SWuw=-60479b3216eb43a6191f8edcddbf4eac5201e410f9549ac2ceac5bd1641b241c", - "last_published": "2026-07-28T14:17:21.880Z" + "version": "1.2.10-beta.0", + "hash": "f7lurMdVwemvga4U8TzUvi2SWuw=-059b1042ca835f2b792c435f75d29311d27737ec0e4ade7355d981b55054ecf6", + "last_published": "2026-09-23T12:42:17.482Z" }, "@quatrain/auth-supabase": { - "version": "1.2.11", - "hash": "p4TucOdTDiqdYUm/oPlt9UooZFw=-e04385758a988fbfb83686a86f0934c2c347aa987b951db3123153c53d020f87", - "last_published": "2026-09-14T11:39:51.134Z" + "version": "1.2.12-beta.0", + "hash": "2Wbiv3UWtBKuUpN4F7BmX/EotkU=-b053eba31c314ffcb5354364b98d6cd28517ea7236605dd1d849fd4a539f2aa1", + "last_published": "2026-09-23T12:42:40.600Z" }, "@quatrain/backend": { - "version": "1.2.20", - "hash": "Ix1CDJ2BtToJx99VKpBfL4ILCwI=-3d22d87bc013e2ec06506930c274c8dd325298e8765600c284d8b85f7502b2cb", - "last_published": "2026-09-07T16:13:02.993Z" + "version": "1.2.21-beta.0", + "hash": "iDmoexJU0JJ9FTxxvzWT9FRcUMs=-989fdce0cdb7d3088b6275bac4746949a2924ec968edce66884982c396b1adb6", + "last_published": "2026-09-24T08:15:13.502Z" }, "@quatrain/backend-firestore": { - "version": "1.2.15", - "hash": "8LN7250Ph6JGynCUUfhY1E9MLow=-72b2e6d274840afe0ab759994144415b49603d830ce417c53a7b000b097185bc", - "last_published": "2026-09-07T16:01:37.121Z" + "version": "1.2.16-beta.0", + "hash": "8LN7250Ph6JGynCUUfhY1E9MLow=-46a1dcd9c9bf467973547f1c3eb5712364636fd9848ce26569a992da42840fba", + "last_published": "2026-09-23T12:42:49.616Z" }, "@quatrain/backend-postgres": { "version": "1.2.13", @@ -25,24 +25,24 @@ "last_published": "2026-06-08T20:16:29.045Z" }, "@quatrain/backend-sqlite": { - "version": "1.1.9", - "hash": "W7TZXtxU9jVWPS4Y0G3vonwanYM=-df00ae275320e080c1063ffab75fe8a6ba5b040a7391a88455a8b0080c4f7fdc", - "last_published": "2026-09-07T16:01:37.124Z" + "version": "1.1.10-beta.0", + "hash": "W7TZXtxU9jVWPS4Y0G3vonwanYM=-c0d556d8457b7f7d986c5d74910ab3319aeb09cee9702ffb8614efa99689fbc7", + "last_published": "2026-09-23T12:43:05.385Z" }, "@quatrain/cloudwrapper-firebase": { - "version": "1.2.4", - "hash": "5SkykCLCXGhbfosOHTYGMrCTSyg=-4b482271660c32366afcb9ed0c5dcb19005e9ec494b92723a413486ba51d52df", - "last_published": "2026-09-07T16:01:37.123Z" + "version": "1.2.5-beta.0", + "hash": "5SkykCLCXGhbfosOHTYGMrCTSyg=-d3ff97cae91a20e9d05ddcce387171312b40b2c3e4deabe07a0690258066424e", + "last_published": "2026-09-23T12:43:28.535Z" }, "@quatrain/cloudwrapper-supabase": { - "version": "1.2.4", - "hash": "YAQ9/OXq8JnCRcuthoBBB4EWo6A=-4306eb20c08b57d5ee71ae9eda1bd01b6c46bf62b7426b986fa4a5870660fe59", - "last_published": "2026-06-29T08:49:22.643Z" + "version": "1.2.5-beta.0", + "hash": "YAQ9/OXq8JnCRcuthoBBB4EWo6A=-cfdd862f2e3661d5aedd6c78c505aca8975e99202de599681e1c5427a4f51008", + "last_published": "2026-09-23T12:43:35.547Z" }, "@quatrain/core": { - "version": "1.2.18", - "hash": "Rmvr9YZZ3beBDvdAk71N759nBCY=-2bfd99d899b33e4ed27f8c16af561e0632f4750d893e11ccfe4c2c0674bc3866", - "last_published": "2026-08-12T15:12:52.960Z" + "version": "1.2.19-beta.0", + "hash": "7fHzH2xtc+JcykELdDxj2Op0G94=-2bfd99d899b33e4ed27f8c16af561e0632f4750d893e11ccfe4c2c0674bc3866", + "last_published": "2026-09-23T12:44:03.938Z" }, "@quatrain/log": { "version": "1.2.6", @@ -50,59 +50,59 @@ "last_published": "2026-09-07T16:13:23.016Z" }, "@quatrain/messaging": { - "version": "1.1.5", - "hash": "dUV5HY5JKFnutv54dra3IV3EAvM=-89db897311adc029139fe658207ffef749d071594a39967d975c5d9122bba613", - "last_published": "2026-06-08T09:53:38.070Z" + "version": "1.1.6-beta.0", + "hash": "dUV5HY5JKFnutv54dra3IV3EAvM=-762d6b8c9b337eeccef11dbd209d932359323611583845acfb38a54f2bdb217d", + "last_published": "2026-09-23T12:44:40.794Z" }, "@quatrain/messaging-firebase": { - "version": "1.1.3", - "hash": "qM6DYVwicC+wal6gZ/xd58y602I=-3ab71109452234101ed6c0f2836c58d6575bc73a8eed31e552d5a3d72df52190", - "last_published": "2026-05-21T19:44:23.520Z" + "version": "1.1.4-beta.0", + "hash": "qM6DYVwicC+wal6gZ/xd58y602I=-d992870d3bd819f8cebc8999423608bbbcb988500149f9d74746fc19b81fdf21", + "last_published": "2026-09-23T12:44:47.770Z" }, "@quatrain/queue": { - "version": "1.2.3", - "hash": "dvIZU/fnLk0Dn1uWxR+rkWgk9O0=-54cf0d32a3bf3f31ca0965c9a586ef87ada0cb43a2efcb113a2e018efa56adcf", - "last_published": "2026-05-11T13:56:40.832Z" + "version": "1.2.4-beta.0", + "hash": "dvIZU/fnLk0Dn1uWxR+rkWgk9O0=-0145ae9aade51d99deae733294873fd28c0aa04ff0cc88c6365df7328cfe38ba", + "last_published": "2026-09-23T12:44:56.767Z" }, "@quatrain/queue-amqp": { - "version": "1.3.4", - "hash": "5dGYf9oXcEBIrv49cQrvQDQ81H0=-5ade041e47a9c65c5262bc94f0540436116b0f979632609e58fd652abeea016d", - "last_published": "2026-05-21T13:11:28.437Z" + "version": "1.3.5-beta.0", + "hash": "5dGYf9oXcEBIrv49cQrvQDQ81H0=-5cee78ff2b716f8ef2589f385997e730851a0231b1a5c8d66f22e7a6a861add3", + "last_published": "2026-09-23T12:45:03.925Z" }, "@quatrain/queue-aws": { - "version": "1.2.1", - "hash": "Sb2VLdVF70DE3mj5iJqIOFSPmc4=-d2676c295551f84d9fe6988d6866655d4551c8a29c29cd1f3df88a65d047da9a", - "last_published": "2026-05-11T13:56:53.824Z" + "version": "1.2.2-beta.0", + "hash": "Sb2VLdVF70DE3mj5iJqIOFSPmc4=-5bed3a6ceede00eddf798134ae0920d6addf5317c122877ccfd0cd47632519b6", + "last_published": "2026-09-23T12:45:10.949Z" }, "@quatrain/queue-gcp": { - "version": "1.2.1", - "hash": "bshcAtDwVy0bomU0+yfZzLH8hVE=-26a248a34bc0ae23907019542530c9647ed25e470cf43fa207a7dc6ed7be7efd", - "last_published": "2026-05-11T13:57:00.598Z" + "version": "1.2.2-beta.0", + "hash": "NbWOZ1NGpDEShUQ0JRgskW/Ix5s=-ede908afad5413a3455e763405a9968c569259da8aba7354d8c5886cb0904067", + "last_published": "2026-09-23T12:45:18.265Z" }, "@quatrain/storage": { - "version": "1.2.14", - "hash": "w3GFXaZFWhVI+vJgkW3TqWu9hXQ=-ae4887d91ab0e8a0b6ed04240c66e6718d1769816a4bc4a32e5b2868e7a10f97", - "last_published": "2026-08-27T11:26:20.729Z" + "version": "1.2.15-beta.0", + "hash": "w3GFXaZFWhVI+vJgkW3TqWu9hXQ=-896e79f2ebcb9f11cb5093ef975c1f04fd5576e000ddc86a85f424d0069bf8ef", + "last_published": "2026-09-23T12:45:42.751Z" }, "@quatrain/storage-firebase": { - "version": "1.2.4", - "hash": "PYwf+gcXe6XvweIREusALauJon0=-54d4b037a1e386e07c739834236779884d52c7952dbe03e8f4b2930feab558f8", - "last_published": "2026-07-06T10:17:25.143Z" + "version": "1.2.5-beta.0", + "hash": "PYwf+gcXe6XvweIREusALauJon0=-b1e0c96b29731c3fa78227266bc96a545529e9eeb148fe261838b29b53a94870", + "last_published": "2026-09-23T12:45:50.085Z" }, "@quatrain/storage-s3": { - "version": "1.2.9", - "hash": "gmuFDALrvoYxrjF8gD0b9ny+Prw=-56bb978ff5dcebcf9b944df96f3a626265091957d2b036fec8f508391c2095df", - "last_published": "2026-09-16T08:53:46.078Z" + "version": "1.2.10-beta.0", + "hash": "gmuFDALrvoYxrjF8gD0b9ny+Prw=-5592b44b2f3073fd46e5b886d275dc4ad3561e0e98a599fc6813c5c169f8cb11", + "last_published": "2026-09-23T16:14:42.517Z" }, "@quatrain/storage-supabase": { - "version": "1.2.11", - "hash": "PFIgolKCf5csB4oF7OhnUDeFfmE=-7e39ea528a70e9e9ed244d312b00bbdbc8bee47320eb0f8adc355392548775b2", - "last_published": "2026-09-16T08:53:49.504Z" + "version": "1.2.12-beta.0", + "hash": "PFIgolKCf5csB4oF7OhnUDeFfmE=-a750f1a6b38b835a1489f5439bdbae8b336973799a351705362d97515b3f3f98", + "last_published": "2026-09-23T16:14:48.990Z" }, "@quatrain/worker": { - "version": "1.2.14", - "hash": "Q8kF5hw7NTnkLaxQ2BVhFH1grIA=-6a24d4d55096199c8e1595628a09a21cd2e1d37961c69de27aec09b85d2f60df", - "last_published": "2026-08-14T12:17:17.237Z" + "version": "1.2.15-beta.0", + "hash": "Q8kF5hw7NTnkLaxQ2BVhFH1grIA=-6f53143406bdc785268690c0b8fe99e329613ce2a5e54e962be4f988cc845e4c", + "last_published": "2026-09-23T12:46:21.595Z" }, "@quatrain/testing": { "version": "1.1.4", @@ -110,14 +110,14 @@ "last_published": "2026-05-11T13:57:47.849Z" }, "@quatrain/ai": { - "version": "1.1.4", - "hash": "S6h8JV5Mftl5Ts8o4296DbCfjSg=-945b7ff140714a5af21cfc9e299d56da0c457deb860eadabbb762f1388fb9a23", - "last_published": "2026-07-19T14:46:21.821Z" + "version": "1.1.5-beta.0", + "hash": "S6h8JV5Mftl5Ts8o4296DbCfjSg=-b463e9192e9d14f8d13fc16728ee6c26e826730dbacbcef4f31822455935b89b", + "last_published": "2026-09-23T12:41:30.831Z" }, "@quatrain/ai-gemini": { - "version": "1.1.5", - "hash": "b9FoJDkRA+BcIrHH2OIiy6Gwp6U=-11ce21da74fe1e7e3d4ac74640c393743456e3860ac5beff9c3e0c3140b6eb2b", - "last_published": "2026-07-26T10:06:47.679Z" + "version": "1.1.6-beta.0", + "hash": "b9FoJDkRA+BcIrHH2OIiy6Gwp6U=-cb944e9ec485a3679f20da4b2bea9ae6ed16a69b599e48b4960c08a803b6f534", + "last_published": "2026-09-23T12:41:37.627Z" }, "@quatrain/api": { "version": "1.1.8", @@ -135,9 +135,9 @@ "last_published": "2026-07-19T14:46:35.236Z" }, "@quatrain/app": { - "version": "1.1.22", - "hash": "LQ1zWtuuJcQg391kH5TP5SsQdaw=-79ddbc50e793edbbc6bfe33d5dab6b0a6cd9ffa4a314026cef8e9d15534e8806", - "last_published": "2026-07-21T20:51:02.445Z" + "version": "1.1.23-beta.0", + "hash": "N2njdpD/OVyRGnBFdSh+Eky+qFY=-79ddbc50e793edbbc6bfe33d5dab6b0a6cd9ffa4a314026cef8e9d15534e8806", + "last_published": "2026-09-23T12:42:02.920Z" }, "@quatrain/auth-oidc": { "version": "1.1.5", @@ -155,14 +155,14 @@ "last_published": "2026-05-24T08:09:53.224Z" }, "@quatrain/code": { - "version": "1.1.3", - "hash": "rMT77UCm9T1AnAKNq+Lf9fWGER4=-945b7ff140714a5af21cfc9e299d56da0c457deb860eadabbb762f1388fb9a23", - "last_published": "2026-05-11T13:55:41.253Z" + "version": "1.1.4-beta.0", + "hash": "rMT77UCm9T1AnAKNq+Lf9fWGER4=-b463e9192e9d14f8d13fc16728ee6c26e826730dbacbcef4f31822455935b89b", + "last_published": "2026-09-23T12:43:42.345Z" }, "@quatrain/code-github": { - "version": "1.1.3", - "hash": "70n/BsfeY4DOqu5W3Ovwzec1f0Q=-66723c85c1cee926dd0b4a09f456dc8d6ca90416033e28d2d1859a15fa2e1ca5", - "last_published": "2026-05-11T13:55:47.815Z" + "version": "1.1.4-beta.0", + "hash": "70n/BsfeY4DOqu5W3Ovwzec1f0Q=-13361419be434396c2dda4ca1bfb3a2a6a7b60e95baffd9d508c0ea452028d88", + "last_published": "2026-09-23T12:43:49.509Z" }, "@quatrain/core-cli": { "version": "1.1.7", @@ -195,9 +195,9 @@ "last_published": "2026-05-21T20:56:34.520Z" }, "@quatrain/backend-restapi": { - "version": "1.0.3", - "hash": "FE5fNLVjXFIndZlLFNPBGL2DkDE=-2cbab5719ee8dee03c34bdea351ddf77e5cf5c6d83e2dacaa15f54bea5b863f4", - "last_published": "2026-05-11T13:54:46.539Z" + "version": "1.0.4", + "hash": "MXZAtAlrQkKnAg/7eeRCZXaP++s=-2cbab5719ee8dee03c34bdea351ddf77e5cf5c6d83e2dacaa15f54bea5b863f4", + "last_published": "2026-09-17T07:19:06.577Z" }, "@quatrain/backend-restapi-recipes": { "version": "1.0.3", @@ -275,9 +275,9 @@ "last_published": "2026-09-07T15:16:18.269Z" }, "@quatrain/auth-http-basic": { - "version": "1.0.6", - "hash": "jx9zKbVrMRVD7ZbuKr+lOyWqylU=-02660f7f41b77744d7d8383b58c03866adc9a5ce75616fdb4e5578d4a6c7bdf6", - "last_published": "2026-06-27T09:12:03.861Z" + "version": "1.0.7", + "hash": "tS9oxYugcAErQyrqJR0GUiRXwcM=-02660f7f41b77744d7d8383b58c03866adc9a5ce75616fdb4e5578d4a6c7bdf6", + "last_published": "2026-09-17T07:18:31.336Z" }, "@quatrain/http": { "version": "1.0.5", @@ -305,14 +305,14 @@ "last_published": "2026-07-26T20:12:54.259Z" }, "@quatrain/auth": { - "version": "1.2.12", - "hash": "yvrMIKrmn458Rubu++sSONDtqJw=-d0d6129f17d28d2a1ff423366d1ca0f2a5f35a06fa6f9e90aa739e6beb1c0577", - "last_published": "2026-09-14T11:39:40.146Z" + "version": "1.2.13-beta.0", + "hash": "0In59QbOlvTkhAg1WMBYyb4YcFQ=-7dcf5649d363751505f90463684c588c3c776e731aad9e5408b9271d4a722074", + "last_published": "2026-09-23T12:42:09.983Z" }, "@quatrain/cloudwrapper": { - "version": "1.2.3", - "hash": "L5QnyW8EKb7BOPU+6fIGh7RfJVc=-bea403aabe76a5bfa514bf74d31c673f48f9c2c39a9c23e7b9c1515a97ecaef5", - "last_published": "2026-06-29T09:34:37.314Z" + "version": "1.2.4-beta.0", + "hash": "L5QnyW8EKb7BOPU+6fIGh7RfJVc=-35e81a1214bf8f1ec8f27e34d895a8322d248a7a5e52d4708aa9336c8c86b5ed", + "last_published": "2026-09-23T12:43:21.470Z" }, "@quatrain/okf": { "version": "1.0.5", @@ -365,9 +365,9 @@ "last_published": "2026-09-07T16:01:37.124Z" }, "@quatrain/auth-github": { - "version": "1.2.11", - "hash": "bz+s09FsFbPUF2uLYFeR+cDRiIc=-a0b3e503b5b08c29a87afccdb512dc511da90677d8313971f50c0e695f3b68ac", - "last_published": "2026-07-28T14:17:40.211Z" + "version": "1.2.12-beta.0", + "hash": "bz+s09FsFbPUF2uLYFeR+cDRiIc=-553d0ec56b0908a13e33c2e365cad36a64a983342f68d8bc40189aa21637961c", + "last_published": "2026-09-23T12:42:24.491Z" }, "@quatrain/git-client": { "version": "1.0.1", @@ -390,8 +390,23 @@ "last_published": "2026-09-02T10:18:50.482Z" }, "@quatrain/queue-mqtt": { - "version": "1.0.1", - "hash": "zCXrNUKk5GZcq8pFjLsOLcm6MZw=-6f5c7cd1ad82ce6573ef49e05175541286568d0e6d0bb208e38f6387e3659620", - "last_published": "2026-09-02T10:18:57.526Z" + "version": "1.0.2-beta.0", + "hash": "zCXrNUKk5GZcq8pFjLsOLcm6MZw=-373e618dac28b33968c32f2d03bd8a0f7662ecc054505c16baa3d009e04acf74", + "last_published": "2026-09-23T12:45:25.091Z" + }, + "@quatrain/auth-rbac": { + "version": "1.0.0-beta.2", + "hash": "GjiCTrnUduu5nPOdHps5n1+srnY=-add1f403a9032c9789ae8d72cf45f315a782097286c3eb1a5e327fe62a5e495c", + "last_published": "2026-09-24T08:15:04.868Z" + }, + "@quatrain/ai-openai": { + "version": "1.1.1", + "hash": "9M066JSMdoXvjUaS7U2CSNXeqPc=-20413853d8835297eb401969acab442c5a1bb9ce6726b2cb16430ff81c4a92eb", + "last_published": "2026-09-20T09:55:39.258Z" + }, + "@quatrain/config": { + "version": "1.0.1-beta.0", + "hash": "xu0o3Bi5ov8vAW7v9mP5tnacPKg=-e33b4920f809ae455ffa1355e141d5a18938984aa147bbd0140f9aef55581695", + "last_published": "2026-09-23T12:43:56.379Z" } } \ No newline at end of file diff --git a/GEMINI.md b/GEMINI.md index c924dd15..53f5147e 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -19,13 +19,21 @@ If the task is to construct or design application schemas, models, repositories, If the task requires editing, refactoring, fixing, or extending the packages, monorepo workspaces, containers, or CI/CD pipelines of the **Quatrain Core repository itself**, follow the strict guidelines in: 👉 [CONTRIBUTING_AGENTS.md (Monorepo Contributing Guidelines)](./guidelines/CONTRIBUTING_AGENTS.md) +### 3. GitFlow Lifecycle & Pull Requests +All contributions follow the GitFlow lifecycle defined in [AGENTS.okf](file:///Users/crapougnax/CODE/CRAPOUGNAX/AGENTS.okf/content/workflow/gitflow-protocol.md): +- Active integration branch: `develop` +- Branching: `feat/-` or `fix/-` strictly from `develop` +- Pull Requests: Target `--base develop` via `gh pr create` + --- ## 👤 Personal Rules & Custom Settings -In addition to the framework-level guidelines, you MUST adhere to the user's personal coding preferences, custom shortcuts, and general development rules published in their dedicated Gist: +In addition to the framework-level guidelines, you MUST adhere to the user's personal coding preferences, custom shortcuts, and general development rules published in: +👉 [AGENTS.okf Knowledge Base](file:///Users/crapougnax/CODE/CRAPOUGNAX/AGENTS.okf/content/index.md) | [GitHub Repository](https://github.com/crapougnax/AGENTS.okf) 👉 [Gist: Personal Gemini Rules & Instructions](https://gist.github.com/crapougnax/47971b85aa73dd702f4372a89858111c) + --- ## 🛑 Strict TypeScript & Code Quality Rules diff --git a/bin/publish_all.js b/bin/publish_all.js index d9035a18..f8fe186b 100644 --- a/bin/publish_all.js +++ b/bin/publish_all.js @@ -1,5 +1,6 @@ const fs = require('node:fs'); const path = require('node:path'); +const crypto = require('node:crypto'); const { spawnSync } = require('node:child_process'); const { computePackageHash, getDepsHash } = require('./hashUtils'); @@ -75,6 +76,13 @@ async function publishAll() { const previousDataMap = {}; let anyPackageChanged = false; + const forceBuild = process.argv.includes('--force'); + const tagArgIndex = process.argv.indexOf('--tag'); + const isBeta = process.argv.includes('--beta') || (tagArgIndex !== -1 && process.argv[tagArgIndex + 1] === 'beta'); + const defaultTag = isBeta ? 'beta' : 'latest'; + const npmTag = tagArgIndex !== -1 ? process.argv[tagArgIndex + 1] : defaultTag; + const tagString = npmTag ? `--tag ${npmTag}` : ''; + console.log('[PREPARE] Computing stable hashes prior to build...'); for (const pkg of packages) { const pkgDir = getPkgDir(pkg); @@ -90,17 +98,13 @@ async function publishAll() { previousDataMap[pkgName] = registry[pkgName] || {}; const hasDist = fs.existsSync(path.join(pkgDir, 'dist')) || fs.existsSync(path.join(pkgDir, 'lib')); + const needsFinalize = !isBeta && pkgJson.version.includes('-beta'); - if (!hasDist || previousDataMap[pkgName].hash !== computedHashes[pkgName]) { + if (!hasDist || previousDataMap[pkgName].hash !== computedHashes[pkgName] || needsFinalize) { anyPackageChanged = true; } } - const forceBuild = process.argv.includes('--force'); - const tagArgIndex = process.argv.indexOf('--tag'); - const npmTag = tagArgIndex !== -1 ? process.argv[tagArgIndex + 1] : 'latest'; - const tagString = npmTag ? `--tag ${npmTag}` : ''; - if (!anyPackageChanged && !forceBuild) { console.log('[BUILD] No package changes detected and build artifacts present. Skipping build phase completely.'); } else { @@ -130,19 +134,53 @@ async function publishAll() { const pkgName = pkgJson.name; const hash = computedHashes[pkgName]; - const previousData = previousDataMap[pkgName]; - - if (previousData.hash !== hash) { - console.log(`[PUBLISH] Changes detected in ${pkgName}. Releasing...`); + const previousData = previousDataMap[pkgName] || {}; + const prevBuf = Buffer.from(previousData.hash || ''); + const currBuf = Buffer.from(hash || ''); + const isHashMatching = prevBuf.length === currBuf.length && crypto.timingSafeEqual(prevBuf, currBuf); + + const needsFinalize = !isBeta && pkgJson.version.includes('-beta'); + + if (!isHashMatching || needsFinalize) { + console.log(`[PUBLISH] Changes detected or beta finalization needed in ${pkgName}. Releasing...`); try { - // Execute standard release pipeline - runSync('yarn', ['version', 'patch'], { cwd: pkgDir, stdio: 'inherit' }); - - // Read new version and keep original content + let newVersion; + let updatedPkgJson; const originalPkgContent = fs.readFileSync(pkgJsonPath, 'utf8'); - const updatedPkgJson = JSON.parse(originalPkgContent); - const newVersion = updatedPkgJson.version; + let bumpedContent = originalPkgContent; + + if (isBeta) { + const currentVer = pkgJson.version; + const betaMatch = currentVer.match(/^(\d+\.\d+\.\d+)-beta\.(\d+)$/); + if (betaMatch) { + const nextCount = parseInt(betaMatch[2], 10) + 1; + newVersion = `${betaMatch[1]}-beta.${nextCount}`; + } else { + // Current version is stable (e.g. 1.2.19), bump patch and append -beta.0 + const base = currentVer.split('-')[0]; + const parts = base.split('.').map(Number); + parts[2] = (parts[2] || 0) + 1; + newVersion = `${parts.join('.')}-beta.0`; + } + runSync('yarn', ['version', newVersion], { cwd: pkgDir, stdio: 'inherit' }); + bumpedContent = fs.readFileSync(pkgJsonPath, 'utf8'); + updatedPkgJson = JSON.parse(bumpedContent); + } else { + // Standard stable release (on main) + if (pkgJson.version.includes('-beta')) { + // Finalize beta version to stable SemVer + newVersion = pkgJson.version.split('-')[0]; + runSync('yarn', ['version', newVersion], { cwd: pkgDir, stdio: 'inherit' }); + bumpedContent = fs.readFileSync(pkgJsonPath, 'utf8'); + updatedPkgJson = JSON.parse(bumpedContent); + } else { + runSync('yarn', ['version', 'patch'], { cwd: pkgDir, stdio: 'inherit' }); + bumpedContent = fs.readFileSync(pkgJsonPath, 'utf8'); + updatedPkgJson = JSON.parse(bumpedContent); + newVersion = updatedPkgJson.version; + } + } // Strip workspace: protocol before packing ['dependencies', 'devDependencies', 'peerDependencies'].forEach(deptype => { @@ -162,6 +200,15 @@ async function publishAll() { } }); + // Ensure repository metadata exists for npm provenance verification + if (!updatedPkgJson.repository || !updatedPkgJson.repository.url) { + updatedPkgJson.repository = { + type: 'git', + url: 'git+https://github.com/Quatrain/Core.git', + directory: `packages/${pkg}` + }; + } + try { // Temporarily write the versioned + stripped file fs.writeFileSync(pkgJsonPath, JSON.stringify(updatedPkgJson, null, 2), 'utf8'); @@ -206,7 +253,8 @@ async function publishAll() { } } finally { // Restore the package.json to retain workspace: protocols but keep the version bump - fs.writeFileSync(pkgJsonPath, originalPkgContent, 'utf8'); + // eslint-disable-next-line security/detect-non-literal-fs-filename + fs.writeFileSync(pkgJsonPath, bumpedContent, 'utf8'); if (fs.existsSync(path.join(pkgDir, 'package.tgz'))) fs.unlinkSync(path.join(pkgDir, 'package.tgz')); if (fs.existsSync(path.join(pkgDir, '.npmignore'))) fs.unlinkSync(path.join(pkgDir, '.npmignore')); } @@ -217,12 +265,13 @@ async function publishAll() { hash: hash, last_published: new Date().toISOString() }; - changed = true; - console.log(`[PUBLISH] Success for ${pkgName} v${newVersion}`); + + console.log(`[PUBLISH] Success for ${pkgName} v${newVersion} (tag: ${npmTag})`); publishedPackages.push({ Package: pkgName, - Version: newVersion + Version: newVersion, + Tag: npmTag }); } catch (error) { diff --git a/docs/ContainerFile b/docs/ContainerFile index fb4bd367..952c7eb8 100644 --- a/docs/ContainerFile +++ b/docs/ContainerFile @@ -1,5 +1,5 @@ # Stage 1: Builder -FROM node:22-alpine AS builder +FROM --platform=$BUILDPLATFORM node:22-alpine AS builder WORKDIR /app RUN corepack enable @@ -21,7 +21,7 @@ COPY tsconfig.json ./ RUN yarn config set nodeLinker node-modules # Install dependencies -RUN touch docs/yarn.lock && yarn install --immutable +RUN touch docs/yarn.lock && YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install # Run full documentation aggregation # On utilise 10Go de RAM pour TypeDoc diff --git a/docs/pages/_meta.js b/docs/pages/_meta.js new file mode 100644 index 00000000..46ee3d32 --- /dev/null +++ b/docs/pages/_meta.js @@ -0,0 +1,9 @@ +export default { + "index": "Introduction", + "packages": "Packages", + "api-reference": { + "title": "API Reference ↗", + "href": "/api-reference/modules.html", + "newWindow": true + } +} diff --git a/docs/pages/packages/ai-gemini/readme.md b/docs/pages/packages/ai-gemini/readme.md index f5a76199..ab8f7898 100644 --- a/docs/pages/packages/ai-gemini/readme.md +++ b/docs/pages/packages/ai-gemini/readme.md @@ -1,5 +1,7 @@ # @quatrain/ai-gemini +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/ai-gemini ↗](/api-reference/modules/_quatrain_ai-gemini.html). + AI adapter for Google Gemini. Integrates the Gemini API into the Quatrain AI ecosystem. ## Purpose diff --git a/docs/pages/packages/ai/readme.md b/docs/pages/packages/ai/readme.md index 4c7a359f..484cdf30 100644 --- a/docs/pages/packages/ai/readme.md +++ b/docs/pages/packages/ai/readme.md @@ -1,5 +1,7 @@ # @quatrain/ai +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/ai ↗](/api-reference/modules/_quatrain_ai.html). + AI adapters commons. Provides an abstraction layer for various Artificial Intelligence providers. ## Purpose diff --git a/docs/pages/packages/api-client/readme.md b/docs/pages/packages/api-client/readme.md index d8b71c57..6fcfabbc 100644 --- a/docs/pages/packages/api-client/readme.md +++ b/docs/pages/packages/api-client/readme.md @@ -1,5 +1,7 @@ # @quatrain/api-client +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/api-client ↗](/api-reference/modules/_quatrain_api-client.html). + The `@quatrain/api-client` package provides an isomorphic, lightweight HTTP client designed specifically for Quatrain APIs. It works seamlessly in both Node.js and Browser environments. ## Features diff --git a/docs/pages/packages/api-server-astro/howto.md b/docs/pages/packages/api-server-astro/howto.md new file mode 100644 index 00000000..38056669 --- /dev/null +++ b/docs/pages/packages/api-server-astro/howto.md @@ -0,0 +1,35 @@ +# HOWTO: Using @quatrain/api-server-astro + +This document guides you on routing API endpoints through Astro. + +--- + +## 1. Catch-all Routing in Astro + +Create a catch-all server endpoint in Astro (e.g. `src/pages/api/[...path].ts`) and bind the AstroAdapter: + +```typescript +import { AstroAdapter } from '@quatrain/api-server-astro'; +import { setupApiServer } from '../your-api-setup'; // Your API router configuration + +const adapter = new AstroAdapter('/api'); +setupApiServer(adapter); + +// Export Astro APIRoute handlers +export const ALL = adapter.handle(); +``` + +## 2. Wrapping a single handler + +If you only want to wrap a single Quatrain API handler as an Astro APIRoute: + +```typescript +import { AstroAdapter } from '@quatrain/api-server-astro'; +import { ApiRequest, ApiResponse } from '@quatrain/api'; + +const myHandler = async (req: ApiRequest, res: ApiResponse) => { + res.json({ message: 'Hello from Astro!' }); +}; + +export const GET = AstroAdapter.wrap(myHandler); +``` diff --git a/docs/pages/packages/api-server-astro/readme.md b/docs/pages/packages/api-server-astro/readme.md new file mode 100644 index 00000000..62db87ec --- /dev/null +++ b/docs/pages/packages/api-server-astro/readme.md @@ -0,0 +1,21 @@ +# @quatrain/api-server-astro + +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/api-server-astro ↗](/api-reference/modules/_quatrain_api-server-astro.html). + +Astro Adapter for the Quatrain API Server. It bridges the Quatrain API server interface with the web standard Request/Response API used natively by Astro endpoints. + +## Features + +- **Standard Astro APIRoute compatibility**: Easily host Quatrain API handlers inside Astro server routes. +- **Express-like Route Parsing**: Supports catch-all routes and extracts route parameters dynamically. +- **Response Recording**: Records Quatrain API responses and translates them to native Astro standard Responses. + +--- + +## Getting Started + +Refer to `HOWTO.md` for integration details. + +## License + +AGPL-3.0-only diff --git a/docs/pages/packages/api-server-express/readme.md b/docs/pages/packages/api-server-express/readme.md new file mode 100644 index 00000000..93ab1da8 --- /dev/null +++ b/docs/pages/packages/api-server-express/readme.md @@ -0,0 +1,5 @@ +# @quatrain/api-server-express + +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/api-server-express ↗](/api-reference/modules/_quatrain_api-server-express.html). + +Express Adapter for Quatrain API Server diff --git a/docs/pages/packages/api-server/readme.md b/docs/pages/packages/api-server/readme.md index e36eda66..6438e803 100644 --- a/docs/pages/packages/api-server/readme.md +++ b/docs/pages/packages/api-server/readme.md @@ -1,5 +1,7 @@ # @quatrain/api-server-express +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/api-server ↗](/api-reference/modules/_quatrain_api-server.html). + The `@quatrain/api-server-express` package provides the backend REST layer for Quatrain, exposing the underlying DataObjects and Backend engines through HTTP endpoints. ## Features diff --git a/docs/pages/packages/api-xmlrpc/howto.md b/docs/pages/packages/api-xmlrpc/howto.md new file mode 100644 index 00000000..58f38335 --- /dev/null +++ b/docs/pages/packages/api-xmlrpc/howto.md @@ -0,0 +1,33 @@ +# HOWTO: Using @quatrain/api-xmlrpc + +This document shows how to initialize and use the XML-RPC client wrapper. + +--- + +## 1. Initializing the Client + +Provide target connection options to instantiate `XmlRpcClient`: + +```typescript +import { XmlRpcClient } from '@quatrain/api-xmlrpc'; + +const client = new XmlRpcClient({ + host: 'odoo.example.com', + port: 443, + path: '/xmlrpc/2/common', + secure: true +}); +``` + +## 2. Invoking Remote Methods + +Use the `methodCall` method to execute calls asynchronously. It returns a Promise: + +```typescript +try { + const version = await client.methodCall('version', []); + console.log('Odoo Version Details:', version); +} catch (err) { + console.error('Connection failed:', err); +} +``` diff --git a/docs/pages/packages/api-xmlrpc/readme.md b/docs/pages/packages/api-xmlrpc/readme.md new file mode 100644 index 00000000..0d53266c --- /dev/null +++ b/docs/pages/packages/api-xmlrpc/readme.md @@ -0,0 +1,21 @@ +# @quatrain/api-xmlrpc + +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/api-xmlrpc ↗](/api-reference/modules/_quatrain_api-xmlrpc.html). + +An XML-RPC client package designed for the Quatrain Core framework. It provides a simple, Promise-based wrapper around the XML-RPC protocol. + +## Features + +- **Promise-based API**: Replaces node-style callback interfaces with modern async/await patterns. +- **Support for secure connections**: Easily toggle secure HTTPS execution. +- **Seamless integration**: Built specifically to connect with external systems utilizing the XML-RPC protocol (e.g. Odoo). + +--- + +## Getting Started + +Refer to the `HOWTO.md` file for code examples and configuration details. + +## License + +AGPL-3.0-only diff --git a/docs/pages/packages/api/readme.md b/docs/pages/packages/api/readme.md index ef1518c2..2b931c51 100644 --- a/docs/pages/packages/api/readme.md +++ b/docs/pages/packages/api/readme.md @@ -1,5 +1,7 @@ # @quatrain/api +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/api ↗](/api-reference/modules/_quatrain_api.html). + The `@quatrain/api` package defines the core types, interfaces, and shared logic for Quatrain's API ecosystem. It acts as the contract between the server-side implementations (like `@quatrain/api-server`) and the client-side consumers (like `@quatrain/api-client`). ## Features diff --git a/docs/pages/packages/app/howto.md b/docs/pages/packages/app/howto.md new file mode 100644 index 00000000..e3dbebd0 --- /dev/null +++ b/docs/pages/packages/app/howto.md @@ -0,0 +1,162 @@ +# Application Composition & Ports/Adapters Guide (@quatrain/app) + +This document provides a comprehensive guide on the **Hexagonal Application Composition Model** defined in `@quatrain/types` and orchestrated via `@quatrain/app`. + +--- + +## 1. Architectural Philosophy + +Quatrain applications strictly follow the **Hexagonal Architecture (Ports & Adapters)** design pattern: + +- **The Deliverable Application Payload (`AppContentInterface`)**: Represents the user-facing application deliverable (such as a PWA, a Web Bundle, a CLI tool, or Native Assets). It is 100% agnostic to deployment topology. +- **Pivot Classes (`Ai`, `Backend`, `Storage`, `Auth`, `Queue`, `Messaging`)**: Central registries and lifecycle managers in Quatrain Core that can hold single or multiple named adapter instances. +- **Composition (`AppCompositionInterface`)**: A typed, isomorphic contract that glues a deliverable application payload with its runtime context of Quatrain infrastructure adapters. + +Whether an application runs as a **Local Single-User App**, an **Offline Mobile App (Native WebView Shell)**, or a **Multi-Tenant Cloud SaaS**, the application core remains unchanged; only the context of bound infrastructure adapters changes. + +--- + +## 2. Interface Definitions + +All shared composition contracts reside in `@quatrain/types` to ensure isomorphic sharing across both frontend (browser/WebView) and backend (Node/Bun) environments with zero bundle bloat: + +```typescript +import type { + AppCompositionInterface, + PWAContentInterface, + PivotAdaptersSpec, + AdapterConfigSpec +} from '@quatrain/types'; +``` + +### Key Interfaces + +- **`AdapterConfigSpec`**: Specifies a single adapter package, class, and configuration options. +- **`PivotAdaptersSpec`**: Configures either a single default adapter or a map of named adapters for a pivot class (e.g. `ai.default`, `ai.transcription`). +- **`AppContentInterface`**: Base interface describing any deliverable payload (`pwa`, `web-bundle`, `cli`, `native`). +- **`PWAContentInterface`**: Specialized payload contract for Progressive Web Applications. +- **`AppCompositionInterface`**: Isomorphic glue binding `TContent` with its pivot adapters and domain config. + +--- + +## 3. Real-World Case Study 1: Modaka (Second Brain Copilot) + +**Modaka** is a local-first personal knowledge copilot. It exports its composition using `AppCompositionInterface`. + +### A. Composition Specification + +```typescript +// modaka/src/composition.ts +import type { AppCompositionInterface, PWAContentInterface } from '@quatrain/types'; + +export const modakaComposition: AppCompositionInterface = { + content: { + type: 'pwa', + name: 'modaka', + version: '1.0.0', + distPath: './dist', + manifest: { + name: 'Modaka Second Brain', + short_name: 'Modaka', + theme_color: '#090d16', + background_color: '#090d16' + } + }, + adapters: { + // Pivot class Ai holding Gemini text generation and Whisper audio transcription + ai: { + default: { package: '@quatrain/ai-gemini', adapter: 'GeminiAdapter' }, + transcription: { package: '@quatrain/ai-whisper', adapter: 'WhisperAdapter' } + }, + // Local SQLite backend for desktop/local deployment + backend: { package: '@quatrain/backend-sqlite', adapter: 'SQLiteAdapter' }, + // Local disk storage for OKF documents + storage: { package: '@quatrain/storage-local', adapter: 'LocalStorageAdapter' }, + // GitHub OAuth authentication provider + auth: { package: '@quatrain/auth-github', adapter: 'GitHubAuthAdapter' } + }, + config: { + okfRoot: './second-brain-data/content', + defaultCategory: 'inbox' + } +}; +``` + +### B. Deployment Modalities for Modaka + +1. **Local Desktop / PWA Mode**: Bootstrapped via `AppBootloader.bootstrap()` with local disk storage and SQLite. +2. **Mobile App Mode (`modaka-app`)**: Embedded inside an Expo React Native `WebView` shell. The mobile shell injects a native bridge adapter (`expo-sqlite`, `expo-audio`) into the composition context without changing Modaka's UI or domain code. + +--- + +## 4. Real-World Case Study 2: Hey Brad (Agronomic AI Companion) + +**Hey Brad** is a verticalized domain application built for the agricultural sector. It extends the knowledge engine by injecting agricultural system prompts, domain-specific schemas, and agricultural UI styling while connecting to cloud multi-tenant adapters. + +### A. Composition Specification + +```typescript +// hey-brad/src/composition.ts +import type { AppCompositionInterface, PWAContentInterface } from '@quatrain/types'; + +export const heyBradComposition: AppCompositionInterface = { + content: { + type: 'pwa', + name: 'hey-brad', + version: '1.0.0', + distPath: './dist', + theme: { + primaryColor: '#2e7d32', // Agronomic green + accentColor: '#81c784' + }, + manifest: { + name: 'Hey Brad — Agricultural AI Companion', + short_name: 'HeyBrad' + } + }, + adapters: { + // Multi-tenant Cloud AI configuration + ai: { + default: { package: '@quatrain/ai-gemini', adapter: 'GeminiAdapter' } + }, + // Cloud PostgreSQL backend for tenant data + backend: { package: '@quatrain/backend-postgres', adapter: 'PostgreSQLAdapter' }, + // Managed S3 bucket storage for farm documents and images + storage: { package: '@quatrain/storage-s3', adapter: 'S3StorageAdapter' }, + // Supabase / OIDC authentication for agricultural enterprise tenants + auth: { package: '@quatrain/auth-supabase', adapter: 'SupabaseAuthAdapter' } + }, + config: { + domain: 'agronomy', + systemPromptPath: './prompts/agronomic-rules.yaml', + supportedCrops: ['wheat', 'corn', 'vineyard', 'fruit-trees'] + } +}; +``` + +--- + +## 5. Bootstrapping a Composition + +To bootstrap any composition at runtime, pass the configuration to `AppBootloader`: + +```typescript +import { AppBootloader } from '@quatrain/app'; +import { modakaComposition } from './composition'; + +async function main() { + // Bootstraps all declared adapters into Quatrain Core singletons + await AppBootloader.bootstrapFromComposition(modakaComposition); + console.log('Application environment initialized successfully.'); +} + +main(); +``` + +--- + +## 6. Summary of Architectural Benefits + +- **Isomorphic Types**: Shared contracts reside in `@quatrain/types`, ensuring zero bundle weight overhead on client builds. +- **Multi-Adapter Support**: Pivot classes (`Ai`, `Storage`, etc.) can host multiple named adapters for specialized sub-tasks. +- **Total Decoupling**: Products (`modaka`, `hey-brad`) remain pure PWA/Web deliverables; infrastructure modalities (Mobile, SaaS, Standalone) are simply contexts of adapters glued to the deliverable payload. diff --git a/docs/pages/packages/app/readme.md b/docs/pages/packages/app/readme.md index 1743d349..8c086500 100644 --- a/docs/pages/packages/app/readme.md +++ b/docs/pages/packages/app/readme.md @@ -1,5 +1,7 @@ # @quatrain/app +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/app ↗](/api-reference/modules/_quatrain_app.html). + Application infrastructure builder for Quatrain. Provides utilities to generate code, scaffold projects and handle core application lifecycle. ## Purpose diff --git a/docs/pages/packages/auth-firebase/readme.md b/docs/pages/packages/auth-firebase/readme.md index 33046377..f6751a23 100644 --- a/docs/pages/packages/auth-firebase/readme.md +++ b/docs/pages/packages/auth-firebase/readme.md @@ -1,5 +1,7 @@ # @quatrain/auth-firebase +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/auth-firebase ↗](/api-reference/modules/_quatrain_auth-firebase.html). + The Firebase Authentication adapter for `@quatrain/auth`. ## Introduction diff --git a/docs/pages/packages/auth-github/howto.md b/docs/pages/packages/auth-github/howto.md new file mode 100644 index 00000000..879ce4c9 --- /dev/null +++ b/docs/pages/packages/auth-github/howto.md @@ -0,0 +1,83 @@ +# HOWTO: Using `@quatrain/auth-github` + +This guide explains how to configure and use the GitHub OAuth adapter and its associated endpoints. + +--- + +## 1. Registering the Adapter + +First, initialize the adapter using your GitHub OAuth application credentials, and register it to the global `Auth` manager: + +```typescript +import { Auth } from '@quatrain/auth' +import { GithubAuthAdapter } from '@quatrain/auth-github' + +const githubAdapter = GithubAuthAdapter.factory({ + clientId: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET, +}) + +if (githubAdapter) { + Auth.addProvider(githubAdapter, 'github') +} +``` + +--- + +## 2. Registering Pluggable Router Endpoints + +To expose the login and callback routes, register them on your `ServerAdapter` using `Auth.registerEndpoints()`. This dynamically collects endpoints from all registered adapters and namespaces them. + +### Web Server (Astro/Express) Example + +```typescript +import { Auth } from '@quatrain/auth' +import { AstroAdapter } from '@quatrain/api-server-astro' + +const server = new AstroAdapter() + +// Register all endpoints under /api/auth/[provider_alias] +Auth.registerEndpoints(server, '/api/auth') +``` + +This will automatically mount: +- `GET /api/auth/github/login` -> Redirects the browser to GitHub login. +- `GET /api/auth/github/callback` -> Handles the OAuth code exchange. + +--- + +## 3. Configuring Mobile Deep-Link Redirection + +If the API is consumed by a mobile application, configure the `appScheme` option during server initialization: + +```typescript +Auth.addProvider(githubAdapter, 'github') + +// Inside your API setup, specify the target app scheme +server.addEndpoint(githubAdapter.getEndpointHandler(), '/api/auth/github', { + appScheme: 'modaka' // Will redirect to modaka://auth/github/callback?token=... +}) +``` + +Alternatively, you can pass the app scheme dynamically in the login/callback query string: +`GET /api/auth/github/callback?code=CODE&app_scheme=modaka` + +--- + +## 4. Custom GitHub Repository Actions + +The adapter includes helper functions to check and create repositories directly: + +```typescript +// Check if a repository exists +const exists = await githubAdapter.checkRepositoryExists(accessToken, 'owner', 'repo-name') + +// Create a new private repository +if (!exists) { + const repo = await githubAdapter.createRepository(accessToken, 'repo-name', { + private: true, + description: 'Tactile knowledge base repository', + autoInit: true + }) +} +``` diff --git a/docs/pages/packages/auth-github/readme.md b/docs/pages/packages/auth-github/readme.md new file mode 100644 index 00000000..b4faab2e --- /dev/null +++ b/docs/pages/packages/auth-github/readme.md @@ -0,0 +1,20 @@ +# @quatrain/auth-github + +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/auth-github ↗](/api-reference/modules/_quatrain_auth-github.html). + +Authentication adapter and pluggable endpoints for GitHub OAuth 2.0 Web Application Flow. + +## Installation + +This package is a workspace package within the Quatrain Core monorepo. It depends on `@quatrain/auth` and `@quatrain/api`. + +```bash +yarn add @quatrain/auth-github +``` + +## Features + +- **OAuth 2.0 Flow**: Handles authorization URL generation and exchanging code for token. +- **Pluggable API Router**: Framework-agnostic `GithubAuthApi` endpoint handler matching `ServerAdapter` specification. +- **Deep Linking**: Supports generic redirect schemes for mobile contexts. +- **GitHub API Utilities**: Methods to check repository existence and create new repositories. diff --git a/docs/pages/packages/auth-http-basic/howto.md b/docs/pages/packages/auth-http-basic/howto.md new file mode 100644 index 00000000..dc61ce11 --- /dev/null +++ b/docs/pages/packages/auth-http-basic/howto.md @@ -0,0 +1,33 @@ +# HOWTO: Using @quatrain/auth-http-basic + +This document shows how to configure and run the Basic Authentication middleware inside your Quatrain API. + +--- + +## 1. Initializing AuthBasic + +Create a new basic auth verifier manually or via its `factory` method: + +```typescript +import { AuthBasic } from '@quatrain/auth-http-basic'; + +// Manually +const auth = new AuthBasic('admin', 'super-secret-password'); + +// Or from a configuration object +const configAuth = AuthBasic.factory({ + user: 'admin', + pass: 'super-secret-password' +}); +``` + +## 2. Registering the Middleware + +Register the verifier's middleware on your Quatrain API instance: + +```typescript +import { Api } from '@quatrain/api'; + +const api = new Api(); +api.use(auth.middleware()); +``` diff --git a/docs/pages/packages/auth-http-basic/readme.md b/docs/pages/packages/auth-http-basic/readme.md new file mode 100644 index 00000000..58c80e9c --- /dev/null +++ b/docs/pages/packages/auth-http-basic/readme.md @@ -0,0 +1,21 @@ +# @quatrain/auth-http-basic + +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/auth-http-basic ↗](/api-reference/modules/_quatrain_auth-http-basic.html). + +Basic HTTP Authentication Adapter (RFC 7617) for the Quatrain API Server. + +## Features + +- **Standard RFC 7617 Compliance**: Decodes `Authorization: Basic ` header payloads. +- **Isomorphic Support**: Works correctly inside standard Express-like contexts and Quatrain API servers. +- **Simple Configuration**: Instantiate with user/password credentials or configuration structures. + +--- + +## Getting Started + +Refer to `HOWTO.md` for integration examples. + +## License + +AGPL-3.0-only diff --git a/docs/pages/packages/auth-oidc/readme.md b/docs/pages/packages/auth-oidc/readme.md index d176d0d0..59f9598f 100644 --- a/docs/pages/packages/auth-oidc/readme.md +++ b/docs/pages/packages/auth-oidc/readme.md @@ -1,5 +1,7 @@ # @quatrain/auth-oidc +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/auth-oidc ↗](/api-reference/modules/_quatrain_auth-oidc.html). + OIDC Authentication provider for Quatrain. ## Purpose diff --git a/docs/pages/packages/auth-pocketbase/readme.md b/docs/pages/packages/auth-pocketbase/readme.md index 8b3763e3..6b2b3e6a 100644 --- a/docs/pages/packages/auth-pocketbase/readme.md +++ b/docs/pages/packages/auth-pocketbase/readme.md @@ -1,5 +1,7 @@ # @quatrain/auth-pocketbase +> 📦 **API Reference**: Detailed TypeScript documentation, classes, interfaces, and methods are available in the [TypeDoc API Reference for @quatrain/auth-pocketbase ↗](/api-reference/modules/_quatrain_auth-pocketbase.html). + PocketBase Authentication adapter for Quatrain. ## Purpose diff --git a/docs/pages/packages/auth-rbac/howto.md b/docs/pages/packages/auth-rbac/howto.md new file mode 100644 index 00000000..1c845a18 --- /dev/null +++ b/docs/pages/packages/auth-rbac/howto.md @@ -0,0 +1,332 @@ +# Hands-On Workshop : Unified Auth & Fine-Grained RBAC with Quatrain + +Welcome to the end-to-end integration workshop for **`@quatrain/auth-rbac`** and **`@quatrain/auth`**. + +In this step-by-step tutorial, you will learn how to design a complete, production-grade authorization and authentication architecture that decouples identity providers from business permission rules. + +```mermaid +flowchart LR + subgraph "1. Authentification (@quatrain/auth)" + OAuth["OAuth / Supabase / OIDC"] + LDAP["LDAP / Active Directory"] + Basic["HTTP Basic / htpasswd"] + Key["API Key (M2M Agent)"] + end + + subgraph "2. Résolution d'Identité" + Identity["RbacUserContext
• id
• roles
• subjectType
• attributes"] + end + + subgraph "3. Gouvernance (@quatrain/auth-rbac)" + Engine["RbacPolicyEngine
• Actions CRUD (READ, WRITE, ...)
• Tarpit & Anti-Abus M2M
• Field-Level Security (FLS)"] + end + + OAuth --> Identity + LDAP --> Identity + Basic --> Identity + Key --> Identity + Identity --> Engine + Engine --> App["Contrôleurs & Pages Web (Astro / Express / React)"] +``` + +--- + +## 🎯 Workshop Overview + +1. **Step 1:** Declare Roles, Semantic Actions, and Field-Level Security (FLS) +2. **Step 2:** Choose & Configure an Authentication Adapter (OAuth/Supabase, LDAP, or htpasswd) +3. **Step 3:** Chain Multiple Authentication Methods (Composite Auth) +4. **Step 4:** Secure Astro SSR & Express API Routes with Automated Tarpitting +5. **Step 5:** Enforce Micro-Security on Data Payloads (`sanitizeRead` & `sanitizeWrite`) +6. **Step 6:** Implement Visual UI Component Guards in Frontend Views + +--- + +## 🛠️ Step 1: Declare Roles, Semantic Actions & FLS + +Create `src/security/roles.ts` to define your application's security policies: + +```typescript +import { RbacPolicyEngine, type RoleDefinition } from '@quatrain/auth-rbac' + +export const appRoles: RoleDefinition[] = [ + // 1. Anonymous Public Visitor + { + id: 'anonymous', + name: 'Anonymous Visitor', + routes: [ + { pattern: '/public/**', actions: ['READ'], access: 'allow' }, + { pattern: '/login', actions: ['*'], access: 'allow' }, + { pattern: '/**', actions: ['*'], access: 'deny' } + ] + }, + + // 2. Authenticated Reader + { + id: 'reader', + name: 'Standard Reader', + inherits: ['anonymous'], + routes: [ + { pattern: '/api/documents/**', actions: ['READ'], access: 'allow' }, + { pattern: '/dashboard/**', actions: ['READ'], access: 'allow' } + ], + entities: { + 'document': { + defaultMode: 'readonly', + fields: { + internalReviewerNotes: 'hidden', // Field stripped on read + draftHistory: 'hidden' + } + } + } + }, + + // 3. Curator + { + id: 'curator', + name: 'Content Curator', + inherits: ['reader'], + routes: [ + { pattern: '/api/documents/**', actions: ['READ', 'WRITE', 'UPDATE'], access: 'allow' }, + { pattern: '/api/upload', actions: ['WRITE'], access: 'allow' } + ], + entities: { + 'document': { + defaultMode: 'readwrite', + fields: { + soa: 'readonly', // Cannot be overwritten by Curator + publishedAt: 'readonly', + internalReviewerNotes: 'readwrite' + } + } + } + }, + + // 4. Autonomous AI Agent / M2M Service + { + id: 'ai-agent', + name: 'Autonomous AI Crawler', + subjectTypes: ['agent', 'service'], + routes: [ + { pattern: '/api/ingest/**', actions: ['WRITE', 'EXECUTE'], access: 'allow' } + ], + tarpit: { + enabled: true, + burst: 5, + maxRequestsPerMinute: 30, + delayMs: 500, // Injected delay per consecutive violation + blockDurationMs: 60000 // 1-minute lockout on abusive polling + } + } +] + +export const rbacEngine = new RbacPolicyEngine(appRoles) +``` + +--- + +## 🔐 Step 2: Connect an Authentication Adapter + +Authentication adapters answer *"Who is the subject?"*, while `@quatrain/auth-rbac` evaluates *"What is this subject allowed to do?"*. + +Choose the adapter that matches your infrastructure: + +### Option A: Supabase / OAuth 2.0 / OpenID Connect + +```typescript +import { SupabaseAuthAdapter } from '@quatrain/auth-supabase' +import type { RbacUserContext } from '@quatrain/auth-rbac' + +export const authAdapter = new SupabaseAuthAdapter({ + config: { + supabaseUrl: process.env.SUPABASE_URL, + supabaseKey: process.env.SUPABASE_ANON_KEY + } +}) + +// Custom identity resolver bridging Supabase token to RbacUserContext +export async function resolveSupabaseUser(req: any): Promise { + const token = req.headers?.authorization?.replace('Bearer ', '') || req.cookies?.['sb-access-token'] + if (!token) return null + + try { + const { data } = await authAdapter.client.auth.getUser(token) + if (!data?.user) return null + + return { + id: data.user.id, + roles: data.user.app_metadata?.roles || [data.user.app_metadata?.role || 'reader'], + subjectType: data.user.app_metadata?.subjectType || 'human', + attributes: { + email: data.user.email, + name: data.user.user_metadata?.full_name + } + } + } catch { + return null + } +} +``` + +### Option B: HTTP Basic / .htpasswd (Legacy or Edge systems) + +```typescript +import { HttpBasicAuthAdapter } from '@quatrain/auth-http-basic' +import type { RbacUserContext } from '@quatrain/auth-rbac' + +export const basicAuth = new HttpBasicAuthAdapter({ + config: { htpasswdPath: '/etc/nginx/.htpasswd' } +}) + +export async function resolveBasicUser(req: any): Promise { + const authHeader = req.headers?.authorization + if (!authHeader?.startsWith('Basic ')) return null + + const user = await basicAuth.authenticateHeader(authHeader) + if (!user) return null + + return { + id: user.username, + roles: user.roles || ['reader'], + subjectType: 'human' + } +} +``` + +--- + +## 🔗 Step 3: Chain Multi-Provider Authentication (Composite Auth) + +For architectures requiring dual-stack authentication (e.g. Bearer OAuth for humans + API Key for autonomous M2M AI Agents): + +```typescript +import type { RbacUserContext } from '@quatrain/auth-rbac' + +export async function compositeUserResolver(req: any): Promise { + // 1. Try M2M API Key header first + const apiKey = req.headers?.['x-api-key'] + if (apiKey) { + if (apiKey === process.env.AGENT_SECRET_KEY) { + return { + id: 'gemini-agent-worker-01', + roles: ['ai-agent'], + subjectType: 'agent', + attributes: { model: 'gemini-2.5-pro' } + } + } + } + + // 2. Fallback to Supabase OAuth Session + return await resolveSupabaseUser(req) +} +``` + +--- + +## 🚀 Step 4: Secure Astro SSR & Express API Routes + +### In Astro SSR (`src/middleware.ts`): + +```typescript +import { sequence } from 'astro:middleware' +import { AstroRbacMiddleware } from '@quatrain/auth-rbac' +import { rbacEngine } from './security/roles' +import { compositeUserResolver } from './security/auth' + +const rbacMiddleware = new AstroRbacMiddleware(rbacEngine, { + userResolver: compositeUserResolver, + loginRedirectPath: '/login', + forbiddenRedirectPath: '/403', + enableTarpitSleep: true // Injects progressive latency on abusive subjects +}) + +export const onRequest = sequence(rbacMiddleware.handler()) +``` + +--- + +## 🛡️ Step 5: Enforce Micro-Security on Data Payloads (FLS) + +Inside an API Endpoint (`src/pages/api/documents/[id].ts`): + +```typescript +import type { APIRoute } from 'astro' + +export const GET: APIRoute = async ({ params, locals }) => { + const rbac = locals.rbac // Injected automatically by AstroRbacMiddleware + + // Fetch full document from DB + const rawDocument = await db.documents.findUnique({ where: { id: params.id } }) + + // Sanitize outgoing read payload: automatically strips 'hidden' fields + const safeData = rbac.sanitizeRead('document', rawDocument) + + return new Response(JSON.stringify(safeData), { + headers: { 'Content-Type': 'application/json' } + }) +} + +export const PUT: APIRoute = async ({ request, locals }) => { + const rbac = locals.rbac + const incomingPayload = await request.json() + + // Sanitize incoming write payload: automatically strips 'readonly' & 'hidden' fields + const validatedData = rbac.sanitizeWrite('document', incomingPayload) + + const updated = await db.documents.update({ data: validatedData }) + return new Response(JSON.stringify(updated)) +} +``` + +--- + +## 🎨 Step 6: Visual UI Component Guards in React + +In your React / Mantine UI components: + +```tsx +import React from 'react' + +interface DocumentEditorProps { + document: any + rbacContext: { + isFieldEditable: (entity: string, prop: string) => boolean + isFieldVisible: (entity: string, prop: string) => boolean + } +} + +export function DocumentEditor({ document, rbacContext }: DocumentEditorProps) { + return ( +
+ + + {/* Field visible only to authorized roles */} + {rbacContext.isFieldVisible('document', 'internalReviewerNotes') && ( +