grails-cli 0.0.0-stage → 0.3.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/LICENSE.md +250 -0
- package/README.md +279 -2
- package/dist/cli.js +28652 -0
- package/package.json +45 -4
- package/templates/grails-cli-sync.yml +56 -0
- package/templates/skill/grails-cli/SKILL.md +150 -0
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grails-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Grails Code Sync from the command line: reads a component library's code and uploads its design tokens and component specs to Grails.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"homepage": "https://docs.grails.design/cli/overview/",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"grails",
|
|
9
|
+
"design-system",
|
|
10
|
+
"design-tokens",
|
|
11
|
+
"dtcg",
|
|
12
|
+
"components",
|
|
13
|
+
"figma",
|
|
14
|
+
"react",
|
|
15
|
+
"tailwind",
|
|
16
|
+
"cli",
|
|
17
|
+
"code-sync"
|
|
18
|
+
],
|
|
19
|
+
"packageManager": "pnpm@10.32.1",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"bin": {
|
|
22
|
+
"grails-cli": "./dist/cli.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"templates"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public",
|
|
30
|
+
"registry": "https://registry.npmjs.org/"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "node scripts/build.mjs",
|
|
37
|
+
"smoke": "node scripts/smoke.mjs",
|
|
38
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"typescript": "5.8.3"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^22.0.0",
|
|
45
|
+
"esbuild": "^0.27.7"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Grails Code Sync: on every push to the branch your team syncs from (a merge into it), read this repository's
|
|
2
|
+
# design tokens and components and upload them to Grails, so the vault shows what the code actually holds.
|
|
3
|
+
#
|
|
4
|
+
# SETUP
|
|
5
|
+
# 1. In Grails, create an API key with the write:code scope: Team Settings > Integrations > API Keys.
|
|
6
|
+
# 2. Save it as a repository secret named GRAILS_API_KEY: Settings > Secrets and variables > Actions.
|
|
7
|
+
# 3. Say in grails.code.json which branch syncs ("branch": "main", or "preview", …): `npx grails-cli init`
|
|
8
|
+
# proposes it. Put the SAME branch under `branches:` below.
|
|
9
|
+
# 4. Commit this file as .github/workflows/grails-cli-sync.yml.
|
|
10
|
+
#
|
|
11
|
+
# THE KEY stays in the step's `env:`. It is never written into a `run:` line, never echoed, and never passed as
|
|
12
|
+
# an argument: the CLI reads it from the environment and refuses it as a flag. Do not change that, and do not run
|
|
13
|
+
# this on `pull_request`, where a fork's run has no secrets and no business syncing.
|
|
14
|
+
#
|
|
15
|
+
# WHAT LEAVES THE REPOSITORY: design tokens (names, types, values), component names, file paths, and each
|
|
16
|
+
# component's spec (props, anatomy names, resolved style values). Never source code. `npx grails-cli tokens
|
|
17
|
+
# --json` prints the exact token list. `sync --dry-run` stores nothing: with the key it asks Grails what would
|
|
18
|
+
# change, without it it counts locally.
|
|
19
|
+
#
|
|
20
|
+
# EXIT CODES: 0 done · 1 the sync ended incomplete · 2 misconfigured (grails.code.json declares no branch, too) · 3
|
|
21
|
+
# refused by the server (a CLI older than the minimum, too) · 4 the server could not be reached or failed. Only 4 is
|
|
22
|
+
# worth retrying.
|
|
23
|
+
#
|
|
24
|
+
# THE BRANCH: the CLI uploads only from the branch grails.code.json declares. Run anywhere else it uploads nothing,
|
|
25
|
+
# exits 0 so the job stays green, and says so: a notice on the run, the job summary, and the step output
|
|
26
|
+
# `steps.grails.outputs.result` = `skipped-branch` (otherwise `synced`, `preview`, `offline` or `failed`).
|
|
27
|
+
#
|
|
28
|
+
# THE VERSION: `npx --yes grails-cli` fetches the latest release from npm on every run. To choose when a new release
|
|
29
|
+
# reaches your pushes, pin a version (`grails-cli@<version>`) so a new release cannot change a push you did not look at.
|
|
30
|
+
name: Grails Code Sync
|
|
31
|
+
|
|
32
|
+
on:
|
|
33
|
+
push:
|
|
34
|
+
# The branch grails.code.json declares. Keep the two equal.
|
|
35
|
+
branches: [main]
|
|
36
|
+
|
|
37
|
+
permissions:
|
|
38
|
+
contents: read
|
|
39
|
+
|
|
40
|
+
jobs:
|
|
41
|
+
sync:
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v4
|
|
45
|
+
- uses: actions/setup-node@v4
|
|
46
|
+
with:
|
|
47
|
+
node-version: 22
|
|
48
|
+
# Needed when the library uses Tailwind (its tokens are read with the project's OWN tailwindcss; without the
|
|
49
|
+
# dependencies installed the run exits 2), and better in any case: with them the scanner reads the types the
|
|
50
|
+
# way the project does (the "typed" run). Use your package manager's command.
|
|
51
|
+
# - run: npm ci
|
|
52
|
+
- name: Sync the design tokens and the components to Grails
|
|
53
|
+
id: grails
|
|
54
|
+
env:
|
|
55
|
+
GRAILS_API_KEY: ${{ secrets.GRAILS_API_KEY }}
|
|
56
|
+
run: npx --yes grails-cli sync --trigger ci
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: grails-cli
|
|
3
|
+
description: Use when working with the Grails CLI (`grails-cli`) in a component library's repository. That covers setting up `grails.code.json`, syncing components and design tokens to Grails from a terminal or from CI, writing DTCG token files that Grails accepts, starting a first DTCG version from the scanner's tokens, listing the token changes people asked for in Grails, and fixing a sync that failed. Triggers on grails-cli, grails.code.json, Grails Code Sync, GRAILS_API_KEY, DTCG upload, design tokens to Grails, tokens pending, tokens baseline.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# The Grails CLI
|
|
7
|
+
|
|
8
|
+
Grails (grails.design) keeps a design system's Figma files and its code side by side. This CLI reads your
|
|
9
|
+
component library's repository (its components and its design tokens) and syncs them to a Grails project.
|
|
10
|
+
|
|
11
|
+
**It never changes your code.** It writes only the files you explicitly ask it to: `init --write` writes
|
|
12
|
+
`grails.code.json`, `scan --out <file>` writes the manifest, and `tokens baseline --out <dir>` writes the scanner's
|
|
13
|
+
tokens as DTCG files (both refuse a path inside the repository unless you pass `--in-repo`). When people in Grails ask
|
|
14
|
+
for a token change, the CLI lists the change, and you or your build make it in your own source.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
`npm i -D grails-cli` in the repository (Node 20 or newer), or run any command without installing it as
|
|
19
|
+
`npx grails-cli <command>`. The commands below are written `grails-cli`. The GitHub workflow template the package
|
|
20
|
+
ships (`templates/grails-cli-sync.yml`) runs `npx --yes grails-cli sync`: pin a version in it (`grails-cli@<version>`)
|
|
21
|
+
to choose when a new release reaches your pushes.
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
| command | what it does | sends anything? |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `grails-cli init [root] [--write]` | proposes a `grails.code.json` from the code, with the evidence for each decision | no |
|
|
28
|
+
| `grails-cli scan [root] [--out <file>]` | writes the component manifest Grails would receive | no |
|
|
29
|
+
| `grails-cli tokens [root] [--json]` | the design tokens a sync would upload (with `dtcg`: the files, or the exact upload with `--json`) | no |
|
|
30
|
+
| `grails-cli tokens baseline [root] --out <dir> [--in-repo]` | writes the scanner's tokens as DTCG files and a resolver, a first version to own | no |
|
|
31
|
+
| `grails-cli sync [root] [--dry-run] [--json]` | uploads the tokens, then the components | yes (with `--dry-run`: asks what would change, stores nothing) |
|
|
32
|
+
| `grails-cli tokens pending [root] [--json]` | the token changes people asked for in Grails, before → after | reads only |
|
|
33
|
+
| `grails-cli rules [root] [--set code=figma]` | the naming rules that match code tokens to Figma ones | edits need `write:tokens` |
|
|
34
|
+
| `grails-cli status` | the project's code sources and their last syncs | reads only |
|
|
35
|
+
| `grails-cli chi [root]` | the Component Health Index of every component | no |
|
|
36
|
+
| `grails-cli check-classes [root]` | Tailwind classes that are probably misspelt | no |
|
|
37
|
+
| `grails-cli skill` | prints this file | no |
|
|
38
|
+
|
|
39
|
+
**The API key** is read from `GRAILS_API_KEY` and from nowhere else. Never pass it as a flag, and never print the
|
|
40
|
+
environment. To create one, go to Team Settings › Integrations › API Keys in Grails and give it the `write:code`
|
|
41
|
+
scope. `GRAILS_API_URL` points the CLI at another API.
|
|
42
|
+
|
|
43
|
+
## `grails.code.json`
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"branch": "main",
|
|
48
|
+
"components": ["src/components/**/*.tsx"],
|
|
49
|
+
"exclude": ["**/*.stories.tsx"],
|
|
50
|
+
"dtcg": { "files": ["tokens/**/*.json"], "resolver": "tokens/resolver.json" }
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- **`branch`** is required for a real sync. It names the branch that IS this source in Grails: the one merges land
|
|
55
|
+
on. Run anywhere else, the sync uploads nothing and exits 0.
|
|
56
|
+
- **`components`** and **`exclude`** are globs, relative to the root.
|
|
57
|
+
- **`dtcg`** names your DTCG files. **When it is set, it is the only token source**: the scanner reads no token from
|
|
58
|
+
Tailwind or CSS, and `css` and `tokens` are ignored with a warning.
|
|
59
|
+
- `files` are globs of `.json` files.
|
|
60
|
+
- `resolver` is optional, and is a DTCG resolver document. Its `$ref`s name the files relative to its own folder,
|
|
61
|
+
so every file must sit in or below that folder.
|
|
62
|
+
- **`css`** (CSS files whose custom properties are tokens) and **`tokens`** (TypeScript token objects) are for the
|
|
63
|
+
scanner, when there is no `dtcg`.
|
|
64
|
+
|
|
65
|
+
## Tokens: two ways in
|
|
66
|
+
|
|
67
|
+
1. **DTCG files (the recommended way).** Generate them with your tool of choice: a Style Dictionary build,
|
|
68
|
+
Tokens Studio's DTCG export, Specify, your own script. Declare them in `dtcg`, and `sync` uploads them as they are.
|
|
69
|
+
Grails reads them and answers with precise findings when it cannot.
|
|
70
|
+
2. **The scanner (a convenience).** With no `dtcg`, the CLI reads a Tailwind 3 or 4 theme, the CSS custom properties
|
|
71
|
+
of the files in `css`, and the token objects in `tokens`. It is good for a first sync. Anything it does not
|
|
72
|
+
support should be exported to DTCG rather than worked around. To move to DTCG, run `tokens baseline --out <dir>`,
|
|
73
|
+
review the files, commit them and declare them in `dtcg`. From then on the files are yours and the scanner stops
|
|
74
|
+
being the source. The command lists the values the scanner left out (they are not in the files), refuses to run while
|
|
75
|
+
`dtcg` is set, and never writes into a folder that has files.
|
|
76
|
+
|
|
77
|
+
With DTCG, a sync records which components each component renders, but **not which tokens it uses**: nothing tells
|
|
78
|
+
Grails which DTCG token a style's `var(--x)` is. The sync says so in its output.
|
|
79
|
+
|
|
80
|
+
## Writing DTCG that Grails accepts
|
|
81
|
+
|
|
82
|
+
Grails reads the **DTCG format 2025.10** (designtokens.org) and validates every document against its schema.
|
|
83
|
+
|
|
84
|
+
- **A token's path is its dotted name, and the first segment is its collection.** `color.brand.500` belongs to the
|
|
85
|
+
collection `color`. A token directly at the root (one segment) is refused.
|
|
86
|
+
- **Types.** All 13 DTCG types are accepted: `color` (`{ "colorSpace", "components" }`), `dimension`
|
|
87
|
+
(`{ "value", "unit" }`), `number`, `fontFamily`, `fontWeight`, `duration`, `cubicBezier`, `strokeStyle`, `border`,
|
|
88
|
+
`transition`, `shadow` (one or a list, `inset` allowed), `gradient` and `typography`. A group's `$type` applies to
|
|
89
|
+
the tokens inside it.
|
|
90
|
+
- **Aliases** are `{path}` references, at most 64 deep, with no cycles. A JSON Pointer `$ref` inside a value is
|
|
91
|
+
refused: use `{path}`.
|
|
92
|
+
- **Modes.**
|
|
93
|
+
- With no resolver, each collection has one mode, `default`.
|
|
94
|
+
- With a resolver, its sets give every mode the same value, and a modifier's contexts become the collection's modes.
|
|
95
|
+
- Two or more modifiers give one mode per combination, named with `·` (`light·brand-a`), at most 50.
|
|
96
|
+
- A resolver's `$ref` names only a file of the same upload (or `#/sets/x`, `#/modifiers/x`). Nothing is fetched.
|
|
97
|
+
- **Limits.**
|
|
98
|
+
- At most 500 files and 4 MiB in one upload.
|
|
99
|
+
- At most 20,000 values, 500 collections, 20 modifiers, 50 contexts per modifier, and groups 32 levels deep.
|
|
100
|
+
- The CLI checks the file count and the size before sending anything.
|
|
101
|
+
- **Refused outright:**
|
|
102
|
+
- Tokens Studio's legacy format (`$themes`, `$metadata`, or `value` / `type` without `$`): export as DTCG (W3C).
|
|
103
|
+
- A `boolean` or object `$value` with no `$type`.
|
|
104
|
+
|
|
105
|
+
**What DTCG cannot express.** The token stays valid DTCG, with a `$value` any tool can read, and the extra goes in
|
|
106
|
+
`$extensions` under a `design.grails.*` key:
|
|
107
|
+
|
|
108
|
+
- **Typography.** Use `design.grails.typography.extraProps`: `textCase`, `textDecoration`, `fontStyle`,
|
|
109
|
+
`paragraphSpacing`, `paragraphIndent`, `listSpacing`, `leadingTrim`.
|
|
110
|
+
- **Booleans, copy strings, blurs and layout grids.** Use no `$type`, a readable string `$value` (`"true"`,
|
|
111
|
+
`"blur(4px)"`, `"12 columns, 24px gutter"`), and the real value in
|
|
112
|
+
`design.grails.value: { "type": "boolean" | "string" | "blur" | "grid", "value": … }`.
|
|
113
|
+
|
|
114
|
+
Grails keeps these extensions with each token. Interpreting them is still being built, so write them now, and nothing
|
|
115
|
+
is lost when it lands.
|
|
116
|
+
|
|
117
|
+
When a sync is refused, each finding names the file, the JSON pointer inside it and a code (`schema_invalid`,
|
|
118
|
+
`token_at_root`, `legacy_format`, `alias_unknown`, `alias_cycle`, …). Fix the source and sync again.
|
|
119
|
+
|
|
120
|
+
## Changes asked for in Grails
|
|
121
|
+
|
|
122
|
+
Admins and editors can ask for token changes in Grails: a new value, a new token, collection or mode, or a removal.
|
|
123
|
+
`grails-cli tokens pending` lists them by collection, as `before → after` in DTCG. Carry each one into your token
|
|
124
|
+
source, or into the rules that generate it.
|
|
125
|
+
|
|
126
|
+
⚠️ For now, a change you carried over still shows as pending in Grails. Marking it applied when a sync shows the
|
|
127
|
+
repository says the same is designed but not built yet.
|
|
128
|
+
|
|
129
|
+
## In CI
|
|
130
|
+
|
|
131
|
+
The package ships `templates/grails-cli-sync.yml`, a GitHub Actions workflow that runs `npx --yes grails-cli sync --trigger ci`
|
|
132
|
+
on a push to the declared branch.
|
|
133
|
+
|
|
134
|
+
- Store the key as the repository secret `GRAILS_API_KEY`. It reaches the step only through `env:`, never inside a
|
|
135
|
+
`run:` line.
|
|
136
|
+
- Do not run it on `pull_request`.
|
|
137
|
+
- The run writes a job summary, and the step output `result` is `synced`, `preview`, `offline`, `skipped-branch` or
|
|
138
|
+
`failed`.
|
|
139
|
+
|
|
140
|
+
**Exit codes:**
|
|
141
|
+
|
|
142
|
+
| code | meaning |
|
|
143
|
+
|---|---|
|
|
144
|
+
| 0 | done (synced, unchanged, or a dry run) |
|
|
145
|
+
| 1 | the sync ended incomplete |
|
|
146
|
+
| 2 | misconfigured: a flag, `grails.code.json` (no `branch`, a DTCG file that is not JSON or matches nothing), or the key |
|
|
147
|
+
| 3 | refused by the server: the key, the plan, or the content (DTCG findings included) |
|
|
148
|
+
| 4 | the server could not be reached or failed |
|
|
149
|
+
|
|
150
|
+
Only 4 is worth retrying.
|