Add containerized staging deployment
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
.git
|
||||||
|
.gitea
|
||||||
|
.agents
|
||||||
|
.codex
|
||||||
|
.svelte-kit
|
||||||
|
.vscode
|
||||||
|
build
|
||||||
|
node_modules
|
||||||
|
data
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
.env.keys
|
||||||
|
*.log
|
||||||
|
docs
|
||||||
@@ -6,6 +6,9 @@ TDR_DATABASE_PATH=./data/shd-build-development.db
|
|||||||
# Persistent directory for normalized user-uploaded avatars.
|
# Persistent directory for normalized user-uploaded avatars.
|
||||||
TDR_AVATAR_STORAGE_PATH=./data/avatars
|
TDR_AVATAR_STORAGE_PATH=./data/avatars
|
||||||
|
|
||||||
|
# Persistent directory for uploaded talent, brand, protocol, and skill-chip icons.
|
||||||
|
TDR_GAME_ICON_STORAGE_PATH=./data/game-icons
|
||||||
|
|
||||||
# Optional fixed credentials used only when initializing or explicitly resetting
|
# Optional fixed credentials used only when initializing or explicitly resetting
|
||||||
# the two default accounts. Leave blank to generate secure temporary passwords.
|
# the two default accounts. Leave blank to generate secure temporary passwords.
|
||||||
DEFAULT_ADMIN_PASSWORD=
|
DEFAULT_ADMIN_PASSWORD=
|
||||||
@@ -20,6 +23,8 @@ DISCORD_REDIRECT_URI=https://dev.shd.build/auth/discord/callback
|
|||||||
|
|
||||||
# Canonical application origin used in emailed authentication links.
|
# Canonical application origin used in emailed authentication links.
|
||||||
APP_ORIGIN=https://dev.shd.build
|
APP_ORIGIN=https://dev.shd.build
|
||||||
|
# SvelteKit adapter-node canonical origin when deployed behind a reverse proxy.
|
||||||
|
ORIGIN=https://dev.shd.build
|
||||||
# Use shd.build for both production and dev.shd.build. Use localhost only when
|
# Use shd.build for both production and dev.shd.build. Use localhost only when
|
||||||
# accessing development directly at http://localhost:5173.
|
# accessing development directly at http://localhost:5173.
|
||||||
PASSKEY_RP_ID=shd.build
|
PASSKEY_RP_ID=shd.build
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
|
||||||
|
#/ public-key encryption for .env files /
|
||||||
|
#/ [how it works](https://dotenvx.com/encryption) /
|
||||||
|
#/----------------------------------------------------------/
|
||||||
|
DOTENV_PUBLIC_KEY_STAGING="020bab072314925eb9a9cbaf42282e3a0d5c4a8de3d88a51ed92b4b3c83d598f98"
|
||||||
|
|
||||||
|
# .env.staging
|
||||||
|
# Staging is deployed at https://stage.shd.build.
|
||||||
|
TDR_DATABASE_PATH=encrypted:BEfWarm0yphwbJYuA9UCHZDY4pMcFw1HLHras694Tj2njTVgIuYcJZ2pJEkj8rjWbtFrCGh052GTqf0pX6KmWXTeu0WWgEanSJiRXZ8CYRt8/rif9gk/bqHOq1CUZfG01+eCIqkN1pOEGVAJEsqjkn/EbiWXzcqBtXisBOYdl40=
|
||||||
|
TDR_AVATAR_STORAGE_PATH=encrypted:BMoxUtKT0LXRmZMrj776svimltQlS7TEhv3ganVjJEbTS+4WlGDHzqxQ91+6nUexyXxeBWW7n81UeRTknZqzlENY3dFsaVtYUATv9meKfp806Ab4BVmFdNCVba+GPEMqJ57Sw2fG5ncLrn/AHniLJ0UU
|
||||||
|
TDR_GAME_ICON_STORAGE_PATH=encrypted:BAv4kKj8sR1An7Ym9+hLCIu4xSQdC4fc5dyRy+WaY00kmKLQ7Pj4pO9qc4Vzx4nNYp8124WevhHQeB6CzCoK94owCsmX/HHoEhajcoEGFSOaferE61bJfufK1pbzb7SRsQbZQjCh3yqFlTooYs1x/QHBZ5WW
|
||||||
|
|
||||||
|
APP_ORIGIN=encrypted:BGs9cTUlSzmPJhHESxefXKdzZ9w8l192He08Sl3IR4NXcNUlUuvxrDh0qEJXYX19GHtO1XkB95oXefO6Rw1H8SnZZ5zeeCHbOn+7oW/7v8dGLWkQMGTlG8YyJ6LNVAD1GBISAxjrO3e5lEAUOm/j0rvc8u24tg==
|
||||||
|
PASSKEY_RP_ID=encrypted:BI7ozl7Nw3NR3TlJ4TycJxt0HuNu7sQwNWFldib/sU+F6L60bNgENK60E9JXfx0euz7DwtOCFGeecKCtH7Vb7vIedT6aNyqXOtjmEi4zrEP12oIJJvd1nzU/2f2uBx6Hp+YwOyvhnMxGMg==
|
||||||
|
|
||||||
|
DISCORD_CLIENT_ID="encrypted:BIcLXUW7R3kKKwL+vPqzDzdaF9FFc/o2LLW12za5X04VROQrPgt0nf+fChDvAE9zwd/iUcBGJO0kpYuYHPPxXzjrCRwa64oj+gxKuUtjqWDuJeos5ABYzG2ZPF9M4Lbtdiq7D/PAn4/vGXhKjUxhf+M5wTo="
|
||||||
|
DISCORD_CLIENT_SECRET="encrypted:BGANQ+E6ASvqsdW4Xvyh1hluWRQsyjtXJFJWma3pY00Ge9+kHZD7z9O9kWJKNKFGNoJP3G1bnGk1CT7x0g82R5X5jLx8YF8EETI3KjCybIctTwAGq+GepFpz7FuQIPhL3fNSteuF0eCC7kGTxTW2zrPyaLIDErF4+KCdDKRgoct/"
|
||||||
|
DISCORD_REDIRECT_URI=encrypted:BIVlGoCrhTUQE6e8j0Ol7oi/5DMHblTRfWiexZg24VZrD2t7gomiZn1ylMo0YYHNm2B7jf+TkRTlNg36wUukhkl5L+qQRGaVYuhiD57s8Hzv9Z+8zcREoqKu8jTtA8CKTYHFmaiwRfYQJAxNzWRCGNp1oAUd1TpXUEZ03hORaIgD6DJAu6OeaipofKs=
|
||||||
|
|
||||||
|
SMTP_HOST="encrypted:BAzR0UdCJHlutOl01cw6kzPRseaot+cr+7ePUSKOcorXrGE4d2BWvQCT5A37qydjieSN98H1lV+9nykNMYUAD3PniY7/nd9DD6bDmXnG1nKACedCCF/fTPgfct0PMSN+FmgJI6UMbP45Q/l/QM3eRBJcqQ=="
|
||||||
|
SMTP_PORT="encrypted:BEfvu12wUPhrlVp1ICDAE6ohx5rKsUWGcnYnqL40VwafI8nayK9DqU5tuxag5du4E9lkHZ7QOLbn9QRw3sR3Yl74C+PlIktQWSQ5qCWMh2qbjYmulF8uTSWNXpPhIlhCyYVipA=="
|
||||||
|
SMTP_SECURE="encrypted:BHsWBT1w//boBqindVIcD3KOaDcjBfuHb+cm6ph5zP3dMT+oaFlG7msV28uPnbL5ZtOFXC6sUzVicMeqktu9O5ciQ+K9CFM+q+GStQBFqJ1SbfEq0isspV9eXWcTGPmTZAfBDxFx"
|
||||||
|
SMTP_USER="encrypted:BL5lI1+d91LAlbLyGCZ343Uqq55McVdF7Jo+e33EauaChfg+ltKB2d4lFGm7KHkQ+K9CorxlPpR82YjssS7ixYWwbEkFL7PgOgWxF8ksKQHDL8UR1zaqTmyylKseVfj/YIquaA7nS5FsjzpWCDVQFETJNw=="
|
||||||
|
SMTP_PASSWORD="encrypted:BNsDbxMZqE142HZ8PyEytL9mG7L2y79rMvM6aqDcs0ONNYtjM2ffDwKbu4ZySwkv6NS6mdKtHTVg3/tGmvdPRjbuK4f6FpRsJEnQvCwLaN0Gy9ikeCMOwrqQCOyfJSTkNTBiOvrz5e/Id0BXYjOkShk="
|
||||||
|
SMTP_FROM="encrypted:BDsOlqmQFyAGOOJ2f+h9AS+CmV3uxEpy1mJaNzWIGFI1NMumy9s+dFgvKrunhAIImDSzod5BphAAOtUW8RyrIEO5wJvB1IJ+qoAw06hSWa4qWu1thdSu2ZEP3D+4ll7kpsOcMEU5PVFlSHQRLsPRdOS5mSGCy5VkXb2zhNAQ0A=="
|
||||||
|
|
||||||
|
ORIGIN="encrypted:BHOwfV6ahbNXg8A7HNbApjaDrQiwVXSI5f/tBx/eeFYdBxgQJ8zQQKUOzSeICdOtx1z9BxIOWHr86+E8ZG67k0RxSeevpSLWe8bINOy+7Xf9IzXBgg2aOpY0gWxAcXercLKvP+UNJJy3TuEl9sQZqRJBjNPyhyJB"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
*.sh text eol=lf
|
||||||
|
Dockerfile text eol=lf
|
||||||
|
*.yml text eol=lf
|
||||||
|
*.yaml text eol=lf
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
name: Validate and deploy staging
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: shd-build-staging
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Validate application
|
||||||
|
run: npm run check
|
||||||
|
|
||||||
|
- name: Build Node application
|
||||||
|
run: npm run build:app
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
needs: validate
|
||||||
|
runs-on: stage-deploy
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install environment tooling
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Prepare deployment configuration
|
||||||
|
env:
|
||||||
|
DOTENV_PRIVATE_KEY_STAGING: ${{ secrets.DOTENV_PRIVATE_KEY_STAGING }}
|
||||||
|
CONFIGURED_DEPLOY_PATH: ${{ vars.STAGE_DEPLOY_PATH }}
|
||||||
|
CONFIGURED_STAGE_PORT: ${{ vars.STAGE_PORT }}
|
||||||
|
COMMIT_SHA: ${{ gitea.sha }}
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
deployment_path="${CONFIGURED_DEPLOY_PATH:-/opt/shd-build-stage}"
|
||||||
|
stage_port="${CONFIGURED_STAGE_PORT:-5569}"
|
||||||
|
mkdir -p "$deployment_path"
|
||||||
|
cp deploy/compose.staging.yml deploy/deploy-staging.sh deploy/bootstrap-staging.sh "$deployment_path/"
|
||||||
|
DOTENV_PRIVATE_KEY_STAGING="$DOTENV_PRIVATE_KEY_STAGING" npx dotenvx decrypt -f .env.staging --stdout > "$deployment_path/.env.staging.runtime"
|
||||||
|
printf 'SHD_IMAGE=shd-build-stage:%s\nSHD_STAGE_PORT=%s\n' "$COMMIT_SHA" "$stage_port" > "$deployment_path/.env.deploy"
|
||||||
|
chmod 600 "$deployment_path/.env.staging.runtime" "$deployment_path/.env.deploy"
|
||||||
|
chmod 755 "$deployment_path/deploy-staging.sh" "$deployment_path/bootstrap-staging.sh"
|
||||||
|
|
||||||
|
- name: Deploy staging
|
||||||
|
env:
|
||||||
|
CONFIGURED_DEPLOY_PATH: ${{ vars.STAGE_DEPLOY_PATH }}
|
||||||
|
COMMIT_SHA: ${{ gitea.sha }}
|
||||||
|
run: |
|
||||||
|
deployment_path="${CONFIGURED_DEPLOY_PATH:-/opt/shd-build-stage}"
|
||||||
|
"$deployment_path/deploy-staging.sh" "$deployment_path" "$GITHUB_WORKSPACE" "shd-build-stage:$COMMIT_SHA"
|
||||||
|
|
||||||
|
- name: Remove unused application images
|
||||||
|
if: success()
|
||||||
|
run: docker image prune --force --filter "label=org.opencontainers.image.title=shd-build"
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
name: Export staging ISAC data
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: shd-build-staging
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
export:
|
||||||
|
runs-on: stage-deploy
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Export canonical ISAC data and uploaded icons
|
||||||
|
env:
|
||||||
|
CONFIGURED_DEPLOY_PATH: ${{ vars.STAGE_DEPLOY_PATH }}
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
deployment_path="${CONFIGURED_DEPLOY_PATH:-/opt/shd-build-stage}"
|
||||||
|
compose_file="$deployment_path/compose.staging.yml"
|
||||||
|
deploy_environment="$deployment_path/.env.deploy"
|
||||||
|
compose() {
|
||||||
|
docker compose --env-file "$deploy_environment" -f "$compose_file" "$@"
|
||||||
|
}
|
||||||
|
restart_services() {
|
||||||
|
compose up -d --no-build app scheduler
|
||||||
|
}
|
||||||
|
trap restart_services EXIT
|
||||||
|
snapshot_name="isac-stage-${GITHUB_RUN_ID}.json"
|
||||||
|
compose stop scheduler app
|
||||||
|
compose run --rm --no-deps app node scripts/isac-data-export.mjs "/app/data/exports/$snapshot_name"
|
||||||
|
compose run --rm --no-deps -v "$GITHUB_WORKSPACE:/export" app \
|
||||||
|
cp "/app/data/exports/$snapshot_name" /export/isac-stage.json
|
||||||
|
|
||||||
|
- name: Publish ISAC snapshot
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: shd-stage-isac-data
|
||||||
|
path: isac-stage.json
|
||||||
|
retention-days: 14
|
||||||
@@ -18,6 +18,7 @@ Thumbs.db
|
|||||||
!.env.example
|
!.env.example
|
||||||
!.env.test
|
!.env.test
|
||||||
!.env.development
|
!.env.development
|
||||||
|
!.env.staging
|
||||||
!.env.production
|
!.env.production
|
||||||
.env.keys
|
.env.keys
|
||||||
.env.keys.*
|
.env.keys.*
|
||||||
|
|||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM node:24-bookworm-slim AS dependencies
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json .npmrc ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
FROM dependencies AS build
|
||||||
|
COPY vite.config.ts tsconfig.json ./
|
||||||
|
COPY src ./src
|
||||||
|
COPY static ./static
|
||||||
|
RUN npm run build:app
|
||||||
|
|
||||||
|
FROM node:24-bookworm-slim AS runtime-dependencies
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json .npmrc ./
|
||||||
|
RUN npm ci --omit=dev && npm cache clean --force
|
||||||
|
|
||||||
|
FROM node:24-bookworm-slim AS runtime
|
||||||
|
LABEL org.opencontainers.image.title="shd-build"
|
||||||
|
ENV NODE_ENV=production \
|
||||||
|
HOST=0.0.0.0 \
|
||||||
|
PORT=3000 \
|
||||||
|
TDR_DATABASE_PATH=/app/data/shd-build.db \
|
||||||
|
TDR_AVATAR_STORAGE_PATH=/app/data/avatars \
|
||||||
|
TDR_GAME_ICON_STORAGE_PATH=/app/data/game-icons
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=runtime-dependencies --chown=node:node /app/node_modules ./node_modules
|
||||||
|
COPY --from=build --chown=node:node /app/build ./build
|
||||||
|
COPY --chown=node:node database ./database
|
||||||
|
COPY --chown=node:node scripts ./scripts
|
||||||
|
COPY --chown=node:node deploy/container-entrypoint.sh ./deploy/container-entrypoint.sh
|
||||||
|
RUN chmod 755 ./deploy/container-entrypoint.sh && mkdir -p /app/data && chown node:node /app/data
|
||||||
|
USER node
|
||||||
|
EXPOSE 3000
|
||||||
|
VOLUME ["/app/data"]
|
||||||
|
ENTRYPOINT ["./deploy/container-entrypoint.sh"]
|
||||||
|
CMD ["serve"]
|
||||||
@@ -41,16 +41,19 @@ npm run dev
|
|||||||
|
|
||||||
## Encrypted environment files
|
## Encrypted environment files
|
||||||
|
|
||||||
Development and production settings live in the committed, encrypted
|
Development, staging, and production settings live in the committed, encrypted
|
||||||
`.env.development` and `.env.production` files. Private decryption keys live
|
`.env.development`, `.env.staging`, and `.env.production` files. Private
|
||||||
only in the ignored `.env.keys` file; back it up in a password manager.
|
decryption keys live only in the ignored `.env.keys` file; back it up in a
|
||||||
|
password manager.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run env:decrypt # decrypt both files for editing
|
npm run env:decrypt # decrypt all environment files for editing
|
||||||
npm run env:encrypt # encrypt both files again
|
npm run env:encrypt # encrypt all environment files again
|
||||||
npm run env:stage # encrypt and git-add both files
|
npm run env:stage # encrypt and git-add all environment files
|
||||||
npm run env:decrypt:development # operate on one environment
|
npm run env:decrypt:development # operate on one environment
|
||||||
|
npm run env:decrypt:staging
|
||||||
npm run env:decrypt:production
|
npm run env:decrypt:production
|
||||||
|
npm run env:sync:staging-credentials # copy shared Discord/SMTP values from development
|
||||||
```
|
```
|
||||||
|
|
||||||
The repository pre-commit hook runs `env:stage`, so a commit always replaces
|
The repository pre-commit hook runs `env:stage`, so a commit always replaces
|
||||||
@@ -62,6 +65,20 @@ Never commit or lose `.env.keys`. Production should receive
|
|||||||
`DOTENV_PRIVATE_KEY_PRODUCTION` through its secret manager rather than copying
|
`DOTENV_PRIVATE_KEY_PRODUCTION` through its secret manager rather than copying
|
||||||
the keys file onto the server.
|
the keys file onto the server.
|
||||||
|
|
||||||
|
## Containers and staging deployment
|
||||||
|
|
||||||
|
The application uses the official SvelteKit Node adapter and includes a
|
||||||
|
multi-stage `Dockerfile`. Database files, database backups, user avatars, and
|
||||||
|
uploaded game-data icons live under `/app/data`, which must be a persistent
|
||||||
|
volume. Runtime secrets are injected when the container starts and are never
|
||||||
|
baked into the image.
|
||||||
|
|
||||||
|
The staging compose topology and Gitea Actions deployment are documented in
|
||||||
|
[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md). Pushes to `main` validate the
|
||||||
|
application, build the candidate image while staging remains online, stop the
|
||||||
|
application and scheduler briefly, back up the embedded database, migrate it,
|
||||||
|
and start the new release.
|
||||||
|
|
||||||
## Passwordless authentication
|
## Passwordless authentication
|
||||||
|
|
||||||
Passkeys are the preferred sign-in method. `APP_ORIGIN` must exactly match the
|
Passkeys are the preferred sign-in method. `APP_ORIGIN` must exactly match the
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
bundle_path=${1:-}
|
||||||
|
deployment_directory=${2:-/opt/shd-build-stage}
|
||||||
|
if [ -z "$bundle_path" ] || [ ! -f "$bundle_path" ]; then
|
||||||
|
echo "Usage: bootstrap-staging.sh <bootstrap-bundle.json> [deployment-directory]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
compose_file="$deployment_directory/compose.staging.yml"
|
||||||
|
deploy_environment="$deployment_directory/.env.deploy"
|
||||||
|
compose() {
|
||||||
|
docker compose --env-file "$deploy_environment" -f "$compose_file" "$@"
|
||||||
|
}
|
||||||
|
restart_services() {
|
||||||
|
compose up -d --no-build app scheduler
|
||||||
|
}
|
||||||
|
trap restart_services EXIT
|
||||||
|
|
||||||
|
echo "Stopping staging before the one-time database bootstrap."
|
||||||
|
compose stop scheduler app || true
|
||||||
|
compose run --rm --no-deps \
|
||||||
|
-e ALLOW_STAGING_BOOTSTRAP=true \
|
||||||
|
-v "$bundle_path:/import/staging-bootstrap.json:ro" \
|
||||||
|
app node scripts/stage-bootstrap-import.mjs \
|
||||||
|
/import/staging-bootstrap.json --confirm-empty-staging-replace
|
||||||
|
|
||||||
|
echo "Staging bootstrap completed."
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
name: shd-build-stage
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: ${SHD_IMAGE:-shd-build-stage:local}
|
||||||
|
restart: unless-stopped
|
||||||
|
init: true
|
||||||
|
env_file:
|
||||||
|
- .env.staging.runtime
|
||||||
|
environment:
|
||||||
|
HOST: 0.0.0.0
|
||||||
|
PORT: 3000
|
||||||
|
BODY_SIZE_LIMIT: 6M
|
||||||
|
SKIP_DEFAULT_USERS: "true"
|
||||||
|
TDR_DATABASE_PATH: /app/data/shd-build-staging.db
|
||||||
|
TDR_AVATAR_STORAGE_PATH: /app/data/avatars
|
||||||
|
TDR_GAME_ICON_STORAGE_PATH: /app/data/game-icons
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:${SHD_STAGE_PORT:-5569}:3000"
|
||||||
|
volumes:
|
||||||
|
- shd_stage_data:/app/data
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- node
|
||||||
|
- -e
|
||||||
|
- fetch('http://127.0.0.1:3000/api/health/database').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 8
|
||||||
|
start_period: 30s
|
||||||
|
stop_grace_period: 30s
|
||||||
|
|
||||||
|
scheduler:
|
||||||
|
image: ${SHD_IMAGE:-shd-build-stage:local}
|
||||||
|
restart: unless-stopped
|
||||||
|
init: true
|
||||||
|
command:
|
||||||
|
- scheduler
|
||||||
|
env_file:
|
||||||
|
- .env.staging.runtime
|
||||||
|
environment:
|
||||||
|
SKIP_DEFAULT_USERS: "true"
|
||||||
|
TDR_DATABASE_PATH: /app/data/shd-build-staging.db
|
||||||
|
TDR_AVATAR_STORAGE_PATH: /app/data/avatars
|
||||||
|
TDR_GAME_ICON_STORAGE_PATH: /app/data/game-icons
|
||||||
|
volumes:
|
||||||
|
- shd_stage_data:/app/data
|
||||||
|
depends_on:
|
||||||
|
app:
|
||||||
|
condition: service_healthy
|
||||||
|
stop_grace_period: 30s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
shd_stage_data:
|
||||||
|
name: shd-build-stage-data
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if [ "${1:-}" = "serve" ]; then
|
||||||
|
node scripts/db-migrate.mjs
|
||||||
|
exec node build
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${1:-}" = "scheduler" ]; then
|
||||||
|
while true; do
|
||||||
|
node scripts/system-notification-dispatch.mjs
|
||||||
|
node scripts/oversight-notification-dispatch.mjs
|
||||||
|
node scripts/notification-digest.mjs
|
||||||
|
sleep 60
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$@"
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
deployment_directory=${1:-/opt/shd-build-stage}
|
||||||
|
source_directory=${2:-}
|
||||||
|
candidate_image=${3:-}
|
||||||
|
if [ -z "$source_directory" ] || [ ! -f "$source_directory/Dockerfile" ] || [ -z "$candidate_image" ]; then
|
||||||
|
echo "Usage: deploy-staging.sh <deployment-directory> <source-directory> <candidate-image>" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
compose_file="$deployment_directory/compose.staging.yml"
|
||||||
|
deploy_environment="$deployment_directory/.env.deploy"
|
||||||
|
|
||||||
|
compose() {
|
||||||
|
docker compose --env-file "$deploy_environment" -f "$compose_file" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Building the candidate image while the current staging service remains online."
|
||||||
|
docker build --label org.opencontainers.image.title=shd-build -t "$candidate_image" "$source_directory"
|
||||||
|
|
||||||
|
echo "Stopping SHD staging processes before accessing the embedded database."
|
||||||
|
compose stop scheduler app || true
|
||||||
|
|
||||||
|
echo "Creating a timestamped database backup when a staging database exists."
|
||||||
|
compose run --rm --no-deps app sh -c \
|
||||||
|
'if [ -f "$TDR_DATABASE_PATH" ]; then node scripts/db-backup.mjs; else echo "No staging database exists yet; skipping backup."; fi'
|
||||||
|
|
||||||
|
echo "Applying additive schema migrations."
|
||||||
|
compose run --rm --no-deps app node scripts/db-migrate.mjs
|
||||||
|
|
||||||
|
echo "Starting the application and notification scheduler."
|
||||||
|
compose up -d --no-build --remove-orphans app scheduler
|
||||||
|
|
||||||
|
container_id=$(compose ps -q app)
|
||||||
|
attempt=0
|
||||||
|
while [ "$attempt" -lt 30 ]; do
|
||||||
|
status=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_id")
|
||||||
|
if [ "$status" = "healthy" ]; then
|
||||||
|
echo "SHD staging deployment is healthy."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [ "$status" = "unhealthy" ] || [ "$status" = "exited" ] || [ "$status" = "dead" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "SHD staging failed its health check."
|
||||||
|
compose logs --tail=150 app
|
||||||
|
exit 1
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
# Container and staging deployment
|
||||||
|
|
||||||
|
SHD.build is packaged as a Node 24 container using the official SvelteKit Node
|
||||||
|
adapter. The image contains application code, migration and notification
|
||||||
|
scripts, and native runtime dependencies. It does not contain an environment
|
||||||
|
file, database, backups, or user uploads.
|
||||||
|
|
||||||
|
## Persistent data
|
||||||
|
|
||||||
|
The staging compose project uses the named volume `shd-build-stage-data`,
|
||||||
|
mounted at `/app/data`. It contains:
|
||||||
|
|
||||||
|
- `shd-build-staging.db` and its embedded-database support files;
|
||||||
|
- timestamped JSON backups under `backups/`;
|
||||||
|
- normalized user avatars under `avatars/`;
|
||||||
|
- uploaded game-data icons under `game-icons/`.
|
||||||
|
|
||||||
|
Never remove this volume during routine deployments. `docker compose down`
|
||||||
|
without `--volumes` preserves it. Before any manual backup or restore, stop
|
||||||
|
both the `app` and `scheduler` services because both access the embedded
|
||||||
|
database.
|
||||||
|
|
||||||
|
## Staging environment
|
||||||
|
|
||||||
|
Staging defaults to:
|
||||||
|
|
||||||
|
- Public origin: `https://stage.shd.build`
|
||||||
|
- Reverse-proxy upstream: `http://127.0.0.1:5569`
|
||||||
|
- Database: `/app/data/shd-build-staging.db`
|
||||||
|
- Passkey relying-party ID: `shd.build`
|
||||||
|
- Adapter canonical origin: `https://stage.shd.build`
|
||||||
|
|
||||||
|
Edit its encrypted configuration locally:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run env:decrypt:staging
|
||||||
|
# Fill in Discord and SMTP values.
|
||||||
|
npm run env:encrypt:staging
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `https://stage.shd.build/auth/discord/callback` to the Discord application's
|
||||||
|
allowed OAuth redirect URIs. Point the staging DNS record at the deployment
|
||||||
|
host and configure the host's TLS reverse proxy to forward
|
||||||
|
`stage.shd.build` to `127.0.0.1:5569`.
|
||||||
|
|
||||||
|
The deployment decrypts `.env.staging` on the trusted staging runner into a
|
||||||
|
mode-`0600` runtime file. The decryption key is held by Gitea Actions, not
|
||||||
|
committed or included in the container image.
|
||||||
|
|
||||||
|
The staging container disables default-user seeding. On a brand-new staging
|
||||||
|
database, the existing bootstrap policy grants Admin to the first real account
|
||||||
|
that signs in instead of writing generated credentials into deployment logs.
|
||||||
|
|
||||||
|
## Gitea Actions runner
|
||||||
|
|
||||||
|
Gitea Actions requires an `act_runner`. Register a trusted runner on the
|
||||||
|
staging host from the repository's **Settings → Actions → Runners** page. The
|
||||||
|
host needs Node/npm, Docker Engine, and Docker Compose v2. The runner account
|
||||||
|
must be able to use Docker and write to the deployment directory.
|
||||||
|
|
||||||
|
Use runner labels that provide an isolated validation job and a host deployment
|
||||||
|
job:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ubuntu-latest:docker://node:24-bookworm,stage-deploy:host
|
||||||
|
```
|
||||||
|
|
||||||
|
The `stage-deploy:host` label is intentional: the deployment step needs the
|
||||||
|
staging host's Docker daemon and persistent named volume. Only attach this
|
||||||
|
trusted runner to repositories allowed to deploy to that host. Gitea warns that
|
||||||
|
Docker daemon access is effectively host-level authority.
|
||||||
|
|
||||||
|
Prepare the default deployment directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo install -d -o act_runner -g act_runner -m 0750 /opt/shd-build-stage
|
||||||
|
sudo usermod -aG docker act_runner
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart the runner session after adding it to the Docker group.
|
||||||
|
|
||||||
|
## Repository Actions configuration
|
||||||
|
|
||||||
|
In `https://git.jb.codes/jb/shd.build/settings/actions`:
|
||||||
|
|
||||||
|
1. Add repository secret `DOTENV_PRIVATE_KEY_STAGING`, using the matching value
|
||||||
|
from the ignored local `.env.keys`.
|
||||||
|
2. Optionally add repository variable `STAGE_DEPLOY_PATH`. It defaults to
|
||||||
|
`/opt/shd-build-stage`.
|
||||||
|
3. Optionally add repository variable `STAGE_PORT`. It defaults to `5569`.
|
||||||
|
4. Ensure Actions are enabled and the `stage-deploy` runner is online.
|
||||||
|
|
||||||
|
The workflow at `.gitea/workflows/deploy-staging.yml` runs on every push to
|
||||||
|
`main` and can also be started manually. It does not need registry credentials
|
||||||
|
because the trusted staging runner builds a commit-tagged image locally.
|
||||||
|
|
||||||
|
## Deployment lifecycle
|
||||||
|
|
||||||
|
The workflow:
|
||||||
|
|
||||||
|
1. installs dependencies, runs `npm run check`, and produces the Node build;
|
||||||
|
2. builds the candidate container while the current site remains online;
|
||||||
|
3. stops only the staging application and notification scheduler;
|
||||||
|
4. creates a timestamped JSON backup if the staging database exists;
|
||||||
|
5. applies additive, repeatable migrations;
|
||||||
|
6. starts the application and scheduler with the new image;
|
||||||
|
7. waits for `/api/health/database` to report healthy;
|
||||||
|
8. retains the logs and exits unsuccessfully if health verification fails.
|
||||||
|
|
||||||
|
The short outage is limited to backup, migration, and container startup. This
|
||||||
|
follows the database-preservation policy while minimizing data-entry downtime.
|
||||||
|
The scheduler runs automated system messages, Oversight consolidation, and
|
||||||
|
email digest checks once per minute.
|
||||||
|
|
||||||
|
## Initial development-data bootstrap
|
||||||
|
|
||||||
|
The first staging release can be initialized from the current development
|
||||||
|
database, including users, roles, builds, guides, proposals, audit history,
|
||||||
|
avatars, and uploaded game-data icons. This is deliberately a one-time flow:
|
||||||
|
the staging importer refuses to run after users, builds, audits, or proposals
|
||||||
|
exist in its target database.
|
||||||
|
|
||||||
|
First stop this project's development server. The embedded database must not be
|
||||||
|
open while its complete transfer bundle is created:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run stage:bootstrap:export
|
||||||
|
```
|
||||||
|
|
||||||
|
The command writes a timestamped, ignored JSON bundle under `data/backups/`.
|
||||||
|
It contains user records and password hashes, so transfer it over an encrypted
|
||||||
|
channel and never add it to Git. Copy it to the staging host, then run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
/opt/shd-build-stage/bootstrap-staging.sh \
|
||||||
|
/secure/path/shd-stage-bootstrap-YYYY-MM-DDTHH-MM-SSZ.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The bootstrap flow stops the app and scheduler, creates a safety backup of the
|
||||||
|
target, verifies that staging is unused, imports the entire development
|
||||||
|
database and uploads, and restarts both services. Delete the transfer copy from
|
||||||
|
the staging host after verifying the imported record counts and critical
|
||||||
|
entities. The copy under the local ignored backup directory remains the
|
||||||
|
recovery source.
|
||||||
|
|
||||||
|
Because this is a full initial clone, development session tokens and account
|
||||||
|
data also enter staging. Staging and development remain independent from that
|
||||||
|
point forward.
|
||||||
|
|
||||||
|
## Pulling canonical ISAC data into development
|
||||||
|
|
||||||
|
Use the manually triggered **Export staging ISAC data** workflow in Gitea
|
||||||
|
Actions. It briefly stops staging database writers, exports only canonical
|
||||||
|
builder-source tables plus uploaded game-data icons, restarts staging, and
|
||||||
|
publishes a `shd-stage-isac-data` artifact for 14 days.
|
||||||
|
|
||||||
|
Download `isac-stage.json` from that workflow run. Then stop this project's
|
||||||
|
development server and run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run isac:import -- /path/to/isac-stage.json --confirm-replace-isac-data
|
||||||
|
```
|
||||||
|
|
||||||
|
The importer automatically creates both a complete development database backup
|
||||||
|
and an ISAC-only snapshot before replacing canonical data. It does **not**
|
||||||
|
replace local users, roles, sessions, builds, guides, comments, notifications,
|
||||||
|
proposals, contributor agreements, audit history, or system-message settings.
|
||||||
|
Restart development only after the command reports both backup paths and the
|
||||||
|
imported row/icon counts.
|
||||||
|
|
||||||
|
For a local read-only snapshot without importing:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run isac:export
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual staging operations
|
||||||
|
|
||||||
|
From the deployment directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose --env-file .env.deploy -f compose.staging.yml ps
|
||||||
|
docker compose --env-file .env.deploy -f compose.staging.yml logs -f app
|
||||||
|
docker compose --env-file .env.deploy -f compose.staging.yml stop scheduler app
|
||||||
|
docker compose --env-file .env.deploy -f compose.staging.yml run --rm --no-deps app node scripts/db-backup.mjs
|
||||||
|
docker compose --env-file .env.deploy -f compose.staging.yml up -d app scheduler
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not use `docker compose down --volumes` for staging. That deletes the named
|
||||||
|
volume containing the embedded database and uploads.
|
||||||
|
|
||||||
|
## Production promotion
|
||||||
|
|
||||||
|
Production should use a separate compose project, volume, database filename,
|
||||||
|
runtime environment, port, and deployment workflow. Do not point staging and
|
||||||
|
production at the same embedded database or storage volume. Once staging is
|
||||||
|
proven, the same container image can be tagged for production so promotion does
|
||||||
|
not rebuild application code.
|
||||||
Generated
+694
-6
@@ -17,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dotenvx/dotenvx": "^2.17.4",
|
"@dotenvx/dotenvx": "^2.17.4",
|
||||||
"@sveltejs/adapter-auto": "^7.0.1",
|
"@sveltejs/adapter-node": "^5.5.7",
|
||||||
"@sveltejs/kit": "^2.63.0",
|
"@sveltejs/kit": "^2.63.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||||
"@types/node": "^26.1.1",
|
"@types/node": "^26.1.1",
|
||||||
@@ -1204,6 +1204,523 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/plugin-commonjs": {
|
||||||
|
"version": "29.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.3.tgz",
|
||||||
|
"integrity": "sha512-ZaOxZceP7SOUW7Lqw5IRVweSQYWaeIPnXIGLiB690EBA3FGJTO40EEr2L5yZplJWsgTCogILRSpcAe7+U0Otdg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/pluginutils": "^5.0.1",
|
||||||
|
"commondir": "^1.0.1",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
|
"fdir": "^6.2.0",
|
||||||
|
"is-reference": "1.2.1",
|
||||||
|
"magic-string": "^0.30.3",
|
||||||
|
"picomatch": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0 || 14 >= 14.17"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^2.68.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-commonjs/node_modules/is-reference": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-json": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/pluginutils": "^5.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-node-resolve": {
|
||||||
|
"version": "16.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz",
|
||||||
|
"integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/pluginutils": "^5.0.1",
|
||||||
|
"@types/resolve": "1.20.2",
|
||||||
|
"deepmerge": "^4.2.2",
|
||||||
|
"is-module": "^1.0.0",
|
||||||
|
"resolve": "^1.22.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^2.78.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-replace": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/pluginutils": "^5.0.1",
|
||||||
|
"magic-string": "^0.30.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/pluginutils": {
|
||||||
|
"version": "5.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz",
|
||||||
|
"integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "^1.0.0",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
|
"picomatch": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openharmony"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@simplewebauthn/browser": {
|
"node_modules/@simplewebauthn/browser": {
|
||||||
"version": "13.3.0",
|
"version": "13.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.3.0.tgz",
|
||||||
@@ -1246,14 +1763,21 @@
|
|||||||
"acorn": "^8.9.0"
|
"acorn": "^8.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sveltejs/adapter-auto": {
|
"node_modules/@sveltejs/adapter-node": {
|
||||||
"version": "7.0.1",
|
"version": "5.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.5.7.tgz",
|
||||||
"integrity": "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ==",
|
"integrity": "sha512-uOfc9eVlI3A37RRSaKcgrheBYPrfJwC9VMqDp8x/O6tlKdcLLvHThSWD0KNIbjQ/d+7bwLGx3vx6aowAcRfd2g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^29.0.0",
|
||||||
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||||
|
"@rollup/plugin-replace": "^6.0.3",
|
||||||
|
"rollup": "^4.59.0"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@sveltejs/kit": "^2.0.0"
|
"@sveltejs/kit": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sveltejs/kit": {
|
"node_modules/@sveltejs/kit": {
|
||||||
@@ -1452,6 +1976,13 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/resolve": {
|
||||||
|
"version": "1.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||||
|
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/trusted-types": {
|
"node_modules/@types/trusted-types": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
@@ -1548,6 +2079,13 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/commondir": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/cookie": {
|
"node_modules/cookie": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||||
@@ -1615,6 +2153,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esm-env": {
|
"node_modules/esm-env": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||||
@@ -1640,6 +2188,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/estree-walker": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||||
@@ -1673,6 +2228,52 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-core-module": {
|
||||||
|
"version": "2.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
|
||||||
|
"integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"hasown": "^2.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-module": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-reference": {
|
"node_modules/is-reference": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||||
@@ -2080,6 +2681,13 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-parse": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -2167,6 +2775,28 @@
|
|||||||
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
|
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/resolve": {
|
||||||
|
"version": "1.22.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
|
||||||
|
"integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"is-core-module": "^2.16.1",
|
||||||
|
"path-parse": "^1.0.7",
|
||||||
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"resolve": "bin/resolve"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rolldown": {
|
"node_modules/rolldown": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
|
||||||
@@ -2201,6 +2831,51 @@
|
|||||||
"@rolldown/binding-win32-x64-msvc": "1.1.5"
|
"@rolldown/binding-win32-x64-msvc": "1.1.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rollup": {
|
||||||
|
"version": "4.62.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
|
||||||
|
"integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "1.0.9"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rollup": "dist/bin/rollup"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0",
|
||||||
|
"npm": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@rollup/rollup-android-arm-eabi": "4.62.2",
|
||||||
|
"@rollup/rollup-android-arm64": "4.62.2",
|
||||||
|
"@rollup/rollup-darwin-arm64": "4.62.2",
|
||||||
|
"@rollup/rollup-darwin-x64": "4.62.2",
|
||||||
|
"@rollup/rollup-freebsd-arm64": "4.62.2",
|
||||||
|
"@rollup/rollup-freebsd-x64": "4.62.2",
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
|
||||||
|
"@rollup/rollup-linux-arm-musleabihf": "4.62.2",
|
||||||
|
"@rollup/rollup-linux-arm64-gnu": "4.62.2",
|
||||||
|
"@rollup/rollup-linux-arm64-musl": "4.62.2",
|
||||||
|
"@rollup/rollup-linux-loong64-gnu": "4.62.2",
|
||||||
|
"@rollup/rollup-linux-loong64-musl": "4.62.2",
|
||||||
|
"@rollup/rollup-linux-ppc64-gnu": "4.62.2",
|
||||||
|
"@rollup/rollup-linux-ppc64-musl": "4.62.2",
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu": "4.62.2",
|
||||||
|
"@rollup/rollup-linux-riscv64-musl": "4.62.2",
|
||||||
|
"@rollup/rollup-linux-s390x-gnu": "4.62.2",
|
||||||
|
"@rollup/rollup-linux-x64-gnu": "4.62.2",
|
||||||
|
"@rollup/rollup-linux-x64-musl": "4.62.2",
|
||||||
|
"@rollup/rollup-openbsd-x64": "4.62.2",
|
||||||
|
"@rollup/rollup-openharmony-arm64": "4.62.2",
|
||||||
|
"@rollup/rollup-win32-arm64-msvc": "4.62.2",
|
||||||
|
"@rollup/rollup-win32-ia32-msvc": "4.62.2",
|
||||||
|
"@rollup/rollup-win32-x64-gnu": "4.62.2",
|
||||||
|
"@rollup/rollup-win32-x64-msvc": "4.62.2",
|
||||||
|
"fsevents": "~2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sade": {
|
"node_modules/sade": {
|
||||||
"version": "1.8.1",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
|
||||||
@@ -2328,6 +3003,19 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/svelte": {
|
"node_modules/svelte": {
|
||||||
"version": "5.56.8",
|
"version": "5.56.8",
|
||||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.8.tgz",
|
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.8.tgz",
|
||||||
|
|||||||
+14
-2
@@ -5,16 +5,25 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "dotenvx run -f .env.development -- vite dev --host 0.0.0.0",
|
"dev": "dotenvx run -f .env.development -- vite dev --host 0.0.0.0",
|
||||||
|
"build:app": "vite build",
|
||||||
|
"build:staging": "dotenvx run -f .env.staging -- vite build",
|
||||||
"build": "dotenvx run -f .env.production -- vite build",
|
"build": "dotenvx run -f .env.production -- vite build",
|
||||||
"preview": "vite preview",
|
"start": "node build",
|
||||||
|
"preview": "node build",
|
||||||
"db:migrate": "dotenvx run -f .env.development -- node scripts/db-migrate.mjs",
|
"db:migrate": "dotenvx run -f .env.development -- node scripts/db-migrate.mjs",
|
||||||
|
"db:migrate:staging": "dotenvx run -f .env.staging -- node scripts/db-migrate.mjs",
|
||||||
"db:migrate:production": "dotenvx run -f .env.production -- node scripts/db-migrate.mjs",
|
"db:migrate:production": "dotenvx run -f .env.production -- node scripts/db-migrate.mjs",
|
||||||
"db:seed-users": "dotenvx run -f .env.development -- node scripts/seed-initial-users.mjs",
|
"db:seed-users": "dotenvx run -f .env.development -- node scripts/seed-initial-users.mjs",
|
||||||
"db:seed-users:reset": "dotenvx run -f .env.development -- node scripts/seed-initial-users.mjs --reset-passwords",
|
"db:seed-users:reset": "dotenvx run -f .env.development -- node scripts/seed-initial-users.mjs --reset-passwords",
|
||||||
"db:backup": "dotenvx run -f .env.development -- node scripts/db-backup.mjs",
|
"db:backup": "dotenvx run -f .env.development -- node scripts/db-backup.mjs",
|
||||||
|
"db:backup:staging": "dotenvx run -f .env.staging -- node scripts/db-backup.mjs",
|
||||||
"db:backup:production": "dotenvx run -f .env.production -- node scripts/db-backup.mjs",
|
"db:backup:production": "dotenvx run -f .env.production -- node scripts/db-backup.mjs",
|
||||||
"db:restore": "dotenvx run -f .env.development -- node scripts/db-restore.mjs",
|
"db:restore": "dotenvx run -f .env.development -- node scripts/db-restore.mjs",
|
||||||
"db:restore:production": "dotenvx run -f .env.production -- node scripts/db-restore.mjs",
|
"db:restore:production": "dotenvx run -f .env.production -- node scripts/db-restore.mjs",
|
||||||
|
"isac:export": "dotenvx run -f .env.development -- node scripts/isac-data-export.mjs",
|
||||||
|
"isac:import": "dotenvx run -f .env.development -- node scripts/isac-data-import.mjs",
|
||||||
|
"isac:export:staging": "dotenvx run -f .env.staging -- node scripts/isac-data-export.mjs",
|
||||||
|
"stage:bootstrap:export": "dotenvx run -f .env.development -- node scripts/stage-bootstrap-export.mjs",
|
||||||
"notifications:digest": "dotenvx run -f .env.development -- node scripts/notification-digest.mjs",
|
"notifications:digest": "dotenvx run -f .env.development -- node scripts/notification-digest.mjs",
|
||||||
"notifications:digest:production": "dotenvx run -f .env.production -- node scripts/notification-digest.mjs",
|
"notifications:digest:production": "dotenvx run -f .env.production -- node scripts/notification-digest.mjs",
|
||||||
"notifications:oversight": "dotenvx run -f .env.development -- node scripts/oversight-notification-dispatch.mjs",
|
"notifications:oversight": "dotenvx run -f .env.development -- node scripts/oversight-notification-dispatch.mjs",
|
||||||
@@ -27,6 +36,9 @@
|
|||||||
"env:decrypt": "node scripts/env-files.mjs decrypt",
|
"env:decrypt": "node scripts/env-files.mjs decrypt",
|
||||||
"env:encrypt:development": "dotenvx encrypt -f .env.development",
|
"env:encrypt:development": "dotenvx encrypt -f .env.development",
|
||||||
"env:decrypt:development": "dotenvx decrypt -f .env.development",
|
"env:decrypt:development": "dotenvx decrypt -f .env.development",
|
||||||
|
"env:encrypt:staging": "dotenvx encrypt -f .env.staging",
|
||||||
|
"env:decrypt:staging": "dotenvx decrypt -f .env.staging",
|
||||||
|
"env:sync:staging-credentials": "dotenvx run -f .env.development -- node scripts/sync-staging-credentials.mjs",
|
||||||
"env:encrypt:production": "dotenvx encrypt -f .env.production",
|
"env:encrypt:production": "dotenvx encrypt -f .env.production",
|
||||||
"env:decrypt:production": "dotenvx decrypt -f .env.production",
|
"env:decrypt:production": "dotenvx decrypt -f .env.production",
|
||||||
"env:stage": "node scripts/env-files.mjs stage",
|
"env:stage": "node scripts/env-files.mjs stage",
|
||||||
@@ -36,7 +48,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dotenvx/dotenvx": "^2.17.4",
|
"@dotenvx/dotenvx": "^2.17.4",
|
||||||
"@sveltejs/adapter-auto": "^7.0.1",
|
"@sveltejs/adapter-node": "^5.5.7",
|
||||||
"@sveltejs/kit": "^2.63.0",
|
"@sveltejs/kit": "^2.63.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||||
"@types/node": "^26.1.1",
|
"@types/node": "^26.1.1",
|
||||||
|
|||||||
+19
-1
@@ -99,6 +99,22 @@ if (!notificationColumns.some((column) => column.name === 'actions_json')) {
|
|||||||
await database.exec('ALTER TABLE notifications ADD COLUMN actions_json TEXT');
|
await database.exec('ALTER TABLE notifications ADD COLUMN actions_json TEXT');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const legacyProtocolEffects = await (await database.prepare(
|
||||||
|
"SELECT 1 AS present FROM sqlite_master WHERE type='table' AND name='os_protocol_attribute_effects'"
|
||||||
|
)).get();
|
||||||
|
if (legacyProtocolEffects) {
|
||||||
|
const legacyProtocolEffectCount = await (await database.prepare(
|
||||||
|
'SELECT COUNT(*) AS count FROM os_protocol_attribute_effects'
|
||||||
|
)).get();
|
||||||
|
if (Number(legacyProtocolEffectCount?.count ?? 0) > 0) {
|
||||||
|
throw new Error(
|
||||||
|
'Legacy OS Protocol attribute effects still contain rows. Export and reconcile them before removing the retired table.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log('Removing the empty legacy OS Protocol attribute-effects table.');
|
||||||
|
await database.exec('DROP TABLE os_protocol_attribute_effects');
|
||||||
|
}
|
||||||
|
|
||||||
const proposalDefinition = await (await database.prepare(
|
const proposalDefinition = await (await database.prepare(
|
||||||
"SELECT sql FROM sqlite_master WHERE type='table' AND name='data_change_proposals'"
|
"SELECT sql FROM sqlite_master WHERE type='table' AND name='data_change_proposals'"
|
||||||
)).get();
|
)).get();
|
||||||
@@ -355,7 +371,9 @@ await database.exec('CREATE INDEX IF NOT EXISTS builds_visibility_updated_idx ON
|
|||||||
|
|
||||||
console.log(`Applied ${statements.length} schema statements to ${databasePath}`);
|
console.log(`Applied ${statements.length} schema statements to ${databasePath}`);
|
||||||
|
|
||||||
if (!databaseAlreadyExisted) {
|
if (!databaseAlreadyExisted && process.env.SKIP_DEFAULT_USERS !== 'true') {
|
||||||
console.log('New database detected; creating default users.');
|
console.log('New database detected; creating default users.');
|
||||||
await initializeDefaultUsers(database);
|
await initializeDefaultUsers(database);
|
||||||
|
} else if (!databaseAlreadyExisted) {
|
||||||
|
console.log('New database detected; default-user seeding is disabled for this environment.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { spawnSync } from 'node:child_process';
|
|||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
|
|
||||||
const files = ['.env.development', '.env.production'];
|
const files = ['.env.development', '.env.staging', '.env.production'];
|
||||||
const action = process.argv[2];
|
const action = process.argv[2];
|
||||||
const gitCommand = process.platform === 'win32' ? 'git.exe' : 'git';
|
const gitCommand = process.platform === 'win32' ? 'git.exe' : 'git';
|
||||||
const dotenvxCli = resolve('node_modules/@dotenvx/dotenvx/src/cli/dotenvx.js');
|
const dotenvxCli = resolve('node_modules/@dotenvx/dotenvx/src/cli/dotenvx.js');
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { connect } from '@tursodatabase/database';
|
||||||
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||||
|
import { basename, dirname, join, resolve } from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
|
import { ISAC_DATA_TABLES, quoteIdentifier } from './isac-data-tables.mjs';
|
||||||
|
|
||||||
|
const databasePath = resolve(process.env.TDR_DATABASE_PATH || './data/shd-build-development.db');
|
||||||
|
const iconDirectory = resolve(process.env.TDR_GAME_ICON_STORAGE_PATH || './data/game-icons');
|
||||||
|
const timestamp = new Date().toISOString().replaceAll(':', '-').replace(/\.\d{3}Z$/, 'Z');
|
||||||
|
const outputPath = resolve(process.argv[2] || `./data/backups/isac-data-${timestamp}.json`);
|
||||||
|
const database = await connect(databasePath);
|
||||||
|
const tables = [];
|
||||||
|
let totalRows = 0;
|
||||||
|
|
||||||
|
for (const name of ISAC_DATA_TABLES) {
|
||||||
|
const exists = await (await database.prepare(
|
||||||
|
"SELECT 1 AS present FROM sqlite_master WHERE type='table' AND name=?"
|
||||||
|
)).get(name);
|
||||||
|
if (!exists) throw new Error(`ISAC export requires missing table "${name}". Run the schema migration first.`);
|
||||||
|
const rows = await (await database.prepare(`SELECT * FROM ${quoteIdentifier(name)}`)).all();
|
||||||
|
tables.push({ name, rowCount: rows.length, rows });
|
||||||
|
totalRows += rows.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const icons = [];
|
||||||
|
const iconTable = tables.find((table) => table.name === 'game_data_icons');
|
||||||
|
for (const row of iconTable?.rows ?? []) {
|
||||||
|
if (typeof row.storage_key !== 'string' || !/^[a-z_]+-[0-9a-f-]{36}\.webp$/i.test(row.storage_key)) {
|
||||||
|
throw new Error(`Invalid game-data icon storage key "${String(row.storage_key)}".`);
|
||||||
|
}
|
||||||
|
const content = await readFile(join(iconDirectory, row.storage_key));
|
||||||
|
icons.push({ storageKey: row.storage_key, contentBase64: content.toString('base64') });
|
||||||
|
}
|
||||||
|
|
||||||
|
const snapshot = {
|
||||||
|
format: 'shd-build-isac-data',
|
||||||
|
version: 1,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
sourceDatabase: basename(databasePath),
|
||||||
|
totalRows,
|
||||||
|
tables,
|
||||||
|
icons
|
||||||
|
};
|
||||||
|
await mkdir(dirname(outputPath), { recursive: true });
|
||||||
|
await writeFile(outputPath, `${JSON.stringify(snapshot, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
|
||||||
|
|
||||||
|
console.log(`Exported ${totalRows} ISAC rows and ${icons.length} uploaded icons.`);
|
||||||
|
console.log(`ISAC snapshot: ${outputPath}`);
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { connect } from '@tursodatabase/database';
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||||
|
import { basename, join, resolve } from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
|
import { ISAC_DATA_TABLES, quoteIdentifier } from './isac-data-tables.mjs';
|
||||||
|
|
||||||
|
const snapshotArgument = process.argv.slice(2).find((argument) => !argument.startsWith('--'));
|
||||||
|
if (!snapshotArgument || !process.argv.includes('--confirm-replace-isac-data')) {
|
||||||
|
console.error('Usage: node scripts/isac-data-import.mjs <snapshot.json> --confirm-replace-isac-data');
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const snapshotPath = resolve(snapshotArgument);
|
||||||
|
const snapshot = JSON.parse(await readFile(snapshotPath, 'utf8'));
|
||||||
|
if (snapshot?.format !== 'shd-build-isac-data' || snapshot?.version !== 1 || !Array.isArray(snapshot.tables) || !Array.isArray(snapshot.icons)) {
|
||||||
|
throw new Error('The selected file is not a supported SHD.build ISAC data snapshot.');
|
||||||
|
}
|
||||||
|
const receivedNames = snapshot.tables.map((table) => table.name);
|
||||||
|
if (receivedNames.length !== ISAC_DATA_TABLES.length || ISAC_DATA_TABLES.some((name, index) => receivedNames[index] !== name)) {
|
||||||
|
throw new Error('The ISAC snapshot table manifest is incomplete or out of order.');
|
||||||
|
}
|
||||||
|
for (const icon of snapshot.icons) {
|
||||||
|
if (typeof icon.storageKey !== 'string' || !/^[a-z_]+-[0-9a-f-]{36}\.webp$/i.test(icon.storageKey)
|
||||||
|
|| typeof icon.contentBase64 !== 'string') {
|
||||||
|
throw new Error('The ISAC snapshot contains an invalid uploaded icon.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = new Date().toISOString().replaceAll(':', '-').replace(/\.\d{3}Z$/, 'Z');
|
||||||
|
const databasePath = resolve(process.env.TDR_DATABASE_PATH || './data/shd-build-development.db');
|
||||||
|
const backupPath = resolve(`./data/backups/${basename(databasePath).replace(/\.[^.]+$/, '')}-before-isac-import-${timestamp}.json`);
|
||||||
|
const isacBackupPath = resolve(`./data/backups/isac-data-before-import-${timestamp}.json`);
|
||||||
|
for (const [script, output] of [
|
||||||
|
['scripts/db-backup.mjs', backupPath],
|
||||||
|
['scripts/isac-data-export.mjs', isacBackupPath]
|
||||||
|
]) {
|
||||||
|
const result = spawnSync(process.execPath, [script, output], { cwd: process.cwd(), stdio: 'inherit', env: process.env });
|
||||||
|
if (result.error) throw result.error;
|
||||||
|
if (result.status !== 0) throw new Error(`Safety backup failed in ${script}; no ISAC data was changed.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const database = await connect(databasePath);
|
||||||
|
let importedRows = 0;
|
||||||
|
await database.exec('PRAGMA foreign_keys=OFF');
|
||||||
|
await database.exec('BEGIN');
|
||||||
|
try {
|
||||||
|
for (const name of [...ISAC_DATA_TABLES].reverse()) {
|
||||||
|
await database.exec(`DELETE FROM ${quoteIdentifier(name)}`);
|
||||||
|
}
|
||||||
|
for (const table of snapshot.tables) {
|
||||||
|
for (const row of table.rows) {
|
||||||
|
const columns = Object.keys(row);
|
||||||
|
if (!columns.length) continue;
|
||||||
|
const insert = await database.prepare(
|
||||||
|
`INSERT INTO ${quoteIdentifier(table.name)} (${columns.map(quoteIdentifier).join(', ')})
|
||||||
|
VALUES (${columns.map(() => '?').join(', ')})`
|
||||||
|
);
|
||||||
|
await insert.run(...columns.map((column) => row[column]));
|
||||||
|
importedRows++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await database.exec('COMMIT');
|
||||||
|
} catch (error) {
|
||||||
|
await database.exec('ROLLBACK');
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await database.exec('PRAGMA foreign_keys=ON');
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconDirectory = resolve(process.env.TDR_GAME_ICON_STORAGE_PATH || './data/game-icons');
|
||||||
|
await mkdir(iconDirectory, { recursive: true });
|
||||||
|
for (const icon of snapshot.icons) {
|
||||||
|
await writeFile(join(iconDirectory, icon.storageKey), Buffer.from(icon.contentBase64, 'base64'), { mode: 0o600 });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Imported ${importedRows} ISAC rows and ${snapshot.icons.length} uploaded icons from ${snapshotPath}.`);
|
||||||
|
console.log(`Full pre-import backup: ${backupPath}`);
|
||||||
|
console.log(`ISAC pre-import snapshot: ${isacBackupPath}`);
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
export const ISAC_DATA_TABLES = [
|
||||||
|
'stat_attributes',
|
||||||
|
'talents',
|
||||||
|
'talent_tiers',
|
||||||
|
'talent_triggers',
|
||||||
|
'talent_tier_triggers',
|
||||||
|
'talent_tier_effects',
|
||||||
|
'talent_description_templates',
|
||||||
|
'talent_weapon_scopes',
|
||||||
|
'weapon_talent_slots',
|
||||||
|
'brand_sets',
|
||||||
|
'brand_set_effects',
|
||||||
|
'gear',
|
||||||
|
'gear_brand_sets',
|
||||||
|
'gear_talents',
|
||||||
|
'gear_tactical_augments',
|
||||||
|
'gear_tactical_augment_costs',
|
||||||
|
'weapons',
|
||||||
|
'core_attribute_tier_ranges',
|
||||||
|
'weapon_tier_ranges',
|
||||||
|
'weapon_talent_options',
|
||||||
|
'weapon_augmentation_effects',
|
||||||
|
'weapon_base_attribute_bonuses',
|
||||||
|
'os_protocols',
|
||||||
|
'os_protocol_primary_effects',
|
||||||
|
'os_protocol_secondary_profiles',
|
||||||
|
'os_protocol_secondary_effects',
|
||||||
|
'os_protocol_talents',
|
||||||
|
'os_protocol_talent_triggers',
|
||||||
|
'os_protocol_talent_effects',
|
||||||
|
'skill_chip_sets',
|
||||||
|
'skill_chip_two_piece_bonuses',
|
||||||
|
'skill_chip_primary_options',
|
||||||
|
'skill_chip_secondary_options',
|
||||||
|
'skill_chip_talents',
|
||||||
|
'skill_chip_talent_triggers',
|
||||||
|
'skill_chip_talent_effects',
|
||||||
|
'weapon_mods',
|
||||||
|
'weapon_mod_compatible_categories',
|
||||||
|
'weapon_mod_slots',
|
||||||
|
'weapon_mod_quality_values',
|
||||||
|
'weapon_mod_secondary_options',
|
||||||
|
'weapon_mod_secondary_quality_values',
|
||||||
|
'weapon_mod_exotic_attributes',
|
||||||
|
'weapon_mod_effects',
|
||||||
|
'weapon_mod_effect_quality_values',
|
||||||
|
'gear_mods',
|
||||||
|
'gear_mod_quality_values',
|
||||||
|
'gear_mod_secondary_options',
|
||||||
|
'gear_mod_secondary_quality_values',
|
||||||
|
'game_data_icons'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const quoteIdentifier = (identifier) => `"${String(identifier).replaceAll('"', '""')}"`;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
||||||
|
import { dirname, join, resolve } from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
|
|
||||||
|
const outputArgument = process.argv[2];
|
||||||
|
const timestamp = new Date().toISOString().replaceAll(':', '-').replace(/\.\d{3}Z$/, 'Z');
|
||||||
|
const outputPath = resolve(outputArgument || `./data/backups/shd-stage-bootstrap-${timestamp}.json`);
|
||||||
|
const temporaryBackupPath = `${outputPath}.database.json`;
|
||||||
|
const databaseBackup = spawnSync(process.execPath, ['scripts/db-backup.mjs', temporaryBackupPath], {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: process.env
|
||||||
|
});
|
||||||
|
if (databaseBackup.error) throw databaseBackup.error;
|
||||||
|
if (databaseBackup.status !== 0) throw new Error('Database backup failed; no staging bootstrap bundle was created.');
|
||||||
|
|
||||||
|
async function collectFiles(directory, pattern) {
|
||||||
|
try {
|
||||||
|
const names = (await readdir(directory)).filter((name) => pattern.test(name)).sort();
|
||||||
|
return await Promise.all(names.map(async (name) => ({
|
||||||
|
name,
|
||||||
|
contentBase64: (await readFile(join(directory, name))).toString('base64')
|
||||||
|
})));
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.code === 'ENOENT') return [];
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const database = JSON.parse(await readFile(temporaryBackupPath, 'utf8'));
|
||||||
|
const avatars = await collectFiles(
|
||||||
|
resolve(process.env.TDR_AVATAR_STORAGE_PATH || './data/avatars'),
|
||||||
|
/^[0-9a-f-]{36}\.webp$/i
|
||||||
|
);
|
||||||
|
const gameIcons = await collectFiles(
|
||||||
|
resolve(process.env.TDR_GAME_ICON_STORAGE_PATH || './data/game-icons'),
|
||||||
|
/^[a-z_]+-[0-9a-f-]{36}\.webp$/i
|
||||||
|
);
|
||||||
|
const bundle = {
|
||||||
|
format: 'shd-build-stage-bootstrap',
|
||||||
|
version: 1,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
database,
|
||||||
|
files: { avatars, gameIcons }
|
||||||
|
};
|
||||||
|
await mkdir(dirname(outputPath), { recursive: true });
|
||||||
|
await writeFile(outputPath, `${JSON.stringify(bundle, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
|
||||||
|
console.log(`Staging bootstrap bundle includes ${avatars.length} avatars and ${gameIcons.length} game-data icons.`);
|
||||||
|
console.log(`Bootstrap bundle: ${outputPath}`);
|
||||||
|
} finally {
|
||||||
|
await rm(temporaryBackupPath, { force: true });
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { connect } from '@tursodatabase/database';
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||||
|
import { join, resolve } from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
|
|
||||||
|
const bundleArgument = process.argv.slice(2).find((argument) => !argument.startsWith('--'));
|
||||||
|
if (!bundleArgument || !process.argv.includes('--confirm-empty-staging-replace')
|
||||||
|
|| process.env.ALLOW_STAGING_BOOTSTRAP !== 'true') {
|
||||||
|
console.error('Staging bootstrap requires ALLOW_STAGING_BOOTSTRAP=true and:');
|
||||||
|
console.error('node scripts/stage-bootstrap-import.mjs <bundle.json> --confirm-empty-staging-replace');
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundlePath = resolve(bundleArgument);
|
||||||
|
const bundle = JSON.parse(await readFile(bundlePath, 'utf8'));
|
||||||
|
if (bundle?.format !== 'shd-build-stage-bootstrap' || bundle?.version !== 1
|
||||||
|
|| bundle.database?.format !== 'shd-build-database-backup' || bundle.database?.version !== 1
|
||||||
|
|| !Array.isArray(bundle.database.tables) || !Array.isArray(bundle.files?.avatars)
|
||||||
|
|| !Array.isArray(bundle.files?.gameIcons)) {
|
||||||
|
throw new Error('The selected file is not a supported SHD.build staging bootstrap bundle.');
|
||||||
|
}
|
||||||
|
for (const file of bundle.files.avatars) {
|
||||||
|
if (typeof file.name !== 'string' || !/^[0-9a-f-]{36}\.webp$/i.test(file.name) || typeof file.contentBase64 !== 'string') {
|
||||||
|
throw new Error('The bootstrap bundle contains an invalid avatar file.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const file of bundle.files.gameIcons) {
|
||||||
|
if (typeof file.name !== 'string' || !/^[a-z_]+-[0-9a-f-]{36}\.webp$/i.test(file.name) || typeof file.contentBase64 !== 'string') {
|
||||||
|
throw new Error('The bootstrap bundle contains an invalid game-data icon file.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const databasePath = resolve(process.env.TDR_DATABASE_PATH || './data/shd-build-staging.db');
|
||||||
|
const safetyBackup = spawnSync(process.execPath, ['scripts/db-backup.mjs'], {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: process.env
|
||||||
|
});
|
||||||
|
if (safetyBackup.error) throw safetyBackup.error;
|
||||||
|
if (safetyBackup.status !== 0) throw new Error('Staging safety backup failed; the target was not changed.');
|
||||||
|
|
||||||
|
const database = await connect(databasePath);
|
||||||
|
for (const tableName of ['users', 'builds', 'audit_log', 'data_change_proposals']) {
|
||||||
|
const row = await (await database.prepare(`SELECT COUNT(*) AS count FROM "${tableName}"`)).get();
|
||||||
|
if (Number(row?.count ?? 0) > 0) {
|
||||||
|
throw new Error(`Staging bootstrap refused: target table "${tableName}" is not empty.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetTables = await (await database.prepare(`
|
||||||
|
SELECT name FROM sqlite_master
|
||||||
|
WHERE type='table' AND name NOT LIKE 'sqlite_%'
|
||||||
|
ORDER BY rowid
|
||||||
|
`)).all();
|
||||||
|
const targetNames = new Set(targetTables.map((row) => String(row.name)));
|
||||||
|
const sourceNames = new Set(bundle.database.tables.map((table) => String(table.name)));
|
||||||
|
for (const row of targetTables) {
|
||||||
|
if (!sourceNames.has(String(row.name))) {
|
||||||
|
throw new Error(`Bootstrap snapshot is missing target-schema table "${String(row.name)}".`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sourceOnlyTables = bundle.database.tables
|
||||||
|
.map((table) => String(table.name))
|
||||||
|
.filter((name) => !targetNames.has(name));
|
||||||
|
if (sourceOnlyTables.length) {
|
||||||
|
console.log(`Skipping source-only legacy tables: ${sourceOnlyTables.join(', ')}`);
|
||||||
|
}
|
||||||
|
const quoteIdentifier = (identifier) => `"${String(identifier).replaceAll('"', '""')}"`;
|
||||||
|
let restoredRows = 0;
|
||||||
|
await database.exec('PRAGMA foreign_keys=OFF');
|
||||||
|
await database.exec('BEGIN');
|
||||||
|
try {
|
||||||
|
for (const row of [...targetTables].reverse()) {
|
||||||
|
await database.exec(`DELETE FROM ${quoteIdentifier(row.name)}`);
|
||||||
|
}
|
||||||
|
for (const table of bundle.database.tables.filter((candidate) => targetNames.has(candidate.name))) {
|
||||||
|
for (const row of table.rows) {
|
||||||
|
const columns = Object.keys(row);
|
||||||
|
if (!columns.length) continue;
|
||||||
|
const insert = await database.prepare(
|
||||||
|
`INSERT INTO ${quoteIdentifier(table.name)} (${columns.map(quoteIdentifier).join(', ')})
|
||||||
|
VALUES (${columns.map(() => '?').join(', ')})`
|
||||||
|
);
|
||||||
|
await insert.run(...columns.map((column) => row[column]));
|
||||||
|
restoredRows++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await database.exec('COMMIT');
|
||||||
|
} catch (error) {
|
||||||
|
await database.exec('ROLLBACK');
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await database.exec('PRAGMA foreign_keys=ON');
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarDirectory = resolve(process.env.TDR_AVATAR_STORAGE_PATH || './data/avatars');
|
||||||
|
const gameIconDirectory = resolve(process.env.TDR_GAME_ICON_STORAGE_PATH || './data/game-icons');
|
||||||
|
await mkdir(avatarDirectory, { recursive: true });
|
||||||
|
await mkdir(gameIconDirectory, { recursive: true });
|
||||||
|
for (const file of bundle.files.avatars) {
|
||||||
|
await writeFile(join(avatarDirectory, file.name), Buffer.from(file.contentBase64, 'base64'), { mode: 0o600 });
|
||||||
|
}
|
||||||
|
for (const file of bundle.files.gameIcons) {
|
||||||
|
await writeFile(join(gameIconDirectory, file.name), Buffer.from(file.contentBase64, 'base64'), { mode: 0o600 });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Bootstrapped staging with ${restoredRows} database rows, ${bundle.files.avatars.length} avatars, and ${bundle.files.gameIcons.length} game-data icons.`);
|
||||||
|
console.log(`Source bundle: ${bundlePath}`);
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import { readFile, writeFile } from 'node:fs/promises';
|
||||||
|
import { resolve } from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
|
|
||||||
|
const stagingFile = resolve('.env.staging');
|
||||||
|
const dotenvxCli = resolve('node_modules/@dotenvx/dotenvx/src/cli/dotenvx.js');
|
||||||
|
const sharedKeys = [
|
||||||
|
'DISCORD_CLIENT_ID',
|
||||||
|
'DISCORD_CLIENT_SECRET',
|
||||||
|
'SMTP_HOST',
|
||||||
|
'SMTP_PORT',
|
||||||
|
'SMTP_SECURE',
|
||||||
|
'SMTP_USER',
|
||||||
|
'SMTP_PASSWORD',
|
||||||
|
'SMTP_FROM'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const key of sharedKeys) {
|
||||||
|
if (!process.env[key]) throw new Error(`Source environment is missing ${key}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dotenvx(action) {
|
||||||
|
const result = spawnSync(process.execPath, [dotenvxCli, action, '-f', stagingFile], {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
if (result.error) throw result.error;
|
||||||
|
if (result.status !== 0) throw new Error(`dotenvx ${action} failed.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
dotenvx('decrypt');
|
||||||
|
try {
|
||||||
|
let contents = await readFile(stagingFile, 'utf8');
|
||||||
|
for (const key of sharedKeys) {
|
||||||
|
const replacement = `${key}=${JSON.stringify(process.env[key])}`;
|
||||||
|
const pattern = new RegExp(`^${key}=.*$`, 'm');
|
||||||
|
if (!pattern.test(contents)) throw new Error(`Staging environment is missing ${key}.`);
|
||||||
|
contents = contents.replace(pattern, replacement);
|
||||||
|
}
|
||||||
|
await writeFile(stagingFile, contents, 'utf8');
|
||||||
|
} finally {
|
||||||
|
dotenvx('encrypt');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Copied shared Discord and SMTP credentials into encrypted staging configuration.');
|
||||||
+2
-5
@@ -1,11 +1,11 @@
|
|||||||
import adapter from '@sveltejs/adapter-auto';
|
import adapter from '@sveltejs/adapter-node';
|
||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
allowedHosts: ['dev.shd.build', 'shd.build']
|
allowedHosts: ['dev.shd.build', 'stage.shd.build', 'shd.build']
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
sveltekit({
|
sveltekit({
|
||||||
@@ -15,9 +15,6 @@ export default defineConfig({
|
|||||||
filename.split(/[/\\]/).includes('node_modules') ? undefined : true
|
filename.split(/[/\\]/).includes('node_modules') ? undefined : true
|
||||||
},
|
},
|
||||||
|
|
||||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
|
||||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
|
||||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
|
||||||
adapter: adapter()
|
adapter: adapter()
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user