Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d74dfb9938 | ||
|
|
7d60157863 |
No files matched your search
@@ -35,7 +35,7 @@ Inspect `git status --short` and distinguish pre-existing user changes from task
|
||||
Look for the originating spec, in this order:
|
||||
|
||||
1. The user's explicit spec, ticket, path, or acceptance criteria in the conversation. Preserve it as authoritative even when commits reference other work.
|
||||
2. Issue/MR references in relevant commit messages — fetch via `docs/agents/issue-tracker.md` when no explicit source was supplied.
|
||||
2. Issue/PR references in relevant commit messages — fetch via `docs/agents/issue-tracker.md` when no explicit source was supplied.
|
||||
3. A PRD/spec file under `docs/`, `specs/`, or `.scratch/` matching the branch name or feature.
|
||||
4. If nothing is found or the source is inaccessible, continue the Standards review and report the Spec coverage limitation. Ask for a source only when needed to answer the user's request; do not invent requirements or require tracker setup.
|
||||
|
||||
|
||||
@@ -42,10 +42,11 @@ Preserve the configured tracker. For a new setup, infer a likely tracker from th
|
||||
|
||||
- **GitHub** — issues live in the repo's GitHub Issues (uses the `gh` CLI)
|
||||
- **GitLab** — issues live in the repo's GitLab Issues (uses the [`glab`](https://gitlab.com/gitlab-org/cli) CLI)
|
||||
- **Gitea** — issues live in the repo's Gitea Issues (uses `tea-cli`, or `tea` where that is the installed binary; inspect its help for supported commands)
|
||||
- **Local markdown** — issues live as files under `.scratch/<feature>/` in this repo (good for solo projects or repos without a remote)
|
||||
- **Other** (Jira, Linear, etc.) — ask the user to describe the workflow in one paragraph; the skill will record it as freeform prose
|
||||
|
||||
Record the choice in `docs/agents/issue-tracker.md`. The GitHub and GitLab templates carry a "PRs as a request surface" flag, defaulted **off** — leave it off and don't raise it; a user who wants external PRs in the triage queue can flip the flag in the file later.
|
||||
Record the choice in `docs/agents/issue-tracker.md`. The GitHub, GitLab, and Gitea templates carry a "PRs as a request surface" flag, defaulted **off** — leave it off and don't raise it; a user who wants external PRs in the triage queue can flip the flag in the file later.
|
||||
|
||||
**Section B — Triage label vocabulary.** Skip this section entirely if the `triage` skill isn't installed (exploration told you) — an uninstalled skill needs no labels.
|
||||
|
||||
@@ -105,6 +106,7 @@ Then write the docs files using the seed templates in this skill folder as a sta
|
||||
|
||||
- [issue-tracker-github.md](./issue-tracker-github.md) — GitHub issue tracker
|
||||
- [issue-tracker-gitlab.md](./issue-tracker-gitlab.md) — GitLab issue tracker
|
||||
- [issue-tracker-gitea.md](./issue-tracker-gitea.md) — Gitea issue tracker
|
||||
- [issue-tracker-local.md](./issue-tracker-local.md) — local-markdown issue tracker
|
||||
- [triage-labels.md](./triage-labels.md) — label mapping (only if `triage` is installed)
|
||||
- [domain.md](./domain.md) — domain doc consumer rules + layout
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Issue tracker: Gitea
|
||||
|
||||
Issues and specs for this repo live as Gitea issues. Use the [`tea`](https://gitea.com/gitea/tea) CLI, called `tea-cli` on some systems. Record the instance URL, repository owner/name, installed command version, and supported operations when adapting this template.
|
||||
|
||||
## Access and conventions
|
||||
|
||||
Use the existing configured login and infer the repository from `git remote -v`. Check the installed binary's help before selecting commands: tea 1.0 changes the command groups, authentication, flags, and output formats. The examples here use `tea-cli` 0.9.2; options precede positional issue or PR numbers.
|
||||
|
||||
- **Authenticate**: use the existing login; for a new login, run interactive `tea-cli login add` with the repository's instance URL.
|
||||
- **Create an issue**: `tea-cli issues create --title "..." --description "..." --labels "needs-triage"`. Pass multiline descriptions as one quoted argument.
|
||||
- **Read an issue**: `tea-cli issues --comments <number>`.
|
||||
- **List issues**: `tea-cli issues --state open --labels "..." --fields index,title,body,state,labels,assignees,url --output yaml`. Paginate with `--page` and `--limit` until all matching issues have been read.
|
||||
- **Comment**: `tea-cli comment <number> "..."`.
|
||||
- **Close**: post the explanation first, then `tea-cli issues close <number>`.
|
||||
- **Pull requests**: `tea-cli pulls create --base <integration-branch> --title "..." --description "..."`, `tea-cli pulls --comments <number>`, and `tea-cli pulls --state open --output yaml`.
|
||||
- **Milestones**: `tea-cli milestones --state all --fields id,title --output tsv`. Verify migrated milestone IDs rather than copying source-tracker IDs.
|
||||
|
||||
This version has no JSON output, issue-edit command, or `api` command. Structured output flags apply to lists; detail views render for display. For unsupported operations, use the instance's `/api/swagger` explorer and `/swagger.v1.json` schema, or the Gitea UI. Authenticate API requests with an authorized token without displaying or committing it. API paths below are relative to the instance's `/api/v1`:
|
||||
|
||||
- Read issue details and comments with `GET /repos/<owner>/<repo>/issues/<number>` and its `/comments` endpoint; paginate comments.
|
||||
- Resolve label IDs with `GET /repos/<owner>/<repo>/labels`. Add labels with `POST /repos/<owner>/<repo>/issues/<number>/labels`, body `{"labels":[<label-id>]}`; remove one with `DELETE` on `/labels/<label-id>`. Preserve unrelated labels.
|
||||
- Edit a body or assignment with `PATCH /repos/<owner>/<repo>/issues/<number>`, body `{"body":"..."}` or `{"assignees":["<username>"]}`. Read current assignees before updating the list.
|
||||
- Read a PR diff with `GET /repos/<owner>/<repo>/pulls/<number>.diff`.
|
||||
|
||||
## Pull requests as a triage surface
|
||||
|
||||
**PRs as a request surface: no.** _(Set to `yes` if this repo treats external pull requests as feature requests; `/triage` reads this flag.)_
|
||||
|
||||
When enabled, read and list with `tea-cli pulls`; keep only external requests after checking collaborator or organization membership. Use `tea-cli comment`, the issue-label API, and `tea-cli pulls close <number>` for comment / label / close operations.
|
||||
|
||||
Resolve bare `#<number>` references by checking the resource kind with `tea-cli issues --kind all --fields index,kind,title,url --output yaml` or the issue API's `pull_request` field. Issue URLs use `/issues/<number>`; PR URLs use `/pulls/<number>`.
|
||||
|
||||
## When a skill says "publish to the issue tracker"
|
||||
|
||||
Create a Gitea issue with `tea-cli issues create`.
|
||||
|
||||
## When a skill says "fetch the relevant ticket"
|
||||
|
||||
Run `tea-cli issues --comments <number>` and read its labels and assignees.
|
||||
|
||||
## Wayfinding operations
|
||||
|
||||
- **Map**: an issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. Create it with `tea-cli issues create --title "..." --description "..." --labels "wayfinder:map"`.
|
||||
- **Child ticket**: an issue with `Part of #<map>` at the top of its description, labels `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`), and an ordered link in the map's body.
|
||||
- **Blocking**: `POST /repos/<owner>/<repo>/issues/<child>/dependencies`, body `{"owner":"<owner>","repo":"<repo>","index":<blocker-number>}`. `GET` lists blockers; `DELETE` with the same body removes one. Where native dependencies are unavailable, use `Blocked by: #<n>, #<n>` at the top of the description. Every blocker must be closed before the ticket is unblocked.
|
||||
- **Frontier query**: list all open children with bodies and assignees, drop assigned tickets and those with an open native or description-line blocker; first in map order wins.
|
||||
- **Claim**: assign the authenticated username through the issue API or Gitea UI — the session's first write.
|
||||
- **Resolve**: comment with `tea-cli comment`, close with `tea-cli issues close`, and update the map's Decisions-so-far through the issue API or Gitea UI.
|
||||
@@ -19,7 +19,7 @@ A wizard is ephemeral by default — built for one run, saved to a scratch or `s
|
||||
|
||||
Work out every manual step the human must take and every value that gets captured along the way. Read the repo first — don't ask cold:
|
||||
|
||||
- For setup: configuration examples, README, compose/framework configuration, and the actual CI files (this repository uses `.gitlab-ci.yml`). Inspect secret names and references without displaying existing secret values. Include only values needed for the requested procedure, not every variable in the repository.
|
||||
- For setup: configuration examples, README, compose/framework configuration, and the actual CI files. This repository is hosted on Gitea; read `.gitea/workflows/` and `docs/gitea-actions.md` for the configured workflow and owner-activation requirements. Inspect secret names and references without displaying existing secret values. Include only values needed for the requested procedure, not every variable in the repository.
|
||||
- For a migration or transition: the current state, the target state, and the irreversible actions between them.
|
||||
|
||||
Then show the user the ordered list of stages and the values each produces, and confirm — they may add, drop, or reorder.
|
||||
|
||||
+8
-5
@@ -6,7 +6,8 @@ All AI agents (including Cline, Antigravity, Cursor, and Copilot) must adhere to
|
||||
- The overall project code coverage (Statements, Lines, and Functions) must be maintained at **80% or higher**.
|
||||
- Verify coverage by running:
|
||||
```bash
|
||||
bun run test --watch=false --coverage
|
||||
bun scripts/generate-langs.js
|
||||
NODE_OPTIONS=--max-old-space-size=2048 bunx vitest run --coverage --maxWorkers=1 --exclude='src/app/benchmark/**'
|
||||
```
|
||||
- If your changes introduce new code or modify existing code in a way that drops the coverage below 80%, you must write appropriate unit tests in `.spec.ts` files to bring it back up.
|
||||
|
||||
@@ -24,10 +25,12 @@ All AI agents (including Cline, Antigravity, Cursor, and Copilot) must adhere to
|
||||
bun run build
|
||||
```
|
||||
|
||||
4. **GitLab Pipeline Checks**:
|
||||
- You must execute the local equivalent of the GitLab CI/CD pipeline steps before completing your task. All of the following commands must pass:
|
||||
4. **CI Checks**:
|
||||
- You must execute the local validation commands before completing your task. The Gitea workflow is `.gitea/workflows/ci.yml`; owner activation is tracked in issue #268, and local checks do not satisfy the migration gate. All of the following commands must pass:
|
||||
- **Linting check**: `bun run lint` (or use `bun run lint:fix` to resolve auto-fixable issues)
|
||||
- **Formatting check**: `bun run format:check`
|
||||
- **TypeScript Type check**: `bunx tsc --noEmit`
|
||||
- **Unit Tests and Coverage**: `bun run test --watch=false --coverage`
|
||||
- **TypeScript Type check**: `bunx tsc --noEmit -p tsconfig.app.json` and `bunx tsc --noEmit -p tsconfig.spec.json`
|
||||
- **Unit Tests and Coverage**: generate the language registry, then run the coverage command in rule 1, matching the Gitea functional-test job.
|
||||
- **CI policy and publication**: `node scripts/ci-policy.test.mjs` and `node scripts/ci-image.test.mjs`
|
||||
- **Production Build**: `bun run build -- --configuration production`
|
||||
- The separate performance benchmark remains advisory while issue #78 is open. Run it outside coverage with `NODE_OPTIONS=--max-old-space-size=2048 bunx vitest run src/app/benchmark/leave-planner-benchmark.spec.ts --maxWorkers=1` when verifying performance; see `docs/benchmark/README.md`.
|
||||
@@ -0,0 +1,5 @@
|
||||
${PullRequestTitle}
|
||||
|
||||
${ClosingIssues}
|
||||
|
||||
See pull request ${PullRequestReference}
|
||||
@@ -0,0 +1,210 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, edited]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
|
||||
jobs:
|
||||
policy:
|
||||
name: policy
|
||||
runs-on: ubuntu-latest
|
||||
container: node:24.21.0
|
||||
outputs:
|
||||
slug: ${{ steps.policy.outputs.slug }}
|
||||
tag: ${{ steps.policy.outputs.tag }}
|
||||
steps:
|
||||
- uses: https://gitea.com/actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Policy regression tests
|
||||
run: node scripts/ci-policy.test.mjs && node scripts/ci-image.test.mjs
|
||||
- name: Validate event and commit
|
||||
id: policy
|
||||
run: node scripts/ci-policy.mjs
|
||||
|
||||
validate:
|
||||
name: validate
|
||||
runs-on: ubuntu-latest
|
||||
container: node:24.21.0
|
||||
steps:
|
||||
- uses: https://gitea.com/actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- run: npm install --global bun@1.4.2
|
||||
- run: bun install --frozen-lockfile
|
||||
- run: bun run lint
|
||||
- run: bun run format:check
|
||||
- run: bunx prettier --check 'scripts/ci-*.mjs' '.gitea/workflows/*.yml'
|
||||
- run: bunx tsc --noEmit -p tsconfig.app.json
|
||||
- run: bunx tsc --noEmit -p tsconfig.spec.json
|
||||
|
||||
test:
|
||||
name: test
|
||||
runs-on: ubuntu-latest
|
||||
container: node:24.21.0
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=2048
|
||||
steps:
|
||||
- uses: https://gitea.com/actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- run: npm install --global bun@1.4.2
|
||||
- run: bun install --frozen-lockfile
|
||||
- run: bun scripts/generate-langs.js
|
||||
- name: Functional coverage
|
||||
run: bunx vitest run --coverage --maxWorkers=1 --exclude='src/app/benchmark/**'
|
||||
- name: Coverage report
|
||||
if: ${{ always() }}
|
||||
uses: https://gitea.com/actions/upload-artifact@v3
|
||||
with:
|
||||
name: coverage
|
||||
path: coverage/
|
||||
retention-days: 7
|
||||
if-no-files-found: error
|
||||
|
||||
benchmark:
|
||||
name: benchmark
|
||||
runs-on: ubuntu-latest
|
||||
container: node:24.21.0
|
||||
# shortcut: advisory while #78 is open; restore the gate after the protocol passes.
|
||||
continue-on-error: true
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=2048
|
||||
steps:
|
||||
- uses: https://gitea.com/actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- run: npm install --global bun@1.4.2
|
||||
- run: bun install --frozen-lockfile
|
||||
- run: bun scripts/generate-langs.js
|
||||
- name: Unchanged benchmark protocol
|
||||
run: timeout 300s bunx vitest run src/app/benchmark/leave-planner-benchmark.spec.ts --maxWorkers=1
|
||||
|
||||
build:
|
||||
name: build
|
||||
runs-on: ubuntu-latest
|
||||
container: node:24.21.0
|
||||
steps:
|
||||
- uses: https://gitea.com/actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- run: npm install --global bun@1.4.2
|
||||
- run: bun install --frozen-lockfile
|
||||
- run: bun run build -- --configuration production
|
||||
- uses: https://gitea.com/actions/upload-artifact@v3
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
retention-days: 7
|
||||
if-no-files-found: error
|
||||
|
||||
security:
|
||||
name: security
|
||||
runs-on: ubuntu-latest
|
||||
container: node:24.21.0
|
||||
steps:
|
||||
- uses: https://gitea.com/actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install scanners
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install --yes --no-install-recommends python3-venv
|
||||
python3 -m venv /tmp/semgrep
|
||||
/tmp/semgrep/bin/pip install semgrep==1.180.0
|
||||
case "$(uname -m)" in
|
||||
x86_64) trivy_arch=64bit ;;
|
||||
aarch64) trivy_arch=ARM64 ;;
|
||||
*) echo 'Unsupported Trivy architecture' >&2; exit 1 ;;
|
||||
esac
|
||||
archive="trivy_0.75.0_Linux-${trivy_arch}.tar.gz"
|
||||
curl --fail --location --silent --show-error "https://github.com/aquasecurity/trivy/releases/download/v0.75.0/${archive}" --output "/tmp/${archive}"
|
||||
curl --fail --location --silent --show-error https://github.com/aquasecurity/trivy/releases/download/v0.75.0/trivy_0.75.0_checksums.txt --output /tmp/trivy-checksums.txt
|
||||
(cd /tmp && sha256sum --check --ignore-missing trivy-checksums.txt)
|
||||
tar -xzf "/tmp/${archive}" -C /usr/local/bin trivy
|
||||
- name: JavaScript and TypeScript SAST
|
||||
run: /tmp/semgrep/bin/semgrep scan --config p/javascript --config p/typescript --metrics off --sarif --output semgrep-results.sarif src scripts
|
||||
- name: Dependency and secret scan
|
||||
if: ${{ !cancelled() }}
|
||||
run: trivy fs --format sarif --output trivy-results.sarif --skip-dirs node_modules,dist,coverage .
|
||||
- name: Security reports
|
||||
if: ${{ always() }}
|
||||
uses: https://gitea.com/actions/upload-artifact@v3
|
||||
with:
|
||||
name: security
|
||||
path: '*-results.sarif'
|
||||
retention-days: 7
|
||||
if-no-files-found: error
|
||||
|
||||
images:
|
||||
name: images
|
||||
needs: [policy, validate, test, build, security]
|
||||
if: >-
|
||||
success() &&
|
||||
(github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository))
|
||||
runs-on: ubuntu-latest
|
||||
container: docker:29.8.2
|
||||
concurrency:
|
||||
group: image-${{ needs.policy.outputs.tag != '' && 'stable' || needs.policy.outputs.slug }}
|
||||
cancel-in-progress: false
|
||||
services:
|
||||
docker:
|
||||
image: docker:29.8.2-dind
|
||||
env:
|
||||
DOCKER_TLS_CERTDIR: ''
|
||||
options: >-
|
||||
--privileged
|
||||
--health-cmd "docker info"
|
||||
--health-interval 5s
|
||||
--health-timeout 5s
|
||||
--health-retries 12
|
||||
env:
|
||||
DOCKER_HOST: tcp://docker:2375
|
||||
DOCKER_TLS_CERTDIR: ''
|
||||
defaults:
|
||||
run:
|
||||
shell: sh
|
||||
steps:
|
||||
- name: Install checkout and policy runtimes
|
||||
run: apk add --no-cache nodejs git
|
||||
- uses: https://gitea.com/actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Build branch images or promote a release
|
||||
env:
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: node scripts/ci-image.mjs
|
||||
|
||||
required:
|
||||
name: required
|
||||
if: ${{ always() }}
|
||||
needs: [policy, validate, test, build, security, images]
|
||||
runs-on: ubuntu-latest
|
||||
container: node:24.21.0
|
||||
env:
|
||||
CHECK_RESULTS: ${{ needs.policy.result }} ${{ needs.validate.result }} ${{ needs.test.result }} ${{ needs.build.result }} ${{ needs.security.result }}
|
||||
IMAGE_RESULT: ${{ needs.images.result }}
|
||||
IMAGE_EXPECTED: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) }}
|
||||
steps:
|
||||
- name: Require successful validation and expected publication
|
||||
run: |
|
||||
test "$CHECK_RESULTS" = 'success success success success success'
|
||||
if [ "$IMAGE_EXPECTED" = 'true' ]; then
|
||||
test "$IMAGE_RESULT" = 'success'
|
||||
else
|
||||
test "$IMAGE_RESULT" = 'skipped'
|
||||
fi
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
image: oven/bun:latest
|
||||
|
||||
workflow:
|
||||
auto_cancel:
|
||||
on_new_commit: interruptible
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push"
|
||||
when: never
|
||||
- if: $CI_PIPELINE_SOURCE == "push"
|
||||
|
||||
default:
|
||||
interruptible: true
|
||||
|
||||
stages:
|
||||
- validate
|
||||
- test
|
||||
- build
|
||||
- security
|
||||
- docker
|
||||
|
||||
variables:
|
||||
DOCKER_DRIVER: overlay2
|
||||
DOCKER_TLS_CERTDIR: ''
|
||||
AST_ENABLE_MR_PIPELINES: 'true'
|
||||
|
||||
cache:
|
||||
key:
|
||||
files:
|
||||
- bun.lock
|
||||
paths:
|
||||
- .bun/
|
||||
|
||||
.node_setup:
|
||||
rules:
|
||||
- when: on_success
|
||||
before_script:
|
||||
- bun install --frozen-lockfile
|
||||
|
||||
# -- Validate Stage --
|
||||
|
||||
ci-policy:
|
||||
image: node:24.21.0
|
||||
stage: validate
|
||||
variables:
|
||||
GIT_DEPTH: '0'
|
||||
rules:
|
||||
- when: on_success
|
||||
script:
|
||||
- node scripts/ci-policy.test.mjs
|
||||
- node scripts/ci-policy.mjs
|
||||
|
||||
lint:
|
||||
extends: .node_setup
|
||||
stage: validate
|
||||
script:
|
||||
- bunx eslint "src/**/*.ts"
|
||||
|
||||
prettier:
|
||||
extends: .node_setup
|
||||
stage: validate
|
||||
script:
|
||||
- bunx prettier --check "src/**/*.{ts,html,scss,css,json}"
|
||||
|
||||
type-check:
|
||||
extends: .node_setup
|
||||
stage: validate
|
||||
script:
|
||||
- bunx tsc --noEmit
|
||||
|
||||
# -- Test Stage --
|
||||
|
||||
test:
|
||||
image: node:24.21.0
|
||||
extends: .node_setup
|
||||
stage: test
|
||||
variables:
|
||||
NODE_OPTIONS: --max-old-space-size=2048
|
||||
before_script:
|
||||
- npm install --global bun@1.4.2
|
||||
- bun install --frozen-lockfile
|
||||
script:
|
||||
- |
|
||||
node -e 'console.log("CI memory (bytes)", { available: process.availableMemory(), heapLimit: require("node:v8").getHeapStatistics().heap_size_limit })'
|
||||
- bun scripts/generate-langs.js
|
||||
# ponytail: one worker bounds peak RAM; increase concurrency after adding runner memory.
|
||||
- bunx vitest run --coverage --maxWorkers=1 --exclude='src/app/benchmark/**'
|
||||
artifacts:
|
||||
when: always
|
||||
paths:
|
||||
- coverage/
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage/cobertura-coverage.xml
|
||||
expire_in: 7 days
|
||||
|
||||
# shortcut: benchmark failures are advisory while #78 is open; restore the gate when #78 passes.
|
||||
benchmark:
|
||||
extends: test
|
||||
allow_failure: true
|
||||
script:
|
||||
- bun scripts/generate-langs.js
|
||||
- bunx vitest run src/app/benchmark/leave-planner-benchmark.spec.ts --maxWorkers=1
|
||||
artifacts: null
|
||||
|
||||
# -- Build Stage --
|
||||
|
||||
build:
|
||||
image: node:24.21.0
|
||||
extends: .node_setup
|
||||
stage: build
|
||||
before_script:
|
||||
- npm install --global bun@1.4.2
|
||||
- bun install --frozen-lockfile
|
||||
script:
|
||||
- bun run build -- --configuration production
|
||||
artifacts:
|
||||
paths:
|
||||
- dist/
|
||||
expire_in: 7 days
|
||||
|
||||
# -- Security Stage --
|
||||
|
||||
include:
|
||||
- template: Security/SAST.gitlab-ci.yml
|
||||
|
||||
trivy_scan:
|
||||
stage: security
|
||||
image:
|
||||
name: aquasec/trivy:latest
|
||||
entrypoint: ['']
|
||||
before_script: [] # override global before_script
|
||||
rules:
|
||||
- when: on_success
|
||||
script:
|
||||
# Trivy will scan the project directory
|
||||
- trivy fs --format sarif --output trivy-results.sarif .
|
||||
artifacts:
|
||||
reports:
|
||||
sast: trivy-results.sarif
|
||||
expire_in: 7 days
|
||||
|
||||
# -- Docker Stage --
|
||||
|
||||
.docker:
|
||||
stage: docker
|
||||
image: docker:29.8.2
|
||||
services:
|
||||
- docker:29.8.2-dind
|
||||
before_script:
|
||||
- printf '%s' "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin
|
||||
cache: []
|
||||
dependencies: []
|
||||
|
||||
docker-build:
|
||||
extends: .docker
|
||||
resource_group: image-$CI_COMMIT_REF_SLUG
|
||||
script:
|
||||
# ponytail: GitLab slugs truncate at 63 bytes; add a branch-name hash if colliding names are needed.
|
||||
- docker build -t "$CI_REGISTRY_IMAGE:branch-$CI_COMMIT_REF_SLUG" -t "$CI_REGISTRY_IMAGE:sha-$CI_COMMIT_SHA" .
|
||||
- docker push "$CI_REGISTRY_IMAGE:sha-$CI_COMMIT_SHA"
|
||||
- docker push "$CI_REGISTRY_IMAGE:branch-$CI_COMMIT_REF_SLUG"
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_EVENT_TYPE == "detached" && $CI_MERGE_REQUEST_SOURCE_PROJECT_ID == $CI_PROJECT_ID
|
||||
|
||||
docker-release:
|
||||
extends: .docker
|
||||
interruptible: false
|
||||
resource_group: stable-image
|
||||
script:
|
||||
- docker pull "$CI_REGISTRY_IMAGE:sha-$CI_COMMIT_SHA"
|
||||
- docker tag "$CI_REGISTRY_IMAGE:sha-$CI_COMMIT_SHA" "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"
|
||||
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"
|
||||
- docker tag "$CI_REGISTRY_IMAGE:sha-$CI_COMMIT_SHA" "$CI_REGISTRY_IMAGE:latest"
|
||||
- docker push "$CI_REGISTRY_IMAGE:latest"
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
+2
-2
@@ -6,9 +6,9 @@
|
||||
|
||||
**Status:** ready-for-agent
|
||||
|
||||
**GitLab:** [#61](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/61)
|
||||
**Gitea:** [#61](https://gitea.nessar.fr/nessar/vac-optimizer/issues/61)
|
||||
|
||||
Source : [issue #13](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/13), entretien et spécification de développement. Respecter le glossaire du domaine et les ADR 0002, 0005, 0006 et 0007.
|
||||
Source : [issue #13](https://gitea.nessar.fr/nessar/vac-optimizer/issues/13), entretien et spécification de développement. Respecter le glossaire du domaine et les ADR 0002, 0005, 0006 et 0007.
|
||||
|
||||
- [ ] Un mode explicite « Saisir les jours déjà posés » présente l’horizon actuellement choisi et permet de sélectionner CP, RTT ou Autre. Autre couvre toutes les autres raisons de ne pas travailler ; ANY reste uniquement un sélecteur de règle.
|
||||
- [ ] Un clic sur une date admissible ajoute le type sélectionné ; un deuxième clic avec ce type supprime la déclaration ; un clic avec un autre type la remplace. Une date porte au maximum une déclaration.
|
||||
|
||||
+3
-3
@@ -4,13 +4,13 @@
|
||||
|
||||
**Blocked by:**
|
||||
|
||||
- 01 — Saisir, sauvegarder et figer les jours déjà posés ([#61](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/61)).
|
||||
- 01 — Saisir, sauvegarder et figer les jours déjà posés ([#61](https://gitea.nessar.fr/nessar/vac-optimizer/issues/61)).
|
||||
|
||||
**Status:** ready-for-agent
|
||||
|
||||
**GitLab:** [#62](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/62)
|
||||
**Gitea:** [#62](https://gitea.nessar.fr/nessar/vac-optimizer/issues/62)
|
||||
|
||||
Source : [issue #13](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/13), entretien et spécification de développement. Respecter les définitions de période de repos, gain en jours non travaillés, séquence de travail et semaine de jour férié du glossaire, ainsi que les ADR 0006 et 0007.
|
||||
Source : [issue #13](https://gitea.nessar.fr/nessar/vac-optimizer/issues/13), entretien et spécification de développement. Respecter les définitions de période de repos, gain en jours non travaillés, séquence de travail et semaine de jour férié du glossaire, ainsi que les ADR 0006 et 0007.
|
||||
|
||||
- [ ] Un vendredi ordinaire déclaré Autre, sans solde ni règle, produit une période vendredi–dimanche de trois jours. Cette période existe sans nouveau placement et ne provoque aucun débit.
|
||||
- [ ] Les déclarations, nouveaux placements, week-ends et jours fériés adjacents forment des périodes contiguës fusionnées. Chaque date contribue une seule fois à leur durée ; l’origine et le type des déclarations restent distincts des placements.
|
||||
|
||||
+3
-3
@@ -4,13 +4,13 @@
|
||||
|
||||
**Blocked by:**
|
||||
|
||||
- 01 — Saisir, sauvegarder et figer les jours déjà posés ([#61](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/61)).
|
||||
- 01 — Saisir, sauvegarder et figer les jours déjà posés ([#61](https://gitea.nessar.fr/nessar/vac-optimizer/issues/61)).
|
||||
|
||||
**Status:** ready-for-agent
|
||||
|
||||
**GitLab:** [#63](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/63)
|
||||
**Gitea:** [#63](https://gitea.nessar.fr/nessar/vac-optimizer/issues/63)
|
||||
|
||||
Source : [issue #13](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/13), entretien et spécification de développement. Respecter les quantités, fenêtres inclusives, obligations conjointes, priorités et contributions partagées du glossaire, ainsi que l’ADR 0007.
|
||||
Source : [issue #13](https://gitea.nessar.fr/nessar/vac-optimizer/issues/13), entretien et spécification de développement. Respecter les quantités, fenêtres inclusives, obligations conjointes, priorités et contributions partagées du glossaire, ainsi que l’ADR 0007.
|
||||
|
||||
- [ ] Dans une semaine ordinaire, avec quatre CP restants, un mercredi déclaré CP et une obligation de cinq CP du lundi au vendredi, le calcul ajoute quatre CP et débite uniquement ces quatre jours.
|
||||
- [ ] Les déclarations compatibles contribuent aux obligations exactes et aux préférences demandant jusqu’à une quantité donnée, dans l’intersection entre la fenêtre inclusive de la règle et l’horizon soumis. Une déclaration extérieure à ces limites ne contribue pas à la règle.
|
||||
|
||||
+4
-4
@@ -4,14 +4,14 @@
|
||||
|
||||
**Blocked by:**
|
||||
|
||||
- 02 — Optimiser les périodes de repos autour des jours déclarés ([#62](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/62)).
|
||||
- 03 — Compter les jours déclarés dans les règles de période ([#63](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/63)).
|
||||
- 02 — Optimiser les périodes de repos autour des jours déclarés ([#62](https://gitea.nessar.fr/nessar/vac-optimizer/issues/62)).
|
||||
- 03 — Compter les jours déclarés dans les règles de période ([#63](https://gitea.nessar.fr/nessar/vac-optimizer/issues/63)).
|
||||
|
||||
**Status:** ready-for-agent
|
||||
|
||||
**GitLab:** [#64](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/64)
|
||||
**Gitea:** [#64](https://gitea.nessar.fr/nessar/vac-optimizer/issues/64)
|
||||
|
||||
Source : [issue #13](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/13), entretien et spécification de développement. Respecter les occurrences et modes IF_THEN, les droits acquis et l’ordre des objectifs du glossaire, ainsi que les ADR sur les artefacts du même calcul et l’ADR 0007.
|
||||
Source : [issue #13](https://gitea.nessar.fr/nessar/vac-optimizer/issues/13), entretien et spécification de développement. Respecter les occurrences et modes IF_THEN, les droits acquis et l’ordre des objectifs du glossaire, ainsi que les ADR sur les artefacts du même calcul et l’ADR 0007.
|
||||
|
||||
- [ ] Les motifs IF_THEN utilisent les véritables jours fériés français nationaux et les modes before, after, around et auto existants. Une déclaration ne crée pas une occurrence de jour férié et ne permet pas de déplacer le motif hors de ses limites admissibles.
|
||||
- [ ] Une déclaration compatible occupe sa position normale dans le motif et réduit le nombre de nouveaux placements nécessaires. Une déclaration incompatible ne peut être retypée ou déplacée, ni être sautée pour reporter la position demandée sur un jour plus éloigné. Les obligations restent obligatoires pour chaque occurrence ; les préférences conservent leur exigence de motif complet et leurs priorités.
|
||||
|
||||
+2
-2
@@ -6,9 +6,9 @@
|
||||
|
||||
**Status:** ready-for-agent
|
||||
|
||||
**GitLab:** [#65](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/65)
|
||||
**Gitea:** [#65](https://gitea.nessar.fr/nessar/vac-optimizer/issues/65)
|
||||
|
||||
Source: the agreed Plan Statistics Popup spec for [#3](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/3) and [#29](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/29), in milestone %3: V1 stats. Follow the domain glossary and ADRs 0002, 0004, 0006, and 0007.
|
||||
Source: the agreed Plan Statistics Popup spec for [#3](https://gitea.nessar.fr/nessar/vac-optimizer/issues/3) and [#29](https://gitea.nessar.fr/nessar/vac-optimizer/issues/29), in milestone V1 stats. Follow the domain glossary and ADRs 0002, 0004, 0006, and 0007.
|
||||
|
||||
- [ ] A labelled calendar control opens one statistics popup for the latest calculated valid plan. Opening it runs no optimization and changes no planning data.
|
||||
- [ ] The popup shows the number of leave placement dates with an explicit leave-day label. Declared non-working days consume no balance and are excluded from this count. A valid plan with no placements displays 0 and still permits opening the popup.
|
||||
|
||||
+3
-3
@@ -5,13 +5,13 @@
|
||||
**Blocked by:**
|
||||
|
||||
- 01 — Open plan statistics for calculated leave usage.
|
||||
- [#62 — Optimiser les périodes de repos autour des jours déclarés](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/62), which establishes time-off periods and work sequences that include declared non-working days.
|
||||
- [#62 — Optimiser les périodes de repos autour des jours déclarés](https://gitea.nessar.fr/nessar/vac-optimizer/issues/62), which establishes time-off periods and work sequences that include declared non-working days.
|
||||
|
||||
**Status:** ready-for-agent
|
||||
|
||||
**GitLab:** [#66](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/66)
|
||||
**Gitea:** [#66](https://gitea.nessar.fr/nessar/vac-optimizer/issues/66)
|
||||
|
||||
Source: the agreed Plan Statistics Popup spec for [#3](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/3) and [#29](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/29), including [#7](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/7). The existing longest work-sequence metric from #28 is reused. Follow the domain glossary and ADRs 0004, 0006, and 0007.
|
||||
Source: the agreed Plan Statistics Popup spec for [#3](https://gitea.nessar.fr/nessar/vac-optimizer/issues/3) and [#29](https://gitea.nessar.fr/nessar/vac-optimizer/issues/29), including [#7](https://gitea.nessar.fr/nessar/vac-optimizer/issues/7). The existing longest work-sequence metric from #28 is reused. Follow the domain glossary and ADRs 0004, 0006, and 0007.
|
||||
|
||||
- [ ] Add five labelled metrics: non-working-day gain, average work-sequence length, longest work-sequence length, average time-off period length, and longest time-off period length. Work lengths use working days; break lengths use calendar days.
|
||||
- [ ] The work-sequence average includes every non-empty sequence inside the submitted horizon, including both edges. Weekends and French public holidays neither count nor split a sequence; leave placements and declared non-working days interrupt it. Consecutive days off introduce no zero-length samples.
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
**Blocked by:**
|
||||
|
||||
- 01 — Open plan statistics for calculated leave usage.
|
||||
- [#64 — Appliquer les motifs IF_THEN et les droits acquis aux jours déclarés](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/64), which establishes finalized earned entitlements and the fixed Europe/Paris calculation-date cutoff.
|
||||
- [#64 — Appliquer les motifs IF_THEN et les droits acquis aux jours déclarés](https://gitea.nessar.fr/nessar/vac-optimizer/issues/64), which establishes finalized earned entitlements and the fixed Europe/Paris calculation-date cutoff.
|
||||
|
||||
**Status:** ready-for-agent
|
||||
|
||||
**GitLab:** [#67](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/67)
|
||||
**Gitea:** [#67](https://gitea.nessar.fr/nessar/vac-optimizer/issues/67)
|
||||
|
||||
Source: the agreed Plan Statistics Popup spec for [#3](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/3) and [#29](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/29), including [#8](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/8). Follow the domain glossary and ADRs 0004, 0006, and 0007.
|
||||
Source: the agreed Plan Statistics Popup spec for [#3](https://gitea.nessar.fr/nessar/vac-optimizer/issues/3) and [#29](https://gitea.nessar.fr/nessar/vac-optimizer/issues/29), including [#8](https://gitea.nessar.fr/nessar/vac-optimizer/issues/8). Follow the domain glossary and ADRs 0004, 0006, and 0007.
|
||||
|
||||
- [ ] Display gross earned leave from every applicable generating rule, including Samedi Malin, with a per-rule breakdown whose quantities sum to the total.
|
||||
- [ ] A plan earning 3 days and consuming 2 reports 3 earned days. Fully consumed earned entitlements remain included even when absent from remaining balances; generating a future entitlement does not require consuming it inside the horizon.
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
**Blocked by:**
|
||||
|
||||
- 01 — Open plan statistics for calculated leave usage.
|
||||
- [#62 — Optimiser les périodes de repos autour des jours déclarés](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/62), which provides shared time-off periods including declared non-working days and adjacent context dates.
|
||||
- [#62 — Optimiser les périodes de repos autour des jours déclarés](https://gitea.nessar.fr/nessar/vac-optimizer/issues/62), which provides shared time-off periods including declared non-working days and adjacent context dates.
|
||||
|
||||
**Status:** ready-for-agent
|
||||
|
||||
**GitLab:** [#68](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/68)
|
||||
**Gitea:** [#68](https://gitea.nessar.fr/nessar/vac-optimizer/issues/68)
|
||||
|
||||
Source: the agreed Plan Statistics Popup spec for [#3](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/3) and [#29](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/29), including [#9](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/9). Follow the domain glossary and ADRs 0004, 0006, and 0007.
|
||||
Source: the agreed Plan Statistics Popup spec for [#3](https://gitea.nessar.fr/nessar/vac-optimizer/issues/3) and [#29](https://gitea.nessar.fr/nessar/vac-optimizer/issues/29), including [#9](https://gitea.nessar.fr/nessar/vac-optimizer/issues/9). Follow the domain glossary and ADRs 0004, 0006, and 0007.
|
||||
|
||||
- [ ] Show the number of distinct Monday-to-Friday French public-holiday dates in planned time-off periods containing at least one Saturday or Sunday. Each qualifying period contains at least one leave placement or declared non-working day.
|
||||
- [ ] Count direct Monday/Friday connections and connections through leave, such as a Tuesday holiday joined to a weekend by Monday off. Declared non-working days can provide the same connection without consuming a balance.
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
**Blocked by:**
|
||||
|
||||
- 01 — Open plan statistics for calculated leave usage.
|
||||
- [#61 — Saisir, sauvegarder et figer les jours déjà posés](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/61), which carries fixed declared non-working days with the producing calculation and its outcomes.
|
||||
- [#61 — Saisir, sauvegarder et figer les jours déjà posés](https://gitea.nessar.fr/nessar/vac-optimizer/issues/61), which carries fixed declared non-working days with the producing calculation and its outcomes.
|
||||
|
||||
**Status:** ready-for-agent
|
||||
|
||||
**GitLab:** [#69](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/69)
|
||||
**Gitea:** [#69](https://gitea.nessar.fr/nessar/vac-optimizer/issues/69)
|
||||
|
||||
Source: the agreed Plan Statistics Popup spec for [#3](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/3) and [#29](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/29), including the clarification that working only one day in a full week is undesirable. Follow the domain glossary and ADRs 0004, 0006, and 0007.
|
||||
Source: the agreed Plan Statistics Popup spec for [#3](https://gitea.nessar.fr/nessar/vac-optimizer/issues/3) and [#29](https://gitea.nessar.fr/nessar/vac-optimizer/issues/29), including the clarification that working only one day in a full week is undesirable. Follow the domain glossary and ADRs 0004, 0006, and 0007.
|
||||
|
||||
- [ ] Count Monday-to-Sunday calendar weeks whose five Monday-to-Friday dates all lie inside the submitted half-open horizon and that contain exactly one remaining working day after excluding French public holidays, leave placements, and declared non-working days.
|
||||
- [ ] Four weekdays off and one remaining workday qualify. Weeks with zero or two remaining workdays do not qualify, including two-workday weeks containing separate one-day work sequences.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# 01 — Unifier l’évaluation et la comparaison des objectifs
|
||||
|
||||
**GitLab:** [#70](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/70)
|
||||
**Gitea:** [#70](https://gitea.nessar.fr/nessar/vac-optimizer/issues/70)
|
||||
|
||||
**What to build:** Le planificateur conserve ses résultats actuels pendant une préparation qui rend les six objectifs évaluables à partir de configurations de règles. Le contrat commun permet ensuite aux règles fournies et aux règles personnelles d’utiliser les mêmes mesures, sans dépendre de leur nom ou de leur identité.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# 02 — Piloter la prévention des pertes depuis les règles
|
||||
|
||||
**GitLab:** [#71](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/71)
|
||||
**Gitea:** [#71](https://gitea.nessar.fr/nessar/vac-optimizer/issues/71)
|
||||
|
||||
**What to build:** L’utilisateur crée une règle d’optimisation de prévention des pertes, l’enregistre, la déplace parmi ses préférences et constate son effet sur les congés placés et les soldes restants. La désactiver retire réellement sa priorité automatique.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 03 — Composer les règles de semaines fériées et de repos
|
||||
|
||||
**GitLab:** [#72](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/72)
|
||||
**Gitea:** [#72](https://gitea.nessar.fr/nessar/vac-optimizer/issues/72)
|
||||
|
||||
**What to build:** L’utilisateur crée et règle les préférences « Compléter les semaines avec un jour férié » et « Profiter des week-ends et jours fériés », puis choisit laquelle prime dans le planning et dans ses explications.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 04 — Composer les règles de séquences de travail
|
||||
|
||||
**GitLab:** [#73](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/73)
|
||||
**Gitea:** [#73](https://gitea.nessar.fr/nessar/vac-optimizer/issues/73)
|
||||
|
||||
**What to build:** L’utilisateur compose des préférences sur ses séquences de travail : réduire la plus longue séquence ou limiter le déficit des reprises courtes, avec un seuil configurable. Leur position parmi les autres préférences influence le planning.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 05 — Piloter l’économie des jours d’appoint
|
||||
|
||||
**GitLab:** [#74](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/74)
|
||||
**Gitea:** [#74](https://gitea.nessar.fr/nessar/vac-optimizer/issues/74)
|
||||
|
||||
**What to build:** L’utilisateur ajoute une préférence « Économiser les jours d’appoint » et décide, par sa position, jusqu’où les améliorations du planning peuvent consommer des congés qui expirent après l’horizon.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# 06 — Migrer les règles sauvegardées et valider le comportement fonctionnel
|
||||
|
||||
**GitLab:** [#75](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/75)
|
||||
**Gitea:** [#75](https://gitea.nessar.fr/nessar/vac-optimizer/issues/75)
|
||||
|
||||
Blocked by: #74
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 01 — Remove the original decision trace end to end
|
||||
|
||||
**GitLab:** [#76](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/76)
|
||||
**Gitea:** [#76](https://gitea.nessar.fr/nessar/vac-optimizer/issues/76)
|
||||
|
||||
## What to build
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 02 — Verify integrated planning after trace removal
|
||||
|
||||
**GitLab:** [#77](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/77)
|
||||
**Gitea:** [#77](https://gitea.nessar.fr/nessar/vac-optimizer/issues/77)
|
||||
|
||||
Blocked by: #76
|
||||
|
||||
@@ -24,4 +24,4 @@ The removal is agreed; its measured speedup is unresolved and is now evaluated i
|
||||
|
||||
## Blocked by
|
||||
|
||||
- #76 — Remove the original decision trace end to end (https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/76).
|
||||
- #76 — Remove the original decision trace end to end (https://gitea.nessar.fr/nessar/vac-optimizer/issues/76).
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# 03 — Verify and optimize planning to meet the benchmarks from #75
|
||||
|
||||
**GitLab:** [#78](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/78)
|
||||
**Gitea:** [#78](https://gitea.nessar.fr/nessar/vac-optimizer/issues/78)
|
||||
|
||||
Blocked by: #75, #77
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
### Issue tracker
|
||||
|
||||
Issues and specs live in GitLab Issues; use `glab` for tracker operations. See `docs/agents/issue-tracker.md`.
|
||||
Issues and specs live in Gitea Issues; use `tea-cli` for supported tracker operations. Before tracker work, read `docs/agents/issue-tracker.md` for commands and API alternatives.
|
||||
|
||||
### Triage labels
|
||||
|
||||
@@ -14,11 +14,13 @@ This is a single-context repo using root `CONTEXT.md` and `docs/adr/`. See `docs
|
||||
|
||||
## GitFlow
|
||||
|
||||
- `develop` is the default integration branch; `main` contains releases. Start `feature/*` branches from the current `origin/develop` and target their MRs to `develop`.
|
||||
- Rebase working branches onto the latest `origin/develop` before opening or merging an MR. If `develop` advances, rebase and rerun the MR pipeline before merging.
|
||||
- Integrate through MRs with successful pipelines and Maintainer-only merges. Use merge commits (`--no-ff`) with squashing disabled. Direct and forced pushes to `main` and `develop` are forbidden.
|
||||
- Use Conventional Commits for new commits and MR titles.
|
||||
- Create `release/X.Y.Z` from `develop` and `hotfix/X.Y.Z` from `main`. Only `release/*` and `hotfix/*` MRs target `main`. Merge each release or hotfix into both `main` and `develop`, keeping its branch until both MRs are merged.
|
||||
- `develop` is the default integration branch; `main` contains releases. Start `feature/*` branches from the current `origin/develop` and target their PRs to `develop`.
|
||||
- Rebase working branches onto the latest `origin/develop` before opening or merging a PR. If `develop` advances, rebase and rerun the PR checks before merging.
|
||||
- Integrate through PRs with successful required checks and merges restricted to designated maintainers through Gitea's merge allowlist. Use merge commits (`--no-ff`) with squashing disabled. Direct and forced pushes to `main` and `develop` are forbidden.
|
||||
- Use Conventional Commits for new commits and PR titles.
|
||||
- Create `release/X.Y.Z` from `develop` and `hotfix/X.Y.Z` from `main`. Only `release/*` and `hotfix/*` PRs target `main`. Merge each release or hotfix into both `main` and `develop`, keeping its branch until both PRs are merged.
|
||||
- Use merges and back-merges for releases and hotfixes. Never rebase `main`, `develop`, or a frozen release branch.
|
||||
|
||||
The CI workflow is `.gitea/workflows/ci.yml`. Activation is tracked in Gitea issue #268; Actions is enabled with owner approval, and the migration gate stays required until live workflow validation and merge-message review. Before changing CI, image publication, or activation settings, read [Gitea Actions](docs/gitea-actions.md).
|
||||
|
||||
Before preparing a release or hotfix, creating a tag, or changing image publication, read [GitFlow and releases](README.md#gitflow-and-releases) for package versions, explicit stable tags, and image promotion.
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
VacOptimizer is a personal leave planner for turning dated leave balances into a deterministic plan around weekends and French public holidays. It handles mandatory rules, ordered preferences, earned leave, expiring balances, and interrupted calculations without relying on a backend solver.
|
||||
|
||||
Source code, issues, and pull requests are hosted on [Gitea](https://gitea.nessar.fr/nessar/vac-optimizer). The [issue-tracker guide](docs/agents/issue-tracker.md) documents `tea-cli` access and tracker conventions.
|
||||
|
||||
The planner answers these questions:
|
||||
|
||||
- Is the request valid?
|
||||
@@ -164,7 +166,7 @@ The trace explorer navigates original phases and atomic decisions, expands candi
|
||||
- Vitest through the Angular test builder
|
||||
- ESLint and Prettier
|
||||
- Angular server build hosted by Express
|
||||
- GitLab CI and a multi-stage Docker image
|
||||
- Gitea repository hosting and a multi-stage Docker image
|
||||
|
||||
The `/benchmark` route and planner core are lazy-loaded, so the normal application does not execute the benchmark or bundle the planner core into its main entry point.
|
||||
|
||||
@@ -236,37 +238,34 @@ The image builds the Angular application with Bun and Node, then runs the genera
|
||||
|
||||
## Continuous integration
|
||||
|
||||
GitLab CI runs independent validation, test, build, security, and image stages:
|
||||
[`.gitea/workflows/ci.yml`](.gitea/workflows/ci.yml) defines Gitea Actions validation, functional coverage, production builds, security scans, and Docker image publication. [Issue #268](https://gitea.nessar.fr/nessar/vac-optimizer/issues/268) tracks activation. Actions is enabled with owner approval; publisher credentials and live workflow validation are still pending. The [activation guide](docs/gitea-actions.md) records runner, credential, permission, and merge-gate requirements.
|
||||
|
||||
- ESLint, Prettier, and TypeScript checks;
|
||||
- Vitest with coverage artifacts;
|
||||
- an optimized production build;
|
||||
- GitLab SAST and Trivy filesystem scanning;
|
||||
- GitFlow, Conventional Commit, and release-version checks;
|
||||
- Docker image publication for every successful branch push, including branches with an open MR.
|
||||
The workflow runs on branch/tag pushes, PR opening/reopening/synchronization/edits, and manual dispatch. Manual runs validate without publishing images. Required jobs cover policy regression tests, GitFlow and Conventional Commits, ESLint, Prettier, application/test type checks, Vitest coverage, the production build, Semgrep SAST, and Trivy dependency/secret scans. Coverage, build output, and SARIF reports are retained for seven days.
|
||||
|
||||
Pipelines switch to MR pipelines when a branch has an open MR, avoiding duplicate builds. Docker publication from MR pipelines is limited to detached pipelines within this project. Tag pipelines promote the already-built commit image instead of rebuilding it.
|
||||
Functional tests use one worker and a 2 GiB Node heap. The separate benchmark job runs without coverage and keeps its five-minute protocol timeout. At the user's request, it has `continue-on-error: true` while [#78](https://gitea.nessar.fr/nessar/vac-optimizer/issues/78) is open. #78 must restore the benchmark gate after the protocol passes. Passing functional checks during this exception does not establish benchmark acceptance.
|
||||
|
||||
The required `test` job runs application tests with coverage, one worker and a 2 GiB Node heap limit. A separate automatic `benchmark` job uses the same environment outside coverage instrumentation, with the unchanged five-minute timeout and protocol assertions.
|
||||
The `required` job aggregates successful validation and expected image publication, accepting the image job's intentional skip for fork PRs and manual runs. Image jobs require successful validation and are limited to pushes and same-repository PRs. Registry credentials are confined to the publishing step.
|
||||
|
||||
At the user's request, the benchmark job has `allow_failure: true` while #78 is open, so benchmark failure does not block functional delivery. #78 must restore the required benchmark gate after the protocol passes. A green pipeline during this exception does not establish benchmark acceptance.
|
||||
Both protected branches still require `gitlab-to-gitea/ci-conversion-required` until the owner approves the first merge message and a successful Gitea PR run, then replaces the migration gate with the actual pull-request context for `required`. Local validation does not satisfy this status. Historical CI reports and artifacts remain in administrator rollback backups; the original configuration is retained in Git history.
|
||||
|
||||
### GitFlow and releases
|
||||
|
||||
`develop` is the default integration branch. Create `feature/*` branches from it, rebase them onto the current `origin/develop` before opening or merging their MR, and merge into `develop` with a merge commit (`--no-ff`). Existing working-branch names such as `feat/*` follow the same rules. Working branches cannot merge `develop` into themselves instead of rebasing.
|
||||
`develop` is the default integration branch. Create `feature/*` branches from it, rebase them onto the current `origin/develop` before opening or merging their PR, and merge into `develop` with a merge commit (`--no-ff`). Existing working-branch names such as `feat/*` follow the same rules. Working branches cannot merge `develop` into themselves instead of rebasing.
|
||||
|
||||
Create `release/X.Y.Z` from `develop` when the release scope is ready. Set `package.json` to `X.Y.Z` on this branch, stabilize it, then merge it into `main` and back into `develop`. Keep the release branch until both MRs have merged. A `hotfix/X.Y.Z` starts from `main`, changes the package version, and follows the same two-target merge process. Do not rebase `main`, `develop`, or a frozen release onto ongoing development.
|
||||
Create `release/X.Y.Z` from `develop` when the release scope is ready. Set `package.json` to `X.Y.Z` on this branch, stabilize it, then merge it into `main` and back into `develop`. Keep the release branch until both PRs have merged. A `hotfix/X.Y.Z` starts from `main`, changes the package version, and follows the same two-target merge process. Do not rebase `main`, `develop`, or a frozen release onto ongoing development.
|
||||
|
||||
Use Conventional Commits for new commits and MR titles, for example `feat(calendar): add planning periods`, `fix(planner): handle expired balances`, or `chore(release): prepare 1.6.0`. Scopes are optional; `!` marks a breaking change. GitLab generates merge messages from the validated MR title. Legacy history is not rewritten.
|
||||
Use Conventional Commits for new commits and PR titles, for example `feat(calendar): add planning periods`, `fix(planner): handle expired balances`, or `chore(release): prepare 1.6.0`. Scopes are optional; `!` marks a breaking change. The merge commit subject must preserve the validated PR title. Gitea supports a [Git-based merge-message template](https://docs.gitea.com/usage/issues-prs/merge-message-templates/) at `.gitea/default_merge_message/MERGE_TEMPLATE.md`, using `${PullRequestTitle}` and `${PullRequestReference}`. The committed template and a checked example expansion are documented in the [activation guide](docs/gitea-actions.md); legacy history is not rewritten.
|
||||
|
||||
The image publisher implements this tag contract in `gitea.nessar.fr/nessar/vac-optimizer`:
|
||||
|
||||
| Trigger | Published image tags |
|
||||
| --- | --- |
|
||||
| Successful push to any branch | `branch-<GitLab ref slug>` and `sha-<full commit SHA>` |
|
||||
| Successful push to any branch | `branch-<normalized branch slug>` and `sha-<full commit SHA>` |
|
||||
| Explicit release tag `vX.Y.Z` | `vX.Y.Z` and `latest`, using the existing SHA image |
|
||||
|
||||
For example, `feature/calendar` publishes `branch-feature-calendar`; integration publishes `branch-develop`. GitLab normalizes ref slugs to lowercase and truncates them to 63 bytes, so choose branch names with distinct slugs. Only valid release tags can update `latest`.
|
||||
For example, `feature/calendar` uses `branch-feature-calendar`; integration uses `branch-develop`. Preserve the previous slug normalization: lowercase the branch name, replace characters other than `a-z` and `0-9` with `-`, truncate to 63 bytes, and remove leading/trailing `-`. Choose branch names with distinct slugs. Only valid release tags can update `latest`. Automatic image publication and tag promotion begin after the owner provisions publisher credentials and validates publication permissions.
|
||||
|
||||
After the MR into `main` and its full pipeline have succeeded, tag the published main commit:
|
||||
After owner activation, the PR into `main` has merged, and its full checks and SHA image publication have succeeded, tag the published main commit:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
@@ -274,11 +273,13 @@ git tag -a v1.6.0 origin/main -m "Release 1.6.0"
|
||||
git push origin v1.6.0
|
||||
```
|
||||
|
||||
The tag must match the checked-out `package.json` version, use stable `vMAJOR.MINOR.PATCH` syntax, and point to a commit reachable from `main`. Wait for the main pipeline before tagging: the release job requires its `sha-<commit>` image. Tags do not increment the version automatically.
|
||||
The tag must match the tagged commit's `package.json` version, use stable `vMAJOR.MINOR.PATCH` syntax, and point to a commit reachable from `main`. Wait for the main checks and image publication before tagging: promotion requires its `sha-<commit>` image. Tags do not increment the version automatically.
|
||||
|
||||
GitLab project settings must use `develop` as the default branch, the **Merge commit** method, squashing disabled, and **Pipelines must succeed** with skipped pipelines disallowed. Protect `main` and `develop` with no direct or forced pushes and Maintainer-only merges. Set the merge commit template to `%{title}` followed by the MR reference, preserving Conventional Commit subjects. The CI permits only `release/*` and `hotfix/*` into `main`; working MRs target `develop`. Rebase checks validate the target head at pipeline time: if `develop` advances afterward, rebase and rerun the MR pipeline before merging.
|
||||
The Gitea repository policy requires `develop` as the default branch and **Merge commit** as the merge method, with squash and rebase merge methods disabled. Configure [branch protection](https://docs.gitea.com/usage/access-control/protected-branches/) for `main` and `develop` to disable direct and forced pushes, restrict the merge allowlist to designated maintainers, require successful status checks, and block outdated PRs. Administrators must follow the protection rules. After merge-message approval and a successful reviewed PR run, replace the migration gate with the reported pull-request context for the `required` job, as described in the activation guide. These are required settings; the migration does not establish that every setting has already been applied.
|
||||
|
||||
Run the policy integration check locally with `node scripts/ci-policy.test.mjs`.
|
||||
Only `release/*` and `hotfix/*` PRs may target `main`; working PRs target `develop`. CI validates these rules and checks working-branch rebases against the fetched integration head. If `develop` advances after validation, rebase and rerun the PR checks before merging.
|
||||
|
||||
Run `node scripts/ci-policy.test.mjs` and `node scripts/ci-image.test.mjs` locally to verify GitFlow event handling and image publication without writing to a registry. The adapter reads Gitea webhook data through the runner's `GITHUB_*` compatibility variables. Local checks do not satisfy the migration gate.
|
||||
|
||||
## Domain and design references
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"jest-preset-angular": "^17.0.0",
|
||||
"jsdom": "^30.0.0",
|
||||
"prettier": "^3.8.3",
|
||||
"typescript": "~7.0.0",
|
||||
"typescript": "~6.0.3",
|
||||
"typescript-eslint": "^8.60.0",
|
||||
"vite": "^8.0.0",
|
||||
"vite-tsconfig-paths": "^6.0.0",
|
||||
@@ -65,11 +65,11 @@
|
||||
|
||||
"@analogjs/vitest-angular": ["@analogjs/vitest-angular@2.8.0", "", { "peerDependencies": { "@analogjs/vite-plugin-angular": "*", "@angular-devkit/architect": ">=0.1700.0 < 0.2300.0 || >=0.2200.0 < 0.2300.0", "@angular-devkit/schematics": ">=17.0.0", "vitest": "^1.3.1 || ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "zone.js": ">=0.14.0" }, "optionalPeers": ["zone.js"] }, "sha512-ZNwjOf8ACEOW8yTC9iAu3uXt8q0GABgNXoo9lBUN0RuV7SOubpcabZewirETP5NqgyhJ5S96Ql1WFeW/jAznqA=="],
|
||||
|
||||
"@angular-devkit/architect": ["@angular-devkit/architect@0.2202.2", "", { "dependencies": { "@angular-devkit/core": "22.2.2", "rxjs": "7.8.2" }, "bin": { "architect": "bin/cli.js" } }, "sha512-7ScBo47haHyRGG5M5ZEpbhwHgO2LEDbCLwNCjpjwhtRFDIEmAFbULM1OQjIybN3x+wOyY1kWRK7KvihCbs371g=="],
|
||||
"@angular-devkit/architect": ["@angular-devkit/architect@0.2202.1", "", { "dependencies": { "@angular-devkit/core": "22.2.1", "rxjs": "7.8.2" }, "bin": { "architect": "bin/cli.js" } }, "sha512-WGRAxFj+R9LC8HuK3DnDgxHo/XrgfklNQeNgjq/phTEVq5ZdWxc8kDoPA4uQz4vF+HY2eycAAtOPfMvj2TBEiQ=="],
|
||||
|
||||
"@angular-devkit/core": ["@angular-devkit/core@22.2.2", "", { "dependencies": { "ajv": "8.20.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.7", "rxjs": "7.8.2", "source-map": "0.8.0" }, "peerDependencies": { "chokidar": "^5.0.0" }, "optionalPeers": ["chokidar"] }, "sha512-iQexb5xUON9AkTzvBiMftrHWLguIlYv6M/BOH/sj7U5L8eOPMu8N269WF74xPo4n/X5tusFLqniGI0zolT2Sjg=="],
|
||||
|
||||
"@angular-devkit/schematics": ["@angular-devkit/schematics@22.2.2", "", { "dependencies": { "@angular-devkit/core": "22.2.2", "jsonc-parser": "3.3.1", "magic-string": "1.4.1", "ora": "9.4.1", "rxjs": "7.8.2" } }, "sha512-uCpFZbQgFUrOjKHLeYSCaYCJdufauYom8yFGggm67yetMAGKPbmRvUEko8i8auNFoBQ+1w1CivjSTwtmXQzZkA=="],
|
||||
"@angular-devkit/schematics": ["@angular-devkit/schematics@22.2.1", "", { "dependencies": { "@angular-devkit/core": "22.2.1", "jsonc-parser": "3.3.1", "magic-string": "1.4.1", "ora": "9.4.1", "rxjs": "7.8.2" } }, "sha512-frHhFwu2TEfTZX5jfAYL2bztuG5mGggfJhiM9bo5/QtLAi49LtRbJxIktsu6vjJdujxeB2TKD7KqnXwNYgd41Q=="],
|
||||
|
||||
"@angular/animations": ["@angular/animations@22.2.1", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/core": "22.2.1" } }, "sha512-vTxZN6uL/m61lv7ZCvfmQ3VuFb4o/tniGLpc3SpkNHE6MLM8WN0IjfNBRLMYMovd1uUBSPwhcG4Llcmucr9dmw=="],
|
||||
|
||||
@@ -941,46 +941,6 @@
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg=="],
|
||||
|
||||
"@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="],
|
||||
|
||||
"@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="],
|
||||
|
||||
"@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="],
|
||||
|
||||
"@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="],
|
||||
|
||||
"@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="],
|
||||
|
||||
"@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="],
|
||||
|
||||
"@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="],
|
||||
|
||||
"@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="],
|
||||
|
||||
"@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="],
|
||||
|
||||
"@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="],
|
||||
|
||||
"@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="],
|
||||
|
||||
"@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="],
|
||||
|
||||
"@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="],
|
||||
|
||||
"@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="],
|
||||
|
||||
"@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="],
|
||||
|
||||
"@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="],
|
||||
|
||||
"@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="],
|
||||
|
||||
"@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="],
|
||||
|
||||
"@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="],
|
||||
|
||||
"@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.1", "", {}, "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ=="],
|
||||
|
||||
"@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.12.2", "", { "os": "android", "cpu": "arm" }, "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w=="],
|
||||
@@ -2277,7 +2237,7 @@
|
||||
|
||||
"type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="],
|
||||
|
||||
"typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="],
|
||||
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.60.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.60.0", "@typescript-eslint/parser": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0", "@typescript-eslint/utils": "8.60.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw=="],
|
||||
|
||||
@@ -2419,16 +2379,26 @@
|
||||
|
||||
"@analogjs/vite-plugin-nitro/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
|
||||
|
||||
"@angular-devkit/architect/@angular-devkit/core": ["@angular-devkit/core@22.2.1", "", { "dependencies": { "ajv": "8.20.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.7", "rxjs": "7.8.2", "source-map": "0.8.0" }, "peerDependencies": { "chokidar": "^5.0.0" }, "optionalPeers": ["chokidar"] }, "sha512-2oiuWVbUg7pE9kh2grp+MF2ynys+tMWZ/mmoYlqWrhMS2n5oV304ir0/IvvCCSGz9qsbL2A1vZhnlI2UbTSNiA=="],
|
||||
|
||||
"@angular-devkit/core/source-map": ["source-map@0.8.0", "", {}, "sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ=="],
|
||||
|
||||
"@angular-devkit/schematics/@angular-devkit/core": ["@angular-devkit/core@22.2.1", "", { "dependencies": { "ajv": "8.20.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.7", "rxjs": "7.8.2", "source-map": "0.8.0" }, "peerDependencies": { "chokidar": "^5.0.0" }, "optionalPeers": ["chokidar"] }, "sha512-2oiuWVbUg7pE9kh2grp+MF2ynys+tMWZ/mmoYlqWrhMS2n5oV304ir0/IvvCCSGz9qsbL2A1vZhnlI2UbTSNiA=="],
|
||||
|
||||
"@angular-devkit/schematics/magic-string": ["magic-string@1.4.1", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.6.0" } }, "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow=="],
|
||||
|
||||
"@angular/build/@angular-devkit/architect": ["@angular-devkit/architect@0.2202.2", "", { "dependencies": { "@angular-devkit/core": "22.2.2", "rxjs": "7.8.2" }, "bin": { "architect": "bin/cli.js" } }, "sha512-7ScBo47haHyRGG5M5ZEpbhwHgO2LEDbCLwNCjpjwhtRFDIEmAFbULM1OQjIybN3x+wOyY1kWRK7KvihCbs371g=="],
|
||||
|
||||
"@angular/build/magic-string": ["magic-string@1.4.1", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.6.0" } }, "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow=="],
|
||||
|
||||
"@angular/build/oxc-parser": ["oxc-parser@0.150.0", "", { "dependencies": { "@oxc-project/types": "^0.150.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.150.0", "@oxc-parser/binding-android-arm64": "0.150.0", "@oxc-parser/binding-darwin-arm64": "0.150.0", "@oxc-parser/binding-darwin-x64": "0.150.0", "@oxc-parser/binding-freebsd-x64": "0.150.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.150.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.150.0", "@oxc-parser/binding-linux-arm64-gnu": "0.150.0", "@oxc-parser/binding-linux-arm64-musl": "0.150.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.150.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.150.0", "@oxc-parser/binding-linux-riscv64-musl": "0.150.0", "@oxc-parser/binding-linux-s390x-gnu": "0.150.0", "@oxc-parser/binding-linux-x64-gnu": "0.150.0", "@oxc-parser/binding-linux-x64-musl": "0.150.0", "@oxc-parser/binding-openharmony-arm64": "0.150.0", "@oxc-parser/binding-win32-arm64-msvc": "0.150.0", "@oxc-parser/binding-win32-ia32-msvc": "0.150.0", "@oxc-parser/binding-win32-x64-msvc": "0.150.0" } }, "sha512-zwajKw1GUa57IOdafIS7/yOObGF2NihwIfZfs5yJTRprMSkiwhWOc/k+n8bwnUPbHr2/XsjfBO8KyTPaW3VpWg=="],
|
||||
|
||||
"@angular/build/vite": ["vite@8.3.0", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.28", "rolldown": "~1.2.6", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ=="],
|
||||
|
||||
"@angular/cli/@angular-devkit/architect": ["@angular-devkit/architect@0.2202.2", "", { "dependencies": { "@angular-devkit/core": "22.2.2", "rxjs": "7.8.2" }, "bin": { "architect": "bin/cli.js" } }, "sha512-7ScBo47haHyRGG5M5ZEpbhwHgO2LEDbCLwNCjpjwhtRFDIEmAFbULM1OQjIybN3x+wOyY1kWRK7KvihCbs371g=="],
|
||||
|
||||
"@angular/cli/@angular-devkit/schematics": ["@angular-devkit/schematics@22.2.2", "", { "dependencies": { "@angular-devkit/core": "22.2.2", "jsonc-parser": "3.3.1", "magic-string": "1.4.1", "ora": "9.4.1", "rxjs": "7.8.2" } }, "sha512-uCpFZbQgFUrOjKHLeYSCaYCJdufauYom8yFGggm67yetMAGKPbmRvUEko8i8auNFoBQ+1w1CivjSTwtmXQzZkA=="],
|
||||
|
||||
"@angular/compiler-cli/@babel/core": ["@babel/core@8.0.1", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/generator": "^8.0.0", "@babel/helper-compilation-targets": "^8.0.0", "@babel/helpers": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/template": "^8.0.0", "@babel/traverse": "^8.0.0", "@babel/types": "^8.0.0", "@types/gensync": "^1.0.5", "convert-source-map": "^2.0.0", "empathic": "^2.0.1", "gensync": "^1.0.0-beta.2", "import-meta-resolve": "^4.2.0", "json5": "^2.2.3", "obug": "^2.1.1", "semver": "^7.7.3" } }, "sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw=="],
|
||||
|
||||
"@angular/compiler-cli/yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="],
|
||||
@@ -2827,9 +2797,9 @@
|
||||
|
||||
"@rollup/pluginutils/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||
|
||||
"@schematics/angular/oxc-parser": ["oxc-parser@0.150.0", "", { "dependencies": { "@oxc-project/types": "^0.150.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.150.0", "@oxc-parser/binding-android-arm64": "0.150.0", "@oxc-parser/binding-darwin-arm64": "0.150.0", "@oxc-parser/binding-darwin-x64": "0.150.0", "@oxc-parser/binding-freebsd-x64": "0.150.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.150.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.150.0", "@oxc-parser/binding-linux-arm64-gnu": "0.150.0", "@oxc-parser/binding-linux-arm64-musl": "0.150.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.150.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.150.0", "@oxc-parser/binding-linux-riscv64-musl": "0.150.0", "@oxc-parser/binding-linux-s390x-gnu": "0.150.0", "@oxc-parser/binding-linux-x64-gnu": "0.150.0", "@oxc-parser/binding-linux-x64-musl": "0.150.0", "@oxc-parser/binding-openharmony-arm64": "0.150.0", "@oxc-parser/binding-win32-arm64-msvc": "0.150.0", "@oxc-parser/binding-win32-ia32-msvc": "0.150.0", "@oxc-parser/binding-win32-x64-msvc": "0.150.0" } }, "sha512-zwajKw1GUa57IOdafIS7/yOObGF2NihwIfZfs5yJTRprMSkiwhWOc/k+n8bwnUPbHr2/XsjfBO8KyTPaW3VpWg=="],
|
||||
"@schematics/angular/@angular-devkit/schematics": ["@angular-devkit/schematics@22.2.2", "", { "dependencies": { "@angular-devkit/core": "22.2.2", "jsonc-parser": "3.3.1", "magic-string": "1.4.1", "ora": "9.4.1", "rxjs": "7.8.2" } }, "sha512-uCpFZbQgFUrOjKHLeYSCaYCJdufauYom8yFGggm67yetMAGKPbmRvUEko8i8auNFoBQ+1w1CivjSTwtmXQzZkA=="],
|
||||
|
||||
"@schematics/angular/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
||||
"@schematics/angular/oxc-parser": ["oxc-parser@0.150.0", "", { "dependencies": { "@oxc-project/types": "^0.150.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.150.0", "@oxc-parser/binding-android-arm64": "0.150.0", "@oxc-parser/binding-darwin-arm64": "0.150.0", "@oxc-parser/binding-darwin-x64": "0.150.0", "@oxc-parser/binding-freebsd-x64": "0.150.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.150.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.150.0", "@oxc-parser/binding-linux-arm64-gnu": "0.150.0", "@oxc-parser/binding-linux-arm64-musl": "0.150.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.150.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.150.0", "@oxc-parser/binding-linux-riscv64-musl": "0.150.0", "@oxc-parser/binding-linux-s390x-gnu": "0.150.0", "@oxc-parser/binding-linux-x64-gnu": "0.150.0", "@oxc-parser/binding-linux-x64-musl": "0.150.0", "@oxc-parser/binding-openharmony-arm64": "0.150.0", "@oxc-parser/binding-win32-arm64-msvc": "0.150.0", "@oxc-parser/binding-win32-ia32-msvc": "0.150.0", "@oxc-parser/binding-win32-x64-msvc": "0.150.0" } }, "sha512-zwajKw1GUa57IOdafIS7/yOObGF2NihwIfZfs5yJTRprMSkiwhWOc/k+n8bwnUPbHr2/XsjfBO8KyTPaW3VpWg=="],
|
||||
|
||||
"@types/babel__core/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
|
||||
|
||||
@@ -3277,6 +3247,10 @@
|
||||
|
||||
"@analogjs/vite-plugin-nitro/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
|
||||
|
||||
"@angular-devkit/architect/@angular-devkit/core/source-map": ["source-map@0.8.0", "", {}, "sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ=="],
|
||||
|
||||
"@angular-devkit/schematics/@angular-devkit/core/source-map": ["source-map@0.8.0", "", {}, "sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ=="],
|
||||
|
||||
"@angular/build/oxc-parser/@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.150.0", "", { "os": "android", "cpu": "arm" }, "sha512-oQef2Zu4Prz1KLKznz3HqZzU9uVoA5PMoDZuuLmqms7hKmKSAPzlaMnLllJq3t+rgKmfJJ2siPrpZfFrW06btw=="],
|
||||
|
||||
"@angular/build/oxc-parser/@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.150.0", "", { "os": "android", "cpu": "arm64" }, "sha512-B6ofpoFiAUwIZ0MJ2IgHPvZK8FAtL4qzSZRwOkAKIfxEobE1mQC8nDdiFZj7pUaJiVUVCsfkMsBJKedzO8rZvA=="],
|
||||
@@ -3319,6 +3293,8 @@
|
||||
|
||||
"@angular/build/vite/rolldown": ["rolldown@1.2.12", "", { "dependencies": { "@oxc-project/types": "=0.152.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.12", "@rolldown/binding-android-arm64": "1.2.12", "@rolldown/binding-darwin-arm64": "1.2.12", "@rolldown/binding-darwin-x64": "1.2.12", "@rolldown/binding-freebsd-x64": "1.2.12", "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", "@rolldown/binding-linux-arm64-gnu": "1.2.12", "@rolldown/binding-linux-arm64-musl": "1.2.12", "@rolldown/binding-linux-ppc64-gnu": "1.2.12", "@rolldown/binding-linux-s390x-gnu": "1.2.12", "@rolldown/binding-linux-x64-gnu": "1.2.12", "@rolldown/binding-linux-x64-musl": "1.2.12", "@rolldown/binding-openharmony-arm64": "1.2.12", "@rolldown/binding-win32-arm64-msvc": "1.2.12", "@rolldown/binding-win32-x64-msvc": "1.2.12" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ=="],
|
||||
|
||||
"@angular/cli/@angular-devkit/schematics/magic-string": ["magic-string@1.4.1", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.6.0" } }, "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow=="],
|
||||
|
||||
"@angular/compiler-cli/@babel/core/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"@angular/compiler-cli/yargs/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
@@ -5443,6 +5419,8 @@
|
||||
|
||||
"@oxc-parser/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
|
||||
|
||||
"@schematics/angular/@angular-devkit/schematics/magic-string": ["magic-string@1.4.1", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.6.0" } }, "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow=="],
|
||||
|
||||
"@schematics/angular/oxc-parser/@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.150.0", "", { "os": "android", "cpu": "arm" }, "sha512-oQef2Zu4Prz1KLKznz3HqZzU9uVoA5PMoDZuuLmqms7hKmKSAPzlaMnLllJq3t+rgKmfJJ2siPrpZfFrW06btw=="],
|
||||
|
||||
"@schematics/angular/oxc-parser/@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.150.0", "", { "os": "android", "cpu": "arm64" }, "sha512-B6ofpoFiAUwIZ0MJ2IgHPvZK8FAtL4qzSZRwOkAKIfxEobE1mQC8nDdiFZj7pUaJiVUVCsfkMsBJKedzO8rZvA=="],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# User-Selected Horizons and Cancellable Calculations
|
||||
|
||||
Accepted for [issue #12](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/12). Fixed, saved planning horizons are implemented by [issue #57](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/57); user cancellation with a retained valid plan and original trace is implemented by [issue #58](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/58); removal of the automatic calculation deadline and heartbeat-based worker liveness are implemented by [issue #59](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/59). The indicative duration estimate is implemented by [issue #60](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/60); its initial model, session-local refinement and calendar-rendering measurement are documented in [planning-duration.md](../planning-duration.md).
|
||||
Accepted for [issue #12](https://gitea.nessar.fr/nessar/vac-optimizer/issues/12). Fixed, saved planning horizons are implemented by [issue #57](https://gitea.nessar.fr/nessar/vac-optimizer/issues/57); user cancellation with a retained valid plan and original trace is implemented by [issue #58](https://gitea.nessar.fr/nessar/vac-optimizer/issues/58); removal of the automatic calculation deadline and heartbeat-based worker liveness are implemented by [issue #59](https://gitea.nessar.fr/nessar/vac-optimizer/issues/59). The indicative duration estimate is implemented by [issue #60](https://gitea.nessar.fr/nessar/vac-optimizer/issues/60); its initial model, session-local refinement and calendar-rendering measurement are documented in [planning-duration.md](../planning-duration.md).
|
||||
|
||||
`deadlineMs` remains an opt-in contract for technical callers: a finite non-negative planning-work budget with no ten-second cap. An omitted value is unlimited. It starts after calendar preparation and excludes trace capture, observer callbacks, and post-outcome artifact work. The calendar and benchmark omit it. A one-second worker heartbeat renews the coordinator's thirty-second inactivity timer during cooperative preparation and search; total elapsed time never triggers that timer while activity continues. Worker errors and unavailable execution retain their explicit outcomes, and monitoring ends when the outcome arrives so delayed trace delivery cannot invalidate it.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ status: accepted
|
||||
|
||||
# Declared Non-Working Days and Reported Leave Balances
|
||||
|
||||
For [issue #13](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/13), users declare fixed non-working dates among normal working days, with a CP, RTT, or Other type; Other covers every other reason for not working. Reported balances already account for booked leave, so these declarations consume no remaining balance and cannot receive another placement; they count towards compatible planning rules, including rules that earn leave.
|
||||
For [issue #13](https://gitea.nessar.fr/nessar/vac-optimizer/issues/13), users declare fixed non-working dates among normal working days, with a CP, RTT, or Other type; Other covers every other reason for not working. Reported balances already account for booked leave, so these declarations consume no remaining balance and cannot receive another placement; they count towards compatible planning rules, including rules that earn leave.
|
||||
|
||||
Declared dates can form time-off periods without new leave placements, interrupt work sequences, and guide further optimization. Each calculation uses today's Europe/Paris calendar date, with reported balances assumed current as of that date: the planner adds only entitlements available strictly afterward, while earlier entitlements are already represented in the reported balances. This keeps existing bookings useful without debiting them again or asking users to manually add prospective gains; it relies on the user keeping reported balances current as the employer's system credits earned leave.
|
||||
|
||||
|
||||
@@ -1,46 +1,57 @@
|
||||
# Issue tracker: GitLab
|
||||
# Issue tracker: Gitea
|
||||
|
||||
Issues and specs for this repo live as GitLab issues. Use the [`glab`](https://gitlab.com/gitlab-org/cli) CLI for all operations.
|
||||
Issues and specs live in [nessar/vac-optimizer on Gitea](https://gitea.nessar.fr/nessar/vac-optimizer). Use the configured [`tea-cli`](https://gitea.com/gitea/tea) login for supported operations.
|
||||
|
||||
## Access and command version
|
||||
|
||||
The installed binary is `tea-cli` 0.9.2. Its commands below use the pre-1.0 interface; other installations may call the binary `tea`. Check `tea-cli --help` and subcommand help before using commands from another version. This version supports YAML, TSV, CSV, simple, and table output; it has no JSON output, issue-edit command, or `api` command.
|
||||
|
||||
Run inside the clone so the CLI discovers the repository and login from `origin`. Use `--repo nessar/vac-optimizer` and `--login <login-name>` when an explicit selection is needed. Options go before positional issue or PR numbers. Existing authentication can be checked with `tea-cli whoami`; for a new login, use interactive `tea-cli login add` and select `https://gitea.nessar.fr`.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Create an issue**: `glab issue create --title "..." --description "..."`. Use a heredoc for multi-line descriptions. Pass `--description -` to open an editor.
|
||||
- **Read an issue**: `glab issue view <number> --comments`. Use `-F json` for machine-readable output.
|
||||
- **List issues**: `glab issue list -F json` with appropriate `--label` filters.
|
||||
- **Comment on an issue**: `glab issue note <number> --message "..."`. GitLab calls comments "notes".
|
||||
- **Apply / remove labels**: `glab issue update <number> --label "..."` / `--unlabel "..."`. Multiple labels can be comma-separated or by repeating the flag.
|
||||
- **Close**: `glab issue close <number>`. `glab issue close` does not accept a closing comment, so post the explanation first with `glab issue note <number> --message "..."`, then close.
|
||||
- **Merge requests**: GitLab calls PRs "merge requests". Use `glab mr create`, `glab mr view`, `glab mr note`, etc. — the same shape as `gh pr ...` with `mr` in place of `pr` and `note`/`--message` in place of `comment`/`--body`.
|
||||
- **Create an issue**: `tea-cli issues create --title "..." --description "..." --labels "needs-triage"`. Multiple labels or assignees are comma-separated. For multiline text, prepare a draft and pass its contents as one quoted description argument.
|
||||
- **Read an issue**: `tea-cli issues --comments <number>`. Detail views render for display even with `--output yaml`; use list output or the REST operations below for structured data.
|
||||
- **List issues**: `tea-cli issues --state open --labels "needs-triage" --fields index,title,body,state,labels,assignees,url --output yaml`. Use `--page` and `--limit` to read all pages; the server may cap the requested page size.
|
||||
- **Comment**: `tea-cli comment <number> "..."`; this works for issues and PRs.
|
||||
- **Apply / remove labels, edit a body, or assign**: use the Gitea UI or the REST operations below; this CLI version does not support these edits. Preserve unrelated labels and assignees.
|
||||
- **Close**: post the explanation with `tea-cli comment <number> "..."`, then run `tea-cli issues close <number>`.
|
||||
- **Create a pull request**: `tea-cli pulls create --base develop --title "docs: update Gitea documentation" --description "..."`. Read with `tea-cli pulls --comments <number>`; list with `tea-cli pulls --state open --output yaml`.
|
||||
- **List milestones**: `tea-cli milestones --state all --fields id,title --output tsv`. Issue numbers were preserved during migration, but milestone IDs changed; V1 stats is [milestone 13](https://gitea.nessar.fr/nessar/vac-optimizer/milestone/13).
|
||||
|
||||
Infer the repo from `git remote -v` — `glab` does this automatically when run inside a clone.
|
||||
## REST operations for CLI gaps
|
||||
|
||||
## Merge requests as a triage surface
|
||||
The instance's [API explorer](https://gitea.nessar.fr/api/swagger) and [schema](https://gitea.nessar.fr/swagger.v1.json) describe the supported endpoints. Paths below are relative to `https://gitea.nessar.fr/api/v1`; authenticate with an existing authorized Gitea API token without displaying or committing it. Check the installed CLI help first when it is upgraded.
|
||||
|
||||
**MRs as a request surface: no.** _(Set to `yes` if this repo treats external merge requests as feature requests; `/triage` reads this flag.)_
|
||||
- **Read full issue data or comments as JSON**: `GET /repos/nessar/vac-optimizer/issues/<number>` and `GET /repos/nessar/vac-optimizer/issues/<number>/comments`. Paginate comment lists.
|
||||
- **Find label IDs**: `GET /repos/nessar/vac-optimizer/labels`; paginate and match exact names from `triage-labels.md`.
|
||||
- **Add labels**: `POST /repos/nessar/vac-optimizer/issues/<number>/labels` with `{"labels":[<label-id>]}`. **Remove one label**: `DELETE /repos/nessar/vac-optimizer/issues/<number>/labels/<label-id>`. These operations preserve other labels.
|
||||
- **Edit a body or assign**: `PATCH /repos/nessar/vac-optimizer/issues/<number>` with `{"body":"..."}` or `{"assignees":["<username>"]}`. Read current assignees before updating the list.
|
||||
- **Read a PR diff**: `GET /repos/nessar/vac-optimizer/pulls/<number>.diff`.
|
||||
|
||||
When set to `yes`, MRs run through the same labels and states as issues, using the `glab mr` equivalents:
|
||||
## Pull requests as a triage surface
|
||||
|
||||
- **Read an MR**: `glab mr view <number> --comments` and `glab mr diff <number>` for the diff.
|
||||
- **List external MRs for triage**: `glab mr list -F json`, then keep only MRs whose author is not a project member/owner (a contributor's MR, not a maintainer's in-flight work).
|
||||
- **Comment / label / close**: `glab mr note`, `glab mr update --label`/`--unlabel`, `glab mr close`.
|
||||
**PRs as a request surface: no.** _(Set to `yes` if this repo treats external pull requests as feature requests; `/triage` reads this flag.)_
|
||||
|
||||
Unlike GitHub, GitLab numbers issues and MRs separately, so `#42` is unambiguous once you know which surface the maintainer means.
|
||||
When enabled, use `tea-cli pulls --comments <number>` to read and `tea-cli pulls --state open --fields index,title,body,author,labels,url --output yaml` to list. Keep only external contributors' requests, checking repository collaborators or organization membership rather than guessing from the author's name. Comment with `tea-cli comment`, edit labels through the issue-label API, and close with `tea-cli pulls close <number>`.
|
||||
|
||||
For a bare `#<number>`, check the resource kind: `tea-cli issues --kind all --fields index,kind,title,url --output yaml`, or inspect the issue API's `pull_request` field. PR URLs use `/pulls/<number>`; issue URLs use `/issues/<number>`.
|
||||
|
||||
## When a skill says "publish to the issue tracker"
|
||||
|
||||
Create a GitLab issue.
|
||||
Create a Gitea issue with `tea-cli issues create`.
|
||||
|
||||
## When a skill says "fetch the relevant ticket"
|
||||
|
||||
Run `glab issue view <number> --comments`.
|
||||
Run `tea-cli issues --comments <number>` and read its labels and assignees.
|
||||
|
||||
## Wayfinding operations
|
||||
|
||||
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.
|
||||
|
||||
- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `glab issue create --label wayfinder:map`. (On GitLab tiers with native epics, an epic may hold the map instead; a labelled issue works everywhere.)
|
||||
- **Child ticket**: an issue carrying `Part of #<map>` at the top of its description and labels `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
|
||||
- **Blocking**: GitLab's **native blocking link** — the canonical, UI-visible representation. Add it with the `/blocked_by #<n>` quick action, posted as a note (`glab issue note <child> --message "/blocked_by #<blocker>"`). Native blocking links are a Premium/Ultimate feature; on the free tier (or where unavailable) fall back to a `Blocked by: #<n>, #<n>` line at the top of the description. A ticket is unblocked when every blocker is closed.
|
||||
- **Frontier query**: `glab issue list -F json` scoped to the map's children, drop any with an open blocker — a native `blocked_by` link to an open issue (`glab api projects/:id/issues/:iid/links`), or an open issue in the `Blocked by` line — or an assignee; first in map order wins.
|
||||
- **Claim**: `glab issue update <n> --assignee @me` — the session's first write.
|
||||
- **Resolve**: `glab issue note <n> --message "<answer>"`, then `glab issue close <n>`, then append a context pointer (gist + link) to the map's Decisions-so-far.
|
||||
- **Map**: create an issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body, with `tea-cli issues create --title "..." --description "..." --labels "wayfinder:map"`.
|
||||
- **Child ticket**: an issue carrying `Part of #<map>` at the top of its description and labels `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Maintain the ordered child links in the map's body. Once claimed, the ticket is assigned to the driving dev.
|
||||
- **Blocking**: use Gitea's native dependencies. `POST /repos/nessar/vac-optimizer/issues/<child>/dependencies` with `{"owner":"nessar","repo":"vac-optimizer","index":<blocker-number>}` makes the child depend on that blocker. Read blockers with `GET` on the same path; use `DELETE` with the same body to remove a dependency. Where dependencies are unavailable, retain a `Blocked by: #<n>, #<n>` line at the top of the description. A ticket is unblocked when every blocker is closed.
|
||||
- **Frontier query**: list the map's open children with `tea-cli issues --state open --fields index,title,body,assignees,labels --output yaml`, reading all pages. Drop tickets with an assignee or an open native dependency or description-line blocker; first in map order wins.
|
||||
- **Claim**: assign the authenticated username through the issue `PATCH` endpoint or Gitea UI — the session's first write.
|
||||
- **Resolve**: `tea-cli comment <n> "<answer>"`, then `tea-cli issues close <n>`, then append a context pointer (gist + link) to the map's Decisions-so-far through the issue `PATCH` endpoint or Gitea UI.
|
||||
@@ -7,9 +7,9 @@ the benchmark fixtures. The latest full functional coverage run passes 652 tests
|
||||
across 34 files under the 2 GB Node heap limit, excluding the separate benchmark.
|
||||
This does not establish a passing performance protocol.
|
||||
|
||||
MR pipeline 320 passed all 652 functional tests in 109.43 seconds, then the
|
||||
seven-scenario protocol timed out at its unchanged 300-second limit without an
|
||||
out-of-memory failure. In an earlier diagnostic run, the loaded-year warm-up took
|
||||
Before the Gitea migration, GitLab pipeline 320 passed all 652 functional tests
|
||||
in 109.43 seconds, then the seven-scenario protocol timed out at its unchanged
|
||||
300-second limit without an out-of-memory failure. In an earlier diagnostic run, the loaded-year warm-up took
|
||||
184.36 seconds and its first measured repetition began about 251.78 seconds into
|
||||
the protocol. Those diagnostic timings precede the final period-projection cache
|
||||
and the planned original-decision-trace removal, and are not a result for either.
|
||||
@@ -19,12 +19,15 @@ verification. #78 owns benchmark verification and implementation of further
|
||||
optimizations when needed, after #76 removes tracing and #77 verifies the retained
|
||||
behavior. All seven scenarios, one warm-up and two measured repetitions per
|
||||
scenario, the 2 GB heap limit and the 300-second timeout remain required for #78.
|
||||
At the user's request, CI runs the benchmark in a separate job with
|
||||
`allow_failure: true` while #78 is open. Functional coverage tests and all other
|
||||
required checks remain blocking. #78 must restore the required benchmark gate
|
||||
At the user's request, the Gitea CI configuration defines the benchmark in a
|
||||
separate job with `continue-on-error: true` while #78 is open. Functional coverage
|
||||
tests and all other required checks remain blocking. #78 must restore the required benchmark gate
|
||||
after the unchanged protocol passes. A green pipeline during this exception
|
||||
does not establish a passing benchmark. No automatic elapsed-time planning
|
||||
cutoff is introduced.
|
||||
cutoff is introduced. Gitea Actions is enabled; live workflow validation is tracked in
|
||||
[issue #268](https://gitea.nessar.fr/nessar/vac-optimizer/issues/268). The converted
|
||||
workflow preserves the separate advisory benchmark job. See the
|
||||
[CI migration status](../../README.md#continuous-integration).
|
||||
|
||||
The results below document earlier implementations and protocols. They are
|
||||
historical measurements rather than the performance result for composable rules.
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
# Gitea Actions conversion
|
||||
|
||||
[Issue #268](https://gitea.nessar.fr/nessar/vac-optimizer/issues/268) converts the
|
||||
repository's CI to [`.gitea/workflows/ci.yml`](../.gitea/workflows/ci.yml).
|
||||
The workflow and merge-message template have passed implementation review.
|
||||
Repository Actions is enabled with owner approval. Publisher credentials and
|
||||
live workflow validation are still pending.
|
||||
The existing `gitlab-to-gitea/ci-conversion-required` status stays in place until
|
||||
that review, a successful Gitea PR run, and approval of the prepared first merge
|
||||
message.
|
||||
|
||||
## Jobs and triggers
|
||||
|
||||
Every branch and tag push runs CI. Pull requests run on opening, reopening,
|
||||
synchronization, and title/body edits. Manual dispatch runs validation without
|
||||
publishing images. There is no schedule or deployment job in the preserved
|
||||
configuration; the Compose files remain manual deployment options.
|
||||
|
||||
| Job | Behavior |
|
||||
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `policy` | Tests the policy and publisher, then validates the checked-out commit, GitFlow, PR title, commits, and stable release version. |
|
||||
| `validate` | Runs lint, formatting, and application/test type checks. |
|
||||
| `test` | Runs functional coverage with one worker and a 2 GiB Node heap; uploads coverage for seven days even on failure. |
|
||||
| `benchmark` | Runs outside coverage with the same worker/heap limits and 300-second protocol timeout. Advisory while #78 is open. |
|
||||
| `build` | Builds browser/server production bundles and retains `dist` for seven days. |
|
||||
| `security` | Runs Semgrep JavaScript/TypeScript SAST and Trivy dependency/secret scans; retains SARIF reports for seven days. Scanner execution must succeed; findings remain reports rather than a new severity gate. |
|
||||
| `images` | Publishes branch/SHA images after required validation, or promotes an existing SHA image for a stable tag. Runs only for pushes and same-repository PRs. |
|
||||
| `required` | Fails on failed, cancelled, or skipped required validation. Requires image publication when expected; accepts its intentional skip for fork PRs and manual dispatch. Excludes the advisory benchmark. |
|
||||
|
||||
Push and PR runs are separate so PR title and rebase checks run on the PR event.
|
||||
New runs cancel older runs for the same event/ref. Image jobs serialize per
|
||||
branch slug; stable promotions share one group and are not cancelled in progress.
|
||||
|
||||
## Runners and permissions
|
||||
|
||||
Validation uses Node 24.21.0 and Bun 1.4.2 with the frozen lockfile. The image
|
||||
job uses Docker 29.8.2 and its matching Docker-in-Docker service. Scanner
|
||||
versions are Semgrep 1.180.0 and Trivy 0.75.0; Trivy downloads are checksum-checked.
|
||||
Jobs select the `ubuntu-latest` label and require Linux container-job support.
|
||||
Allow at least the preserved 2 GiB Node heap plus runner and container overhead.
|
||||
|
||||
The instance currently has an online inherited runner named `srvhome`, labelled
|
||||
`ubuntu-latest` and `ubuntu-24.04`. Repository runners, secrets, and variables
|
||||
were empty at implementation time. The owner must verify that this runner can
|
||||
run the job containers, JavaScript actions, artifact uploads, and the privileged
|
||||
Docker-in-Docker service before replacing the migration gate. Use isolated runners for untrusted
|
||||
contributor code; the privileged publishing service belongs to trusted runs.
|
||||
|
||||
The workflow requests a read-only repository token. Checkout removes persisted
|
||||
credentials; publication credentials are passed only to the image step. Fork PRs
|
||||
and manual dispatches cannot run that step. Gitea's job token cannot publish
|
||||
container packages, so provision the two repository Actions secrets below.
|
||||
See [job-token permissions](https://docs.gitea.com/usage/actions/token-permissions/)
|
||||
and [package authorization](https://docs.gitea.com/usage/actions/comparison/).
|
||||
|
||||
| Secret | Purpose |
|
||||
| ------------------- | --------------------------------------------------------------------------- |
|
||||
| `REGISTRY_USERNAME` | The package publisher's Gitea username, authorized to publish for `nessar`. |
|
||||
| `REGISTRY_PASSWORD` | A dedicated personal access token with `write:package` permission. |
|
||||
|
||||
Keep secret values in Gitea's Actions secret store. The configured tea account's
|
||||
administrator token is not a CI publishing credential. No SSH or deployment
|
||||
secret is required by this workflow.
|
||||
|
||||
## Images and releases
|
||||
|
||||
Images use the [Gitea container registry](https://docs.gitea.com/usage/packages/container/)
|
||||
at `gitea.nessar.fr/nessar/vac-optimizer`. Branch pushes and same-repository PRs
|
||||
publish `branch-<slug>` and `sha-<full commit SHA>`. Slugs retain the previous
|
||||
lowercase, non-alphanumeric-to-hyphen, 63-byte normalization; choose distinct
|
||||
branch slugs.
|
||||
|
||||
Only stable `vX.Y.Z` tags matching the tagged commit's `package.json` version
|
||||
and reachable from `main` may promote images. Promotion first pulls the existing
|
||||
SHA image, then publishes the version tag and `latest`; it never rebuilds.
|
||||
An absent SHA image fails before either stable tag changes. Wait for the main
|
||||
image job before tagging. No version increment or deployment is automatic.
|
||||
|
||||
## Merge messages and activation
|
||||
|
||||
The [merge template](../.gitea/default_merge_message/MERGE_TEMPLATE.md) translates
|
||||
the previous title, closing-issue references, and review reference using
|
||||
[Gitea's supported variables](https://docs.gitea.com/usage/issues-prs/merge-message-templates/).
|
||||
For a PR titled `ci: migrate to Gitea Actions`, closing #268, with reference #271,
|
||||
the checked expansion is:
|
||||
|
||||
```text
|
||||
ci: migrate to Gitea Actions
|
||||
|
||||
close #268
|
||||
|
||||
See pull request #271
|
||||
```
|
||||
|
||||
The owner has enabled Actions and approved validation before adding publisher
|
||||
secrets. Complete the remaining activation checks in this order:
|
||||
|
||||
1. Review this workflow against the protected administrator exports for runner
|
||||
requirements, triggers/schedules, permissions, secrets, publishing, and
|
||||
deployment dependencies. The original CI definition is retained in Git:
|
||||
`git show 0d900fd72d1a405d257f5a566c55cc0cb3d2bc61:.gitlab-ci.yml`.
|
||||
2. Publish the reviewed feature branch and open its PR to register the workflow.
|
||||
Once registered, manual dispatch verifies validation and artifacts without
|
||||
publishing images. Push and PR validation can also run now; their image and
|
||||
aggregate checks remain blocked until publisher secrets are provisioned.
|
||||
3. Verify runner isolation and Docker service support, then provision the two
|
||||
publisher secrets. Review the package publisher's permissions and registry
|
||||
namespace before permitting publication. Rerun the PR workflow and require a
|
||||
successful **pull-request run** on the final commit, including PR title and
|
||||
rebase checks, artifact handling, and actual image publication. A push run
|
||||
or manual run alone does not validate the PR policy and publication.
|
||||
4. Prepare and review the first PR's merge message before replacing the migration
|
||||
gate. The target branch will not contain the new template until that first
|
||||
merge, so explicitly use the validated PR title, closing references, and PR
|
||||
reference for its merge message. Keep the migration gate throughout this
|
||||
review.
|
||||
5. After the successful PR run and merge-message approval, replace the migration
|
||||
status requirement on both protected branches with the **actual reported
|
||||
pull-request context** for the `required` job. Select it from the PR's reported
|
||||
checks rather than a push run or a guessed context spelling. Fork PRs must
|
||||
also be able to satisfy this check without base-repository push permissions.
|
||||
Keep direct/force pushes disabled, merges restricted to maintainers, outdated
|
||||
PRs blocked, and administrator protection bypass disabled. Select merge
|
||||
commits and disable squash/rebase merge methods.
|
||||
6. Merge the first PR using the approved message, then verify the resulting merge
|
||||
commit. Subsequent merges read the committed template. No direct
|
||||
integration-branch push is needed.
|
||||
|
||||
Historical job execution, reports, and downloadable artifacts remain in the
|
||||
administrator rollback backups. They are not recreated or uploaded by this
|
||||
conversion.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Cancelling a planning calculation
|
||||
|
||||
Issue [#58](https://gitlab.nessar.fr/nessar/vac-optimizer/-/work_items/58) implements the cancellation portion of ADR 0006. Issue #59 removes the automatic deadline while keeping the same cancellation and original-trace protocol. The duration estimate remains in issue #60.
|
||||
Issue [#58](https://gitea.nessar.fr/nessar/vac-optimizer/issues/58) implements the cancellation portion of ADR 0006. Issue #59 removes the automatic deadline while keeping the same cancellation and original-trace protocol. The duration estimate remains in issue #60.
|
||||
|
||||
`PlanningCalculationContext.cancel()` requests user cancellation once while its outcome is pending. `CalendarYear` keeps an accessible Cancel button visible during preparation, obligation search, and optimization. It announces the request, disables duplicate cancellation, and moves keyboard focus to the result heading when the outcome arrives. Cancellation never triggers Calculate. A subsequent explicit Calculate creates a fresh immutable calculation snapshot.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Plan Statistics Popup
|
||||
|
||||
Local development draft for [issue #3](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/3) and [issue #29](https://gitlab.nessar.fr/nessar/vac-optimizer/-/issues/29), within [milestone %3: V1 stats](https://gitlab.nessar.fr/nessar/vac-optimizer/-/milestones/3). The behavior below consolidates the decisions confirmed during grill-with-docs; proposals and remaining presentation choices are identified separately.
|
||||
Local development draft for [issue #3](https://gitea.nessar.fr/nessar/vac-optimizer/issues/3) and [issue #29](https://gitea.nessar.fr/nessar/vac-optimizer/issues/29), within [milestone V1 stats](https://gitea.nessar.fr/nessar/vac-optimizer/milestone/13). The behavior below consolidates the decisions confirmed during grill-with-docs; proposals and remaining presentation choices are identified separately.
|
||||
|
||||
## Problem Statement
|
||||
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@
|
||||
"jest-preset-angular": "^17.0.0",
|
||||
"jsdom": "^30.0.0",
|
||||
"prettier": "^3.8.3",
|
||||
"typescript": "~7.0.0",
|
||||
"typescript": "~6.0.3",
|
||||
"typescript-eslint": "^8.60.0",
|
||||
"vite": "^8.0.0",
|
||||
"vite-tsconfig-paths": "^6.0.0",
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
// Gitea exposes its event payload through the GitHub-compatible runner variables.
|
||||
export function readCiContext() {
|
||||
const env = process.env;
|
||||
const eventName = env.GITHUB_EVENT_NAME;
|
||||
if (!['push', 'pull_request', 'workflow_dispatch'].includes(eventName)) {
|
||||
throw new Error('Only push, pull_request and workflow_dispatch events are supported.');
|
||||
}
|
||||
const event = JSON.parse(readFileSync(env.GITHUB_EVENT_PATH, 'utf8'));
|
||||
const commit = env.GITHUB_SHA;
|
||||
if (!/^[a-f0-9]{40}$/.test(commit ?? '')) throw new Error('A commit SHA is required.');
|
||||
const pullRequest = eventName === 'pull_request' ? event.pull_request : undefined;
|
||||
if (eventName === 'pull_request' && pullRequest?.head?.sha !== commit) {
|
||||
throw new Error('CI must validate the pull request head commit.');
|
||||
}
|
||||
const tag = env.GITHUB_REF?.startsWith('refs/tags/') ? env.GITHUB_REF.slice(10) : '';
|
||||
const branch = pullRequest
|
||||
? pullRequest.head.ref
|
||||
: env.GITHUB_REF?.startsWith('refs/heads/')
|
||||
? env.GITHUB_REF.slice(11)
|
||||
: '';
|
||||
if (!tag && !branch) throw new Error('A branch or tag ref is required.');
|
||||
const slug = branch
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]/g, '-')
|
||||
.slice(0, 63)
|
||||
.replace(/^-+|-+$/g, '');
|
||||
const publish =
|
||||
eventName === 'push' ||
|
||||
(eventName === 'pull_request' &&
|
||||
Boolean(env.GITHUB_REPOSITORY) &&
|
||||
pullRequest.head.repo?.full_name === env.GITHUB_REPOSITORY);
|
||||
return { commit, tag, branch, slug, publish, pullRequest, before: event.before };
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { readCiContext } from './ci-context.mjs';
|
||||
|
||||
try {
|
||||
const { commit, tag, slug, publish } = readCiContext();
|
||||
if (!publish) throw new Error('Images can only be published by pushes or same-repository PRs.');
|
||||
execFileSync(process.execPath, [fileURLToPath(new URL('./ci-policy.mjs', import.meta.url))]);
|
||||
const env = process.env;
|
||||
if (!env.REGISTRY_USERNAME || !env.REGISTRY_PASSWORD) {
|
||||
throw new Error('REGISTRY_USERNAME and REGISTRY_PASSWORD are required for publication.');
|
||||
}
|
||||
const registry = new URL(env.GITHUB_SERVER_URL).host;
|
||||
const image = `${registry}/${env.GITHUB_REPOSITORY.toLowerCase()}`;
|
||||
const shaImage = `${image}:sha-${commit}`;
|
||||
const docker = (...args) => execFileSync('docker', args, { stdio: 'inherit' });
|
||||
execFileSync(
|
||||
'docker',
|
||||
['login', registry, '--username', env.REGISTRY_USERNAME, '--password-stdin'],
|
||||
{
|
||||
input: env.REGISTRY_PASSWORD,
|
||||
stdio: ['pipe', 'inherit', 'inherit'],
|
||||
},
|
||||
);
|
||||
if (tag) {
|
||||
docker('pull', shaImage);
|
||||
docker('tag', shaImage, `${image}:${tag}`);
|
||||
docker('push', `${image}:${tag}`);
|
||||
docker('tag', shaImage, `${image}:latest`);
|
||||
docker('push', `${image}:latest`);
|
||||
} else {
|
||||
const branchImage = `${image}:branch-${slug}`;
|
||||
docker('build', '-t', shaImage, '-t', branchImage, '.');
|
||||
docker('push', shaImage);
|
||||
docker('push', branchImage);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync, spawnSync } from 'node:child_process';
|
||||
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const directory = mkdtempSync(join(tmpdir(), 'ci-image-'));
|
||||
const script = fileURLToPath(new URL('./ci-image.mjs', import.meta.url));
|
||||
const git = (...args) => execFileSync('git', args, { cwd: directory, encoding: 'utf8' }).trim();
|
||||
const image = 'gitea.nessar.fr/nessar/vac-optimizer';
|
||||
const commandsPath = join(directory, 'docker-commands.jsonl');
|
||||
const bin = join(directory, 'bin');
|
||||
|
||||
const run = (variables = {}, expected = 0) => {
|
||||
const commit = git('rev-parse', 'HEAD');
|
||||
const eventName = variables.eventName ?? 'push';
|
||||
const ref = variables.tag
|
||||
? `refs/tags/${variables.tag}`
|
||||
: eventName === 'pull_request'
|
||||
? 'refs/pull/42/head'
|
||||
: 'refs/heads/feature/calendar';
|
||||
const event = { ref };
|
||||
if (eventName === 'pull_request') {
|
||||
event.pull_request = {
|
||||
title: 'feat: calendar',
|
||||
head: {
|
||||
sha: commit,
|
||||
ref: 'feature/calendar',
|
||||
repo: { full_name: variables.headRepository ?? 'nessar/vac-optimizer' },
|
||||
},
|
||||
base: { ref: 'develop' },
|
||||
};
|
||||
}
|
||||
writeFileSync(join(directory, 'event.json'), JSON.stringify(event));
|
||||
writeFileSync(commandsPath, '');
|
||||
const result = spawnSync(process.execPath, [script], {
|
||||
cwd: directory,
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
PATH: `${bin}:${process.env.PATH}`,
|
||||
GITHUB_EVENT_NAME: eventName,
|
||||
GITHUB_EVENT_PATH: join(directory, 'event.json'),
|
||||
GITHUB_SHA: commit,
|
||||
GITHUB_REF: ref,
|
||||
GITHUB_REPOSITORY: 'nessar/vac-optimizer',
|
||||
GITHUB_SERVER_URL: 'https://gitea.nessar.fr',
|
||||
REGISTRY_USERNAME: 'publisher',
|
||||
REGISTRY_PASSWORD: variables.registryPassword ?? 'test-token',
|
||||
DOCKER_COMMANDS: commandsPath,
|
||||
MISSING_IMAGE: variables.missingImage ? 'true' : '',
|
||||
},
|
||||
});
|
||||
assert.equal(result.status, expected, result.stdout + result.stderr);
|
||||
return readFileSync(commandsPath, 'utf8').trim().split('\n').filter(Boolean).map(JSON.parse);
|
||||
};
|
||||
|
||||
try {
|
||||
git('init', '--initial-branch=main');
|
||||
git('config', 'user.name', 'CI image test');
|
||||
git('config', 'user.email', 'ci-image@example.invalid');
|
||||
git('remote', 'add', 'origin', directory);
|
||||
writeFileSync(join(directory, 'package.json'), '{"version":"1.5.0"}\n');
|
||||
git('add', 'package.json');
|
||||
git('commit', '-m', 'chore: initial release');
|
||||
git('branch', 'develop');
|
||||
git('switch', '-c', 'feature/calendar');
|
||||
git('commit', '--allow-empty', '-m', 'feat: calendar');
|
||||
mkdirSync(bin);
|
||||
const docker = join(bin, 'docker');
|
||||
writeFileSync(
|
||||
docker,
|
||||
`#!/usr/bin/env node
|
||||
import { appendFileSync, readFileSync } from 'node:fs';
|
||||
const args = process.argv.slice(2);
|
||||
const input = args[0] === 'login' ? readFileSync(0, 'utf8') : '';
|
||||
appendFileSync(process.env.DOCKER_COMMANDS, JSON.stringify({ args, input }) + '\\n');
|
||||
if (args[0] === 'pull' && process.env.MISSING_IMAGE === 'true') process.exit(1);
|
||||
`,
|
||||
);
|
||||
chmodSync(docker, 0o755);
|
||||
const commit = git('rev-parse', 'HEAD');
|
||||
const commands = run();
|
||||
assert.deepEqual(commands, [
|
||||
{
|
||||
args: ['login', 'gitea.nessar.fr', '--username', 'publisher', '--password-stdin'],
|
||||
input: 'test-token',
|
||||
},
|
||||
{
|
||||
args: [
|
||||
'build',
|
||||
'-t',
|
||||
`${image}:sha-${commit}`,
|
||||
'-t',
|
||||
`${image}:branch-feature-calendar`,
|
||||
'.',
|
||||
],
|
||||
input: '',
|
||||
},
|
||||
{ args: ['push', `${image}:sha-${commit}`], input: '' },
|
||||
{ args: ['push', `${image}:branch-feature-calendar`], input: '' },
|
||||
]);
|
||||
assert.deepEqual(run({ eventName: 'pull_request' }), commands);
|
||||
assert.deepEqual(
|
||||
run({ eventName: 'pull_request', headRepository: 'contributor/vac-optimizer' }, 1),
|
||||
[],
|
||||
);
|
||||
assert.deepEqual(run({ eventName: 'workflow_dispatch' }, 1), []);
|
||||
assert.deepEqual(run({ registryPassword: '' }, 1), []);
|
||||
assert.deepEqual(run({ tag: 'v1.5.0' }, 1), []);
|
||||
git('switch', 'main');
|
||||
git('tag', '-a', 'v1.5.0', '-m', 'Release 1.5.0');
|
||||
const releaseCommit = git('rev-parse', 'HEAD');
|
||||
assert.deepEqual(run({ tag: 'v1.5.0' }), [
|
||||
{
|
||||
args: ['login', 'gitea.nessar.fr', '--username', 'publisher', '--password-stdin'],
|
||||
input: 'test-token',
|
||||
},
|
||||
{ args: ['pull', `${image}:sha-${releaseCommit}`], input: '' },
|
||||
{ args: ['tag', `${image}:sha-${releaseCommit}`, `${image}:v1.5.0`], input: '' },
|
||||
{ args: ['push', `${image}:v1.5.0`], input: '' },
|
||||
{ args: ['tag', `${image}:sha-${releaseCommit}`, `${image}:latest`], input: '' },
|
||||
{ args: ['push', `${image}:latest`], input: '' },
|
||||
]);
|
||||
assert.deepEqual(run({ tag: 'v1.5.0', missingImage: true }, 1), [
|
||||
{
|
||||
args: ['login', 'gitea.nessar.fr', '--username', 'publisher', '--password-stdin'],
|
||||
input: 'test-token',
|
||||
},
|
||||
{ args: ['pull', `${image}:sha-${releaseCommit}`], input: '' },
|
||||
]);
|
||||
assert.deepEqual(run({ tag: 'v1.5.0-rc.1' }, 1), []);
|
||||
assert.deepEqual(run({ tag: 'v1.6.0' }, 1), []);
|
||||
console.log('CI image publication checks passed.');
|
||||
} finally {
|
||||
rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
+17
-15
@@ -1,5 +1,6 @@
|
||||
import { execFileSync, spawnSync } from 'node:child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { appendFileSync, readFileSync } from 'node:fs';
|
||||
import { readCiContext } from './ci-context.mjs';
|
||||
|
||||
const env = process.env;
|
||||
const git = (...args) => execFileSync('git', args, { encoding: 'utf8' }).trim();
|
||||
@@ -11,7 +12,7 @@ const requirePolicy = (condition, message) => {
|
||||
};
|
||||
|
||||
try {
|
||||
const commit = env.CI_COMMIT_SHA;
|
||||
const { commit, tag, branch, slug, pullRequest, before } = readCiContext();
|
||||
requirePolicy(commit === git('rev-parse', 'HEAD'), 'CI must validate the checked-out commit.');
|
||||
git(
|
||||
'fetch',
|
||||
@@ -21,33 +22,32 @@ try {
|
||||
'+refs/heads/develop:refs/remotes/origin/develop',
|
||||
);
|
||||
|
||||
if (env.CI_COMMIT_TAG) {
|
||||
if (tag) {
|
||||
requirePolicy(
|
||||
/^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(env.CI_COMMIT_TAG),
|
||||
/^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(tag),
|
||||
'Release tags must use vMAJOR.MINOR.PATCH without leading zeroes.',
|
||||
);
|
||||
const { version } = JSON.parse(readFileSync('package.json', 'utf8'));
|
||||
requirePolicy(env.CI_COMMIT_TAG === `v${version}`, 'Release tag must match package.json.');
|
||||
requirePolicy(tag === `v${version}`, 'Release tag must match package.json.');
|
||||
requirePolicy(ancestor(commit, 'origin/main'), 'Release commit must belong to main.');
|
||||
} else {
|
||||
const mr = env.CI_PIPELINE_SOURCE === 'merge_request_event';
|
||||
const branch = mr ? env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME : env.CI_COMMIT_BRANCH;
|
||||
const target = mr
|
||||
? env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME
|
||||
const target = pullRequest
|
||||
? pullRequest.base?.ref
|
||||
: branch === 'main' || branch?.startsWith('hotfix/')
|
||||
? 'main'
|
||||
: 'develop';
|
||||
requirePolicy(branch, 'A branch name is required.');
|
||||
requirePolicy(['main', 'develop'].includes(target), 'MRs must target main or develop.');
|
||||
git('check-ref-format', `refs/heads/${branch}`);
|
||||
requirePolicy(['main', 'develop'].includes(target), 'PRs must target main or develop.');
|
||||
|
||||
if (mr) {
|
||||
const title = env.CI_MERGE_REQUEST_TITLE?.replace(
|
||||
if (pullRequest) {
|
||||
const title = pullRequest.title?.replace(
|
||||
/^(?:Draft:|WIP:|\[Draft\]|\[WIP\]|\(Draft\)|\(WIP\))\s*/i,
|
||||
'',
|
||||
);
|
||||
requirePolicy(
|
||||
conventional.test(title ?? ''),
|
||||
'MR title must use Conventional Commits.',
|
||||
'PR title must use Conventional Commits.',
|
||||
);
|
||||
requirePolicy(
|
||||
target !== 'main' || /^(release|hotfix)\/.+/.test(branch),
|
||||
@@ -65,9 +65,8 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
const before = env.CI_COMMIT_BEFORE_SHA;
|
||||
const base =
|
||||
!mr && before && !/^0+$/.test(before) && ancestor(before, commit)
|
||||
!pullRequest && before && !/^0+$/.test(before) && ancestor(before, commit)
|
||||
? before
|
||||
: git('merge-base', `origin/${target}`, commit);
|
||||
const subjects = git('log', '--format=%h %s', `${base}..${commit}`);
|
||||
@@ -78,6 +77,9 @@ try {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (env.GITHUB_OUTPUT) {
|
||||
appendFileSync(env.GITHUB_OUTPUT, `slug=${slug}\ntag=${tag}\n`);
|
||||
}
|
||||
console.log('GitFlow, version and commit policy passed.');
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
|
||||
+65
-38
@@ -1,6 +1,6 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync, spawnSync } from 'node:child_process';
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
@@ -8,26 +8,48 @@ import { fileURLToPath } from 'node:url';
|
||||
const directory = mkdtempSync(join(tmpdir(), 'ci-policy-'));
|
||||
const script = fileURLToPath(new URL('./ci-policy.mjs', import.meta.url));
|
||||
const git = (...args) => execFileSync('git', args, { cwd: directory, encoding: 'utf8' }).trim();
|
||||
const check = (variables, expected = 0) => {
|
||||
const check = (variables = {}, expected = 0) => {
|
||||
const commit = git('rev-parse', 'HEAD');
|
||||
const branch = variables.branch ?? 'feature/test';
|
||||
const eventName = variables.eventName ?? 'push';
|
||||
const ref = variables.tag
|
||||
? `refs/tags/${variables.tag}`
|
||||
: eventName === 'pull_request'
|
||||
? 'refs/pull/42/head'
|
||||
: `refs/heads/${branch}`;
|
||||
const event = { ref, before: variables.before };
|
||||
if (eventName === 'pull_request') {
|
||||
event.pull_request = {
|
||||
title: variables.title ?? 'feat(test): add policy checks',
|
||||
head: {
|
||||
ref: branch,
|
||||
sha: variables.headSha ?? commit,
|
||||
repo: { full_name: variables.headRepository ?? 'nessar/vac-optimizer' },
|
||||
},
|
||||
base: { ref: variables.target ?? 'develop' },
|
||||
};
|
||||
}
|
||||
const eventPath = join(directory, 'event.json');
|
||||
const outputPath = join(directory, 'outputs');
|
||||
writeFileSync(eventPath, JSON.stringify(event));
|
||||
writeFileSync(outputPath, '');
|
||||
const result = spawnSync(process.execPath, [script], {
|
||||
cwd: directory,
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
PATH: process.env.PATH,
|
||||
CI_PIPELINE_SOURCE: 'push',
|
||||
CI_COMMIT_SHA: git('rev-parse', 'HEAD'),
|
||||
CI_COMMIT_BRANCH: 'feature/test',
|
||||
...variables,
|
||||
GITHUB_EVENT_NAME: eventName,
|
||||
GITHUB_EVENT_PATH: eventPath,
|
||||
GITHUB_REF: ref,
|
||||
GITHUB_SHA: variables.sha ?? commit,
|
||||
GITHUB_REPOSITORY: 'nessar/vac-optimizer',
|
||||
GITHUB_OUTPUT: outputPath,
|
||||
},
|
||||
});
|
||||
assert.equal(result.status, expected, result.stdout + result.stderr);
|
||||
return readFileSync(outputPath, 'utf8');
|
||||
};
|
||||
const mr = {
|
||||
CI_PIPELINE_SOURCE: 'merge_request_event',
|
||||
CI_MERGE_REQUEST_SOURCE_BRANCH_NAME: 'feature/test',
|
||||
CI_MERGE_REQUEST_TARGET_BRANCH_NAME: 'develop',
|
||||
CI_MERGE_REQUEST_TITLE: 'feat(test): add policy checks',
|
||||
};
|
||||
const pr = { eventName: 'pull_request' };
|
||||
|
||||
try {
|
||||
git('init', '--initial-branch=main');
|
||||
@@ -41,26 +63,31 @@ try {
|
||||
git('switch', '-c', 'feature/test');
|
||||
git('commit', '--allow-empty', '-m', 'feat(test)!: add checks');
|
||||
check({});
|
||||
check(mr);
|
||||
check({ ...mr, CI_MERGE_REQUEST_TITLE: 'Draft: feat: test policy' });
|
||||
check({ ...mr, CI_MERGE_REQUEST_TITLE: '(Draft) feat: test policy' });
|
||||
check({ ...mr, CI_MERGE_REQUEST_TARGET_BRANCH_NAME: 'main' }, 1);
|
||||
check({ ...mr, CI_MERGE_REQUEST_TITLE: 'Add checks' }, 1);
|
||||
check({ CI_COMMIT_TAG: 'v1.5.0' }, 1);
|
||||
check({ CI_COMMIT_SHA: '0'.repeat(40) }, 1);
|
||||
check(pr);
|
||||
check({ ...pr, headRepository: 'contributor/vac-optimizer' });
|
||||
check({ eventName: 'workflow_dispatch' });
|
||||
assert.match(check({ branch: 'Feature/My_calendar' }), /slug=feature-my-calendar\n/);
|
||||
check({ ...pr, headSha: '0'.repeat(40) }, 1);
|
||||
check({ eventName: 'pull_request_target' }, 1);
|
||||
check({ ...pr, title: 'Draft: feat: test policy' });
|
||||
check({ ...pr, title: '(Draft) feat: test policy' });
|
||||
check({ ...pr, target: 'main' }, 1);
|
||||
check({ ...pr, title: 'Add checks' }, 1);
|
||||
check({ tag: 'v1.5.0' }, 1);
|
||||
check({ sha: '0'.repeat(40) }, 1);
|
||||
|
||||
git('switch', 'develop');
|
||||
git('commit', '--allow-empty', '-m', 'fix: another change');
|
||||
git('switch', 'feature/test');
|
||||
check(mr, 1);
|
||||
check(pr, 1);
|
||||
git('rebase', 'develop');
|
||||
check(mr);
|
||||
check(pr);
|
||||
const before = git('rev-parse', 'HEAD');
|
||||
git('commit', '--allow-empty', '-m', 'not conventional');
|
||||
check({ CI_COMMIT_BEFORE_SHA: before }, 1);
|
||||
check(mr, 1);
|
||||
check({ before: before }, 1);
|
||||
check(pr, 1);
|
||||
git('commit', '--amend', '--allow-empty', '-m', 'ci: fix the commit title');
|
||||
check({ CI_COMMIT_BEFORE_SHA: before });
|
||||
check({ before: before });
|
||||
|
||||
git('switch', 'develop');
|
||||
git('merge', '--no-ff', 'feature/test', '-m', 'feat(test): add policy checks');
|
||||
@@ -69,33 +96,33 @@ try {
|
||||
git('add', 'package.json');
|
||||
git('commit', '-m', 'chore(release): prepare 1.6.0');
|
||||
check({
|
||||
...mr,
|
||||
CI_MERGE_REQUEST_SOURCE_BRANCH_NAME: 'release/1.6.0',
|
||||
CI_MERGE_REQUEST_TARGET_BRANCH_NAME: 'main',
|
||||
...pr,
|
||||
branch: 'release/1.6.0',
|
||||
target: 'main',
|
||||
});
|
||||
check({ ...mr, CI_MERGE_REQUEST_SOURCE_BRANCH_NAME: 'release/1.6.0' });
|
||||
check({ ...pr, branch: 'release/1.6.0' });
|
||||
git('switch', 'main');
|
||||
git('merge', '--no-ff', 'release/1.6.0', '-m', 'chore(release): publish 1.6.0');
|
||||
check({ CI_COMMIT_TAG: 'v1.6.0' });
|
||||
check({ CI_COMMIT_TAG: 'v1.5.0' }, 1);
|
||||
check({ CI_COMMIT_TAG: 'v01.6.0' }, 1);
|
||||
check({ CI_COMMIT_TAG: 'v1.6.0-rc.1' }, 1);
|
||||
check({ ...mr, CI_MERGE_REQUEST_SOURCE_BRANCH_NAME: 'main' });
|
||||
check({ tag: 'v1.6.0' });
|
||||
check({ tag: 'v1.5.0' }, 1);
|
||||
check({ tag: 'v01.6.0' }, 1);
|
||||
check({ tag: 'v1.6.0-rc.1' }, 1);
|
||||
check({ ...pr, branch: 'main' });
|
||||
|
||||
git('switch', '-c', 'hotfix/1.6.1');
|
||||
git('commit', '--allow-empty', '-m', 'fix: production issue');
|
||||
check({
|
||||
...mr,
|
||||
CI_MERGE_REQUEST_SOURCE_BRANCH_NAME: 'hotfix/1.6.1',
|
||||
CI_MERGE_REQUEST_TARGET_BRANCH_NAME: 'main',
|
||||
...pr,
|
||||
branch: 'hotfix/1.6.1',
|
||||
target: 'main',
|
||||
});
|
||||
check({ ...mr, CI_MERGE_REQUEST_SOURCE_BRANCH_NAME: 'hotfix/1.6.1' });
|
||||
check({ ...pr, branch: 'hotfix/1.6.1' });
|
||||
|
||||
// The working branch must not pass by merging develop instead of rebasing.
|
||||
git('switch', 'feature/test');
|
||||
git('commit', '--allow-empty', '-m', 'feat: keep working');
|
||||
git('merge', '--no-ff', 'develop', '-m', 'chore: merge develop');
|
||||
check(mr, 1);
|
||||
check(pr, 1);
|
||||
|
||||
console.log('CI policy integration checks passed.');
|
||||
} finally {
|
||||
|
||||
Reference in new issue
Block a user