@ogygie/cli 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +7596 -2086
- package/package.json +7 -2
- package/templates/project/ci/ogygie-gates.yml +152 -8
- package/templates/project/design/tokens.json +129 -22
- package/templates/project/package.json +1 -0
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ogygie/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Ogygie framework CLI: deterministic scaffolding (init), governed-file upgrades (upgrade) and lockstep verification (check) for Ogygie products.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/julienTeam/Ogygie.git",
|
|
9
|
+
"directory": "packages/ogygie-cli"
|
|
10
|
+
},
|
|
6
11
|
"type": "module",
|
|
7
12
|
"bin": {
|
|
8
13
|
"ogygie": "dist/cli.js"
|
|
@@ -17,7 +22,7 @@
|
|
|
17
22
|
"access": "public"
|
|
18
23
|
},
|
|
19
24
|
"devDependencies": {
|
|
20
|
-
"@ogygie/core": "0.
|
|
25
|
+
"@ogygie/core": "0.21.0",
|
|
21
26
|
"@types/node": "^24.0.0",
|
|
22
27
|
"esbuild": "^0.28.0",
|
|
23
28
|
"tsx": "^4.20.0",
|
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
name: ogygie-gates
|
|
5
5
|
on:
|
|
6
6
|
push:
|
|
7
|
-
branches: [main]
|
|
7
|
+
branches: [main, "spec/**"]
|
|
8
8
|
pull_request:
|
|
9
9
|
|
|
10
|
-
# Least-privilege token:
|
|
11
|
-
#
|
|
10
|
+
# Least-privilege token: the gates only read the checkout. The one job that
|
|
11
|
+
# writes (speculate) raises its own permission, alone.
|
|
12
12
|
permissions:
|
|
13
13
|
contents: read
|
|
14
|
-
pull-requests: read
|
|
15
14
|
|
|
16
15
|
jobs:
|
|
17
16
|
verifier:
|
|
@@ -34,10 +33,155 @@ jobs:
|
|
|
34
33
|
- run: pnpm lint # any, ts-ignore, raw-db, dangerouslySetInnerHTML
|
|
35
34
|
- run: pnpm test
|
|
36
35
|
# Security
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
# Committed secrets: the gitleaks binary, pinned by version and checksum.
|
|
37
|
+
# Its GitHub action asks for a licence key as soon as the repository
|
|
38
|
+
# belongs to an organization; the binary asks for nothing. A pull request
|
|
39
|
+
# scans its own commits and a push the ones it brings, so a leak already
|
|
40
|
+
# dealt with in the past does not keep the gate red.
|
|
41
|
+
- env:
|
|
42
|
+
GITLEAKS_VERSION: "8.30.1"
|
|
43
|
+
GITLEAKS_SHA256: "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb"
|
|
44
|
+
RANGE: "${{ github.event_name == 'pull_request' && format('{0}..{1}', github.event.pull_request.base.sha, github.event.pull_request.head.sha) || format('{0}..{1}', github.event.before, github.sha) }}"
|
|
45
|
+
run: |
|
|
46
|
+
curl -sSLo "$RUNNER_TEMP/gitleaks.tar.gz" "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
|
|
47
|
+
echo "${GITLEAKS_SHA256} $RUNNER_TEMP/gitleaks.tar.gz" | sha256sum -c -
|
|
48
|
+
tar -xzf "$RUNNER_TEMP/gitleaks.tar.gz" -C "$RUNNER_TEMP" gitleaks
|
|
49
|
+
# A branch born with this push has no "before": the whole history is scanned.
|
|
50
|
+
case "$RANGE" in 0000000000000000000000000000000000000000..*) RANGE="" ;; esac
|
|
51
|
+
"$RUNNER_TEMP/gitleaks" git --redact --no-banner ${RANGE:+--log-opts="$RANGE"} .
|
|
41
52
|
- uses: semgrep/semgrep-action@v1 # dangerous patterns (SAST)
|
|
42
53
|
with: { config: "p/typescript p/owasp-top-ten" }
|
|
43
54
|
- run: pnpm audit --audit-level=high # dependency CVEs
|
|
55
|
+
|
|
56
|
+
# Hosted mode (spec ogygie-hosted-mode B5): a pull request whose branch is
|
|
57
|
+
# spec/<id> is mergeable only once its spec is SIGNED (status validated).
|
|
58
|
+
# Require this check in the branch protection of the default branch: GitHub
|
|
59
|
+
# then holds the rule, never a UI. Any other branch passes and says so.
|
|
60
|
+
spec-signed:
|
|
61
|
+
if: "${{ github.event_name == 'pull_request' }}"
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v4
|
|
65
|
+
- uses: pnpm/action-setup@v4
|
|
66
|
+
- uses: actions/setup-node@v4
|
|
67
|
+
with: { node-version: 22, cache: pnpm }
|
|
68
|
+
- run: pnpm install --frozen-lockfile
|
|
69
|
+
- run: pnpm exec ogygie verify-spec-signed # reads GITHUB_HEAD_REF
|
|
70
|
+
|
|
71
|
+
# Hosted mode (spec ogygie-hosted-mode B4): on a push to spec/<id>, the
|
|
72
|
+
# generator derives on the branch what the spec declares, deterministically,
|
|
73
|
+
# and commits it there — never on the default branch — with the preview pages
|
|
74
|
+
# of a screen. The first schemas.ts of a NEW feature is drawn by the model and
|
|
75
|
+
# judged by the gates, written once; refused twice, the pull request gets the
|
|
76
|
+
# needs-operator label and nothing is generated. The holes are filled — library, draw, escalation — with the client's model key
|
|
77
|
+
# (a repo secret), the test of a candidate running without it; the body of a
|
|
78
|
+
# custom view is one of them, and gets the same label when no candidate is green. A repo
|
|
79
|
+
# without the generator installed is told so, and the branch is left as is.
|
|
80
|
+
# With OGYGIE_CI_TOKEN (a token allowed to push to spec/* branches), the
|
|
81
|
+
# commit it pushes gets its own checks; with the default token, it does not.
|
|
82
|
+
speculate:
|
|
83
|
+
if: "${{ github.event_name == 'push' && startsWith(github.ref_name, 'spec/') }}"
|
|
84
|
+
runs-on: ubuntu-latest
|
|
85
|
+
permissions:
|
|
86
|
+
contents: write
|
|
87
|
+
# The needs-operator label on the branch's pull request (labels go through the issues API).
|
|
88
|
+
pull-requests: write
|
|
89
|
+
issues: write
|
|
90
|
+
steps:
|
|
91
|
+
- uses: actions/checkout@v4
|
|
92
|
+
with:
|
|
93
|
+
fetch-depth: 0
|
|
94
|
+
token: "${{ secrets.OGYGIE_CI_TOKEN || github.token }}"
|
|
95
|
+
- uses: pnpm/action-setup@v4
|
|
96
|
+
- uses: actions/setup-node@v4
|
|
97
|
+
with: { node-version: 22, cache: pnpm }
|
|
98
|
+
- run: pnpm install --frozen-lockfile
|
|
99
|
+
- id: generator
|
|
100
|
+
run: |
|
|
101
|
+
if node -e "require.resolve('@ogygie/generate/cli')" 2>/dev/null; then
|
|
102
|
+
echo "installed=true" >> "$GITHUB_OUTPUT"
|
|
103
|
+
else
|
|
104
|
+
echo "installed=false" >> "$GITHUB_OUTPUT"
|
|
105
|
+
echo "ogygie-gates — @ogygie/generate n'est pas installé : la branche n'est pas spéculée."
|
|
106
|
+
fi
|
|
107
|
+
- if: "${{ steps.generator.outputs.installed == 'true' }}"
|
|
108
|
+
env:
|
|
109
|
+
OGYGIE_MODEL_API_KEY: "${{ secrets.OGYGIE_MODEL_API_KEY }}"
|
|
110
|
+
GH_TOKEN: "${{ github.token }}"
|
|
111
|
+
run: |
|
|
112
|
+
SPEC_ID="${GITHUB_REF_NAME#spec/}"
|
|
113
|
+
SPEC_FILE="specs/${SPEC_ID}.spec.md"
|
|
114
|
+
if [ ! -f "$SPEC_FILE" ]; then
|
|
115
|
+
echo "ogygie-gates — $SPEC_FILE absent de la branche : rien à spéculer."
|
|
116
|
+
exit 0
|
|
117
|
+
fi
|
|
118
|
+
if ! grep -q '^# Génération' "$SPEC_FILE"; then
|
|
119
|
+
echo "ogygie-gates — $SPEC_FILE sans section # Génération : rien à générer."
|
|
120
|
+
exit 0
|
|
121
|
+
fi
|
|
122
|
+
# A NEW feature has no schemas.ts yet: the model writes the first one, the
|
|
123
|
+
# gates judge it, two attempts (exit 2 = the operator). The pull request of
|
|
124
|
+
# the branch carries the verdict as a label, so the studio can say it.
|
|
125
|
+
pnpm exec ogygie generate schemas "$SPEC_ID" || SCHEMAS_STATUS=$?
|
|
126
|
+
PR_NUMBER="$(gh pr list --head "$GITHUB_REF_NAME" --state open --json number --jq '.[0].number' 2>/dev/null || true)"
|
|
127
|
+
if [ "${SCHEMAS_STATUS:-0}" = "2" ]; then
|
|
128
|
+
if [ -n "$PR_NUMBER" ]; then
|
|
129
|
+
gh label create needs-operator --force --color D9A35A --description "La spec attend un geste de l'opérateur : un schemas.ts refusé deux fois, ou le corps d'une vue custom sans candidat vert." >/dev/null
|
|
130
|
+
gh pr edit "$PR_NUMBER" --add-label needs-operator
|
|
131
|
+
fi
|
|
132
|
+
echo "ogygie-gates — le schemas.ts de $SPEC_ID attend l'opérateur (needs-operator) : rien n'est généré sur la branche."
|
|
133
|
+
exit 1
|
|
134
|
+
elif [ "${SCHEMAS_STATUS:-0}" != "0" ]; then
|
|
135
|
+
exit "$SCHEMAS_STATUS"
|
|
136
|
+
fi
|
|
137
|
+
if [ -n "$PR_NUMBER" ]; then
|
|
138
|
+
gh pr edit "$PR_NUMBER" --remove-label needs-operator 2>/dev/null || true
|
|
139
|
+
fi
|
|
140
|
+
pnpm exec ogygie generate check "$SPEC_ID"
|
|
141
|
+
pnpm exec ogygie generate generate "$SPEC_ID"
|
|
142
|
+
# The holes: the library first, then the client's model workspace key —
|
|
143
|
+
# a repo secret, present in THIS step alone; the test of a candidate
|
|
144
|
+
# runs in a process that holds no secret. Absent, the library alone.
|
|
145
|
+
pnpm exec ogygie generate fill "$SPEC_ID" || FILL_STATUS=$?
|
|
146
|
+
pnpm exec ogygie generate verify
|
|
147
|
+
if [ "${FILL_STATUS:-0}" = "2" ]; then
|
|
148
|
+
# The body of a custom view (hosted mode, the body as a hole): no candidate
|
|
149
|
+
# rendered its example — the operator writes it. The label says so; the
|
|
150
|
+
# view's own test stays red until then, so the pull request does too.
|
|
151
|
+
if [ -n "$PR_NUMBER" ]; then
|
|
152
|
+
gh label create needs-operator --force --color D9A35A --description "La spec attend un geste de l'opérateur : un schemas.ts refusé deux fois, ou le corps d'une vue custom sans candidat vert." >/dev/null
|
|
153
|
+
gh pr edit "$PR_NUMBER" --add-label needs-operator
|
|
154
|
+
fi
|
|
155
|
+
echo "ogygie-gates — le corps d'une vue custom de $SPEC_ID attend l'opérateur (needs-operator) : son test d'exemple reste rouge."
|
|
156
|
+
elif [ "${FILL_STATUS:-0}" != "0" ]; then
|
|
157
|
+
echo "ogygie-gates — des trous restent à l'humain, ou le plafond du modèle est atteint : voir le journal ci-dessus."
|
|
158
|
+
fi
|
|
159
|
+
# The previews of a screen (spec ogygie-hosted-mode B10): a page per
|
|
160
|
+
# state under previews/<id>/, committed with the derived code; not a
|
|
161
|
+
# screen, or a jsdom front: nothing to render, said, exit 0.
|
|
162
|
+
pnpm exec ogygie generate preview "$SPEC_ID"
|
|
163
|
+
- if: "${{ steps.generator.outputs.installed == 'true' }}"
|
|
164
|
+
run: |
|
|
165
|
+
# A commit that is no longer the head of the branch — a composition the
|
|
166
|
+
# model proposed before its preferred one — keeps its previews alone,
|
|
167
|
+
# under the composition's folder; the derived code is the head's.
|
|
168
|
+
HEAD_SHA="$(git ls-remote origin "refs/heads/${GITHUB_REF_NAME}" | cut -f1)"
|
|
169
|
+
if [ -n "$HEAD_SHA" ] && [ "$HEAD_SHA" != "$GITHUB_SHA" ]; then
|
|
170
|
+
git add previews 2>/dev/null || true
|
|
171
|
+
MESSAGE="speculate: ${GITHUB_REF_NAME#spec/} — aperçus d'une composition proposée (${GITHUB_SHA:0:7})"
|
|
172
|
+
else
|
|
173
|
+
git add -A
|
|
174
|
+
MESSAGE="speculate: ${GITHUB_REF_NAME#spec/} — dérivé par ogygie generate sur la branche"
|
|
175
|
+
fi
|
|
176
|
+
if git diff --cached --quiet; then
|
|
177
|
+
echo "ogygie-gates — rien de nouveau à committer sur la branche."
|
|
178
|
+
exit 0
|
|
179
|
+
fi
|
|
180
|
+
git config user.name "ogygie-speculate"
|
|
181
|
+
git config user.email "ogygie-speculate@users.noreply.github.com"
|
|
182
|
+
git commit -m "$MESSAGE"
|
|
183
|
+
# The branch may have moved since this commit was checked out: replay on its head.
|
|
184
|
+
git reset --hard HEAD --quiet
|
|
185
|
+
git clean -fdq
|
|
186
|
+
git pull --rebase origin "$GITHUB_REF_NAME"
|
|
187
|
+
git push origin "HEAD:${GITHUB_REF_NAME}"
|
|
@@ -1,32 +1,112 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$intent": "
|
|
2
|
+
"$intent": "Design tokens of the starter: the Ogygie charter, two themes (dark first, the identity; light, a complete working theme). A product REWRITES these values when it opens (AGENTS.product.md); the structure stays. A colour or a shadow is either ONE value for every theme or one value per theme id of `themes`, the first theme being the default. The design tool composes ONLY from these tokens: a missing colour, spacing or motion value is an amendment of this file (explicit decision), never a hardcoded value (AGENTS.md §Front). The socle `@ogygie/ui` only names tokens (surface, border, text, accent, danger, focus...): every name it uses must stay declared here.",
|
|
3
|
+
"themes": [
|
|
4
|
+
"dark",
|
|
5
|
+
"light"
|
|
6
|
+
],
|
|
3
7
|
"color": {
|
|
4
|
-
"surface":
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
"surface": {
|
|
9
|
+
"dark": "#0b1322",
|
|
10
|
+
"light": "#f6f3ec"
|
|
11
|
+
},
|
|
12
|
+
"surface-raised": {
|
|
13
|
+
"dark": "#131d30",
|
|
14
|
+
"light": "#ffffff"
|
|
15
|
+
},
|
|
16
|
+
"surface-sunken": {
|
|
17
|
+
"dark": "#060b15",
|
|
18
|
+
"light": "#ece8de"
|
|
19
|
+
},
|
|
20
|
+
"border": {
|
|
21
|
+
"dark": "#223049",
|
|
22
|
+
"light": "#ddd7ca"
|
|
23
|
+
},
|
|
24
|
+
"border-strong": {
|
|
25
|
+
"dark": "#5a6d93",
|
|
26
|
+
"light": "#8b8571"
|
|
27
|
+
},
|
|
28
|
+
"text": {
|
|
29
|
+
"dark": "#ebe6da",
|
|
30
|
+
"light": "#121a2a"
|
|
31
|
+
},
|
|
32
|
+
"text-muted": {
|
|
33
|
+
"dark": "#a2abba",
|
|
34
|
+
"light": "#5a6475"
|
|
35
|
+
},
|
|
36
|
+
"text-faint": {
|
|
37
|
+
"dark": "#7b8694",
|
|
38
|
+
"light": "#667080"
|
|
39
|
+
},
|
|
40
|
+
"accent": {
|
|
41
|
+
"dark": "#5cd6cc",
|
|
42
|
+
"light": "#0f7580"
|
|
43
|
+
},
|
|
44
|
+
"accent-strong": {
|
|
45
|
+
"dark": "#86e6de",
|
|
46
|
+
"light": "#0a5c66"
|
|
47
|
+
},
|
|
48
|
+
"accent-soft": {
|
|
49
|
+
"dark": "#0e2b31",
|
|
50
|
+
"light": "#dbf2f3"
|
|
51
|
+
},
|
|
52
|
+
"accent-contrast": {
|
|
53
|
+
"dark": "#0b1322",
|
|
54
|
+
"light": "#f3fbfb"
|
|
55
|
+
},
|
|
56
|
+
"accent-glow": {
|
|
57
|
+
"dark": "#5cd6cc29",
|
|
58
|
+
"light": "#0f758024"
|
|
59
|
+
},
|
|
60
|
+
"orichalcum": {
|
|
61
|
+
"dark": "#d9a35a",
|
|
62
|
+
"light": "#9e5420"
|
|
63
|
+
},
|
|
64
|
+
"positive": {
|
|
65
|
+
"dark": "#8fd67a",
|
|
66
|
+
"light": "#2e7a1f"
|
|
67
|
+
},
|
|
68
|
+
"positive-soft": {
|
|
69
|
+
"dark": "#152a16",
|
|
70
|
+
"light": "#e2f3dc"
|
|
71
|
+
},
|
|
72
|
+
"warning": {
|
|
73
|
+
"dark": "#f2d06b",
|
|
74
|
+
"light": "#8a6400"
|
|
75
|
+
},
|
|
76
|
+
"warning-soft": {
|
|
77
|
+
"dark": "#2e2410",
|
|
78
|
+
"light": "#fbeccc"
|
|
79
|
+
},
|
|
80
|
+
"danger": {
|
|
81
|
+
"dark": "#ff8a7a",
|
|
82
|
+
"light": "#b42318"
|
|
83
|
+
},
|
|
84
|
+
"danger-soft": {
|
|
85
|
+
"dark": "#3a1612",
|
|
86
|
+
"light": "#fbe3df"
|
|
87
|
+
},
|
|
88
|
+
"focus": {
|
|
89
|
+
"dark": "#5cd6cc",
|
|
90
|
+
"light": "#0f7580"
|
|
91
|
+
}
|
|
15
92
|
},
|
|
16
93
|
"typography": {
|
|
17
|
-
"font-
|
|
18
|
-
"font-
|
|
94
|
+
"font-display": "'Cormorant Garamond', 'Iowan Old Style', Georgia, serif",
|
|
95
|
+
"font-sans": "'Hanken Grotesk', 'Helvetica Neue', Arial, sans-serif",
|
|
96
|
+
"font-mono": "'Fragment Mono', ui-monospace, 'SF Mono', Menlo, monospace",
|
|
19
97
|
"size": {
|
|
20
98
|
"xs": "0.75rem",
|
|
21
|
-
"sm": "0.
|
|
22
|
-
"base": "
|
|
99
|
+
"sm": "0.8125rem",
|
|
100
|
+
"base": "0.9375rem",
|
|
23
101
|
"lg": "1.125rem",
|
|
24
|
-
"xl": "1.
|
|
25
|
-
"2xl": "1.75rem"
|
|
102
|
+
"xl": "1.25rem",
|
|
103
|
+
"2xl": "1.75rem",
|
|
104
|
+
"3xl": "3rem",
|
|
105
|
+
"4xl": "4.5rem"
|
|
26
106
|
},
|
|
27
107
|
"leading": {
|
|
28
|
-
"tight": "1.
|
|
29
|
-
"normal": "1.
|
|
108
|
+
"tight": "1.2",
|
|
109
|
+
"normal": "1.6"
|
|
30
110
|
}
|
|
31
111
|
},
|
|
32
112
|
"spacing": {
|
|
@@ -36,15 +116,42 @@
|
|
|
36
116
|
"4": "1rem",
|
|
37
117
|
"6": "1.5rem",
|
|
38
118
|
"8": "2rem",
|
|
39
|
-
"12": "3rem"
|
|
119
|
+
"12": "3rem",
|
|
120
|
+
"16": "4rem",
|
|
121
|
+
"24": "6rem"
|
|
40
122
|
},
|
|
41
123
|
"radius": {
|
|
124
|
+
"xs": "0.125rem",
|
|
42
125
|
"sm": "0.25rem",
|
|
43
126
|
"md": "0.5rem",
|
|
44
127
|
"lg": "0.75rem",
|
|
128
|
+
"xl": "1.25rem",
|
|
45
129
|
"full": "9999px"
|
|
46
130
|
},
|
|
47
131
|
"shadow": {
|
|
48
|
-
"raised":
|
|
132
|
+
"raised": {
|
|
133
|
+
"dark": "inset 0 1px 0 rgb(255 255 255 / 0.05), 0 8px 24px rgb(0 0 0 / 0.4)",
|
|
134
|
+
"light": "0 1px 2px rgb(18 26 42 / 0.08), 0 8px 24px rgb(18 26 42 / 0.08)"
|
|
135
|
+
},
|
|
136
|
+
"overlay": {
|
|
137
|
+
"dark": "inset 0 1px 0 rgb(255 255 255 / 0.08), 0 24px 64px rgb(0 0 0 / 0.65)",
|
|
138
|
+
"light": "0 2px 4px rgb(18 26 42 / 0.08), 0 24px 64px rgb(18 26 42 / 0.16)"
|
|
139
|
+
},
|
|
140
|
+
"glow": {
|
|
141
|
+
"dark": "0 0 0 1px rgb(92 214 204 / 0.4), 0 0 48px rgb(92 214 204 / 0.2)",
|
|
142
|
+
"light": "0 0 0 1px rgb(15 117 128 / 0.4), 0 0 48px rgb(15 117 128 / 0.14)"
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"motion": {
|
|
146
|
+
"ease": {
|
|
147
|
+
"swell": "cubic-bezier(0.2, 0.7, 0.2, 1)",
|
|
148
|
+
"retreat": "cubic-bezier(0.4, 0, 0.2, 1)"
|
|
149
|
+
},
|
|
150
|
+
"duration": {
|
|
151
|
+
"fast": "160ms",
|
|
152
|
+
"base": "320ms",
|
|
153
|
+
"slow": "640ms",
|
|
154
|
+
"count": "800ms"
|
|
155
|
+
}
|
|
49
156
|
}
|
|
50
157
|
}
|