grails-cli 0.3.0 → 0.4.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/README.md +166 -20
- package/dist/cli.js +6427 -3898
- package/package.json +1 -1
- package/templates/skill/grails-cli/SKILL.md +201 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grails-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
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
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://docs.grails.design/cli/overview/",
|
|
@@ -29,16 +29,32 @@ to choose when a new release reaches your pushes.
|
|
|
29
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
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
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 |
|
|
32
|
+
| `grails-cli tokens pending [root] [--collection <name>] [--request <id>] [--json]` | the token changes people asked for in Grails: type, before → after, and the id; the filters narrow it | reads only |
|
|
33
|
+
| `grails-cli tokens discard <change-id> \| --all [--collection <name>] \| --request <id> --reason "<why>" [--dry-run]` | closes one change, or a set in one step, that your team will not carry, with the reason | yes (Grails keeps the changes, closed); `--dry-run` changes nothing |
|
|
33
34
|
| `grails-cli rules [root] [--set code=figma]` | the naming rules that match code tokens to Figma ones | edits need `write:tokens` |
|
|
34
|
-
| `grails-cli
|
|
35
|
+
| `grails-cli connect [--prompt]` | keeps the API key in the operating system's keychain (checked against Grails first) so no command needs `GRAILS_API_KEY`; reads the clipboard and clears it, or with `--prompt` asks in a hidden prompt | reads only (one authenticated read to validate the key) |
|
|
36
|
+
| `grails-cli disconnect` | removes the key `connect` stored (the key itself is not revoked) | no |
|
|
37
|
+
| `grails-cli update [root]` | asks the npm registry now for the latest `grails-cli` and prints the command that updates it for the project's package manager; it does not run it | asks the npm registry only |
|
|
38
|
+
| `grails-cli status` | the project's code sources, their last syncs, and how many changes asked for in Grails wait for each | reads only |
|
|
35
39
|
| `grails-cli chi [root]` | the Component Health Index of every component | no |
|
|
36
40
|
| `grails-cli check-classes [root]` | Tailwind classes that are probably misspelt | no |
|
|
37
41
|
| `grails-cli skill` | prints this file | no |
|
|
38
42
|
|
|
39
|
-
**The API key** is read from `GRAILS_API_KEY`
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
**The API key** is read from `GRAILS_API_KEY` in the environment (CI, and anyone who prefers it) and, when that is not
|
|
44
|
+
set, from the operating system's keychain, where `grails-cli connect` put it; and from nowhere else. Never pass it as a
|
|
45
|
+
flag or an argument, and never print the environment. To create one, go to Team Settings › Integrations › API Keys in
|
|
46
|
+
Grails and give it the `write:code` scope. `GRAILS_API_URL` points the CLI at another API; the keychain keeps one key
|
|
47
|
+
per API, by its host.
|
|
48
|
+
|
|
49
|
+
**An agent does not run `connect` for the person.** `connect` takes the key from the person's clipboard or from a hidden
|
|
50
|
+
prompt on their terminal; a key an agent was handed in a chat is already in a transcript, so it does not go to
|
|
51
|
+
`connect --prompt` either. When there is no key, tell the person to run `grails-cli connect` in their own terminal (or to set
|
|
52
|
+
`GRAILS_API_KEY`), and carry on with the commands that need none (`init`, `scan`, `tokens`, `sync --dry-run`).
|
|
53
|
+
|
|
54
|
+
**Updates.** Once a day the CLI asks the npm registry for the latest version and, while it is behind, every run ends with
|
|
55
|
+
one line on stderr naming the two versions and the update command. That line is not an error and does not change the
|
|
56
|
+
exit code. `GRAILS_CLI_NO_UPDATE_CHECK=1` turns it off. `grails-cli update` prints the command and never runs it: update
|
|
57
|
+
the version only when the person asks.
|
|
42
58
|
|
|
43
59
|
## `grails.code.json`
|
|
44
60
|
|
|
@@ -59,6 +75,9 @@ scope. `GRAILS_API_URL` points the CLI at another API.
|
|
|
59
75
|
- `files` are globs of `.json` files.
|
|
60
76
|
- `resolver` is optional, and is a DTCG resolver document. Its `$ref`s name the files relative to its own folder,
|
|
61
77
|
so every file must sit in or below that folder.
|
|
78
|
+
- `cssVariables` is optional: how your CSS names the tokens, so Grails can tell which token a `var(--x)` is. It holds
|
|
79
|
+
the `prefix` of your build, the `exceptions` to the naming, and `ignore`, patterns for custom properties that are
|
|
80
|
+
not your tokens. See "Telling Grails which token a `var(--x)` is".
|
|
62
81
|
- **`css`** (CSS files whose custom properties are tokens) and **`tokens`** (TypeScript token objects) are for the
|
|
63
82
|
scanner, when there is no `dtcg`.
|
|
64
83
|
|
|
@@ -74,8 +93,119 @@ scope. `GRAILS_API_URL` points the CLI at another API.
|
|
|
74
93
|
being the source. The command lists the values the scanner left out (they are not in the files), refuses to run while
|
|
75
94
|
`dtcg` is set, and never writes into a folder that has files.
|
|
76
95
|
|
|
77
|
-
With DTCG, a sync records which components each component renders,
|
|
78
|
-
|
|
96
|
+
With DTCG, a sync records which components each component renders, and which tokens it uses **only through a `var(--x)`
|
|
97
|
+
that `cssVariables` ties to a token** (next section). Every `var(--x)` it cannot tie is listed, so you can fix it.
|
|
98
|
+
|
|
99
|
+
## Telling Grails which token a `var(--x)` is
|
|
100
|
+
|
|
101
|
+
*This section is written for a coding agent that is asked to set it up. Follow the steps in order, and never guess a
|
|
102
|
+
name.*
|
|
103
|
+
|
|
104
|
+
Your components write `var(--color-brand-500)`; your DTCG files say `color.brand.500`. With `dtcg` declared, the scanner
|
|
105
|
+
reads no token, so the only thing that ties the two is how your build names a token's custom property. Grails assumes
|
|
106
|
+
**Style Dictionary's default**, the `css` transform group (`name/kebab`):
|
|
107
|
+
|
|
108
|
+
- the token's path segments, joined with `-`, lower case, behind two dashes: `color.brand.500` is `--color-brand-500`;
|
|
109
|
+
- with the build's `prefix` first: `prefix: "ds"` makes it `--ds-color-brand-500`;
|
|
110
|
+
- camelCase is split (`fontSize` → `font-size`, `h1Bold` → `h1-bold`), but **a digit stays with its letters** (`2xl`
|
|
111
|
+
stays `2xl`, `body2` stays `body2`);
|
|
112
|
+
- anything that is not a letter or a digit is a separator (`Brand Primary`, `brand_primary` and `brand-primary` all give
|
|
113
|
+
`brand-primary`);
|
|
114
|
+
- the name is **case-sensitive**: `var(--Color-Brand-500)` is no token, because the build writes lower case.
|
|
115
|
+
|
|
116
|
+
Where a name does not follow that, you write an **exception**: the custom property and the token it is. A custom
|
|
117
|
+
property that is **not your token at all** (a library's, the framework's) goes in **`ignore`**, as a pattern.
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"dtcg": {
|
|
122
|
+
"files": ["tokens/**/*.json"],
|
|
123
|
+
"resolver": "tokens/resolver.json",
|
|
124
|
+
"cssVariables": {
|
|
125
|
+
"prefix": "ds",
|
|
126
|
+
"exceptions": { "--brand": "color.brand.500", "--surface": "color.semantic.surface" },
|
|
127
|
+
"ignore": ["--radix-*", "--tw-*"]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
All three keys are optional. An exception's value is the token's dotted path in the files (the first segment is its
|
|
134
|
+
collection). **An exception wins over the convention**, and one that names a path the files do not have is reported.
|
|
135
|
+
|
|
136
|
+
**`ignore` is a list of patterns**: a name as `var()` writes it, two dashes first, with `*` for the part that varies
|
|
137
|
+
(`*` is the only wildcard, and at most 200 patterns). A name an `ignore` pattern covers is not resolved, not counted,
|
|
138
|
+
not listed and never sent, **even when the convention would have found a token of that name**; an exception for the same
|
|
139
|
+
name wins over `ignore`. A pattern that covers nothing the components use is not an error: `--json` lists it in
|
|
140
|
+
`css_variables.ignore_unused`, and the terminal line says how many were ignored (`· 14 ignored`), never which.
|
|
141
|
+
|
|
142
|
+
**The steps:**
|
|
143
|
+
|
|
144
|
+
1. **Look at how the repository builds its CSS from the DTCG files.** Find the Style Dictionary configuration
|
|
145
|
+
(`config.json`, `style-dictionary.config.*`, `sd.config.*`, or a script that calls it) and read, for the CSS platform,
|
|
146
|
+
its `prefix`, its `transformGroup` and its `transforms`. Then open the CSS it generates and look at a few real names:
|
|
147
|
+
a name in the output beats a reading of the configuration.
|
|
148
|
+
- `transformGroup: "css"` (or `name/kebab` in `transforms`), and no custom `name` transform: the convention applies.
|
|
149
|
+
- `name/camel`, `name/snake`, `name/constant`, `name/pascal` or a custom `name` transform: the names do not follow
|
|
150
|
+
the convention, and each one a component uses needs an exception.
|
|
151
|
+
2. **Set the prefix**: `"cssVariables": { "prefix": "<the build's prefix>" }`, and leave it out when the build has none.
|
|
152
|
+
3. **Run `grails-cli sync --dry-run`.** It needs no key and sends nothing (with a key it also asks Grails what
|
|
153
|
+
would change). `--json` adds a `css_variables` object with the same list. The line `css variables` counts the custom
|
|
154
|
+
properties your components use and how many resolve to a token.
|
|
155
|
+
4. **Read the unresolved list** (the `!` lines under it), and decide each one:
|
|
156
|
+
- `var(--x) is no token of the DTCG files`: find where `--x` comes from, and choose by who owns it.
|
|
157
|
+
- **It is your token under another name**: generated from a DTCG token, or an alias of a generated one
|
|
158
|
+
(`--x: var(--ds-color-brand-500)`). Add it to **`exceptions`** with that token's path. An exception says what a
|
|
159
|
+
name IS.
|
|
160
|
+
- **It is not your token, and never will be**: a library's, the framework's, one the page sets at run time. Add a
|
|
161
|
+
pattern that covers the family to **`ignore`**, so the list holds only what a person still has to decide.
|
|
162
|
+
Examples that are common: Radix sets sizes for its popovers and menus
|
|
163
|
+
(`--radix-dropdown-menu-content-available-height`, `--radix-select-trigger-width`: pattern `--radix-*`);
|
|
164
|
+
Tailwind's own internals, which a component reads when it writes a ring or a shadow by hand
|
|
165
|
+
(`--tw-ring-color`, `--tw-ring-offset-color`: pattern `--tw-*`); a value the page sets from JavaScript (`--vh`,
|
|
166
|
+
`--scroll-y`). A custom property one component declares for itself (`--card-padding`) is also not a token: ignore
|
|
167
|
+
it, by name or by a prefix that component's team already uses.
|
|
168
|
+
- **You cannot tell which of the two it is**: do not ignore it and do not guess an exception. Leave it listed and
|
|
169
|
+
ask the person.
|
|
170
|
+
- `var(--x) is the name of N tokens`: two tokens spell the same CSS name (`brandPrimary` and `brand-primary`).
|
|
171
|
+
Read the generated CSS to see which one the build keeps (the last declaration wins), and add that one to
|
|
172
|
+
`exceptions`. Better, rename one of them in the DTCG files.
|
|
173
|
+
- `dtcg.cssVariables.exceptions: --x names color.y, which is not a token of the DTCG files`: the exception is
|
|
174
|
+
mistyped, or the token was renamed or removed. Fix its path (`grails-cli tokens --json` prints the files) or
|
|
175
|
+
delete the exception.
|
|
176
|
+
- `whose name has a "/"`: a level of that token's name contains a `/`, which a usage upload cannot spell. Rename the
|
|
177
|
+
level in the DTCG files.
|
|
178
|
+
5. **Run step 3 again** until every remaining `!` line is one you decided to leave for the person. Then tell them what
|
|
179
|
+
you added to `exceptions` and to `ignore` and what you left, with the reason for each.
|
|
180
|
+
6. **Prune** with `grails-cli sync --dry-run --json`: `css_variables.ignore_unused` lists the patterns that ignored
|
|
181
|
+
nothing (no component uses a name they cover, or an exception already names every such name). Delete those.
|
|
182
|
+
|
|
183
|
+
**Keep `ignore` narrow.** A pattern beats the naming convention, so `--*-bg` would hide a real token that happens to end
|
|
184
|
+
in `-bg`: the token would no longer be tied to the components that use it, and the line would not say so. Cover a family
|
|
185
|
+
by its owner's prefix (`--radix-*`), not by a shape (`--*-width`). If a name the pattern covers IS a token, add an
|
|
186
|
+
exception for it: that is the way to keep one inside a broad pattern.
|
|
187
|
+
|
|
188
|
+
**What this reads, and what it does not.** The scanner reads a `var(--x)` a component can use in three places:
|
|
189
|
+
|
|
190
|
+
- **the `style` of any element** in the component, every property (`margin`, `border`, `boxShadow`, …) and not only
|
|
191
|
+
colour, padding and size; also the props libraries use for styles (`contentContainerStyle`, `css`, `sx`), a constant or
|
|
192
|
+
style sheet the style refers to, and every branch of a condition or lookup;
|
|
193
|
+
- **the stylesheet the component's file imports** (`.css`, CSS modules, `.scss`, `.less`), for every component that file
|
|
194
|
+
holds. A custom property a rule *declares* in `:root`, a theme selector or `@theme` is a definition, not a use, so a
|
|
195
|
+
tokens sheet imported by a layout does not make the layout "use" every token. `@import` inside a stylesheet is not
|
|
196
|
+
followed;
|
|
197
|
+
- **Tailwind classes** in `className` and in `cn`, `clsx`, `classnames`, `cx`, `twMerge` and `cva` calls, variants
|
|
198
|
+
included: `bg-[var(--x)]` in either version, `bg-(--x)` in Tailwind 4, `bg-[--x]` in Tailwind 3. (Tailwind 4
|
|
199
|
+
compiles `bg-[--x]` to a literal `--x`, not a `var()`, so it is not read there.)
|
|
200
|
+
|
|
201
|
+
It does **not** read a name a template assembles at run time (`` `var(--space-${n})` ``), a class helper it does not know
|
|
202
|
+
(`tv()`), CSS-in-JS template literals (styled-components, Emotion's `css` tag), a component a file defines but does not
|
|
203
|
+
export, or a property set from JavaScript. So a custom property missing from the list may still be used. A reference
|
|
204
|
+
that is not a `var()` (a Tailwind theme class such as `bg-primary`, a TypeScript token object) is counted in a line ("N
|
|
205
|
+
token reference(s)… are not linked") but not tied to a token. A run never fails for any of this.
|
|
206
|
+
|
|
207
|
+
**Do not** edit the token files or the CSS to make a line disappear: an exception, or a pattern in `ignore`, is the
|
|
208
|
+
team's statement of what a name is, or that it is not theirs.
|
|
79
209
|
|
|
80
210
|
## Writing DTCG that Grails accepts
|
|
81
211
|
|
|
@@ -110,21 +240,78 @@ Grails reads the **DTCG format 2025.10** (designtokens.org) and validates every
|
|
|
110
240
|
- **Booleans, copy strings, blurs and layout grids.** Use no `$type`, a readable string `$value` (`"true"`,
|
|
111
241
|
`"blur(4px)"`, `"12 columns, 24px gutter"`), and the real value in
|
|
112
242
|
`design.grails.value: { "type": "boolean" | "string" | "blur" | "grid", "value": … }`.
|
|
243
|
+
- **A colour built from a colour and an opacity token.** Keep the applied colour in `$value`, and name the parts in
|
|
244
|
+
`design.grails.composed: { "color": "{path}" or a DTCG colour, "opacity": "{path}" }`. The opacity token is read
|
|
245
|
+
as it is: a Figma opacity is 0–100, so divide by 100.
|
|
113
246
|
|
|
114
|
-
Grails
|
|
115
|
-
|
|
247
|
+
**What Grails does with them.**
|
|
248
|
+
- It reads a token with `design.grails.value` as that type and that value. The extension wins over `$value`, so a
|
|
249
|
+
`$value` like `"{brand}"` there is text, not a reference.
|
|
250
|
+
- It validates the forms above. A malformed one (a boolean whose value is `"yes"`, a `textCase` of `"shout"`) refuses
|
|
251
|
+
the sync with `extension_invalid` and the pointer. A `{path}` inside them that names no token is `alias_unknown`.
|
|
252
|
+
- What it does not know (a key a form does not have, a value type it has never seen) is kept as written, with the
|
|
253
|
+
warning `extension_not_interpreted`, so a document a newer tool wrote loses nothing.
|
|
116
254
|
|
|
117
255
|
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
|
|
256
|
+
`token_at_root`, `legacy_format`, `alias_unknown`, `alias_cycle`, `extension_invalid`, …). Fix the source and sync
|
|
257
|
+
again.
|
|
119
258
|
|
|
120
259
|
## Changes asked for in Grails
|
|
121
260
|
|
|
122
261
|
Admins and editors can ask for token changes in Grails: a new value, a new token, collection or mode, or a removal.
|
|
123
|
-
|
|
124
|
-
|
|
262
|
+
**Grails never writes your repository, and neither does the CLI.** Closing the loop takes three steps, and the last one
|
|
263
|
+
is automatic:
|
|
264
|
+
|
|
265
|
+
1. `grails-cli tokens pending` lists the changes by collection: each with its **type**, `before → after` as DTCG, and
|
|
266
|
+
its **id**. The `before` is the value your repository holds now, resolved (an alias in your code shows as the colour
|
|
267
|
+
it resolves to). `--json` has every value exact.
|
|
268
|
+
2. Carry each change into your token source, or into the rules that generate it, and merge it as any other change.
|
|
269
|
+
3. The next `grails-cli sync` (in CI, on the declared branch) **marks the change applied** once your repository holds
|
|
270
|
+
its value, and prints how many. Grails compares the resolved value of the token in that mode, with the tolerance the
|
|
271
|
+
Diff uses for colours and sizes, so an alias that resolves to the asked value counts. A token you removed closes
|
|
272
|
+
its change; a created token, collection or mode closes when it exists.
|
|
273
|
+
|
|
274
|
+
To see what the next sync would do **before** it runs, use `grails-cli sync --dry-run` (with the key): it prints how many
|
|
275
|
+
changes it would mark applied, close because their token is gone, or leave open, with their ids, and stores nothing.
|
|
276
|
+
`grails-cli status` shows how many changes wait for each source.
|
|
277
|
+
|
|
278
|
+
**If the repository holds another value, the change is not closed.** Grails keeps proposing its value, checks again on
|
|
279
|
+
every sync, and records what your code holds: running `tokens pending` again after a sync shows the real "before".
|
|
280
|
+
|
|
281
|
+
**A change your team will not carry is closed with a reason, not ignored:**
|
|
282
|
+
|
|
283
|
+
```sh
|
|
284
|
+
grails-cli tokens discard <change-id> --reason "Handled in the design-tokens repo"
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
- The id is the one `tokens pending` prints beside the change. Never invent one.
|
|
288
|
+
- `--reason` is required: 1 to 1000 characters on one line. Say why, in words a designer reads: Grails keeps it with the
|
|
289
|
+
change, with who discarded it (the owner of the API key) and that it came from code.
|
|
290
|
+
- Discarding a collection takes its modes and tokens with it. One value of a token being created cannot be discarded
|
|
291
|
+
alone: discard the token.
|
|
292
|
+
- Exit 3 means Grails has no open change with that id for this source, or it was already applied or discarded. Do not
|
|
293
|
+
retry; list again with `tokens pending`.
|
|
294
|
+
- Do it only when the team decided not to carry the change. If it only has not been carried yet, leave it: the sync
|
|
295
|
+
closes it when it arrives.
|
|
296
|
+
|
|
297
|
+
**Many at once.** Exactly one of three picks the changes (the reason is required, as above):
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
grails-cli tokens discard --all --dry-run # look first: nothing is changed
|
|
301
|
+
grails-cli tokens discard --all --reason "Handled in the tokens repo" # everything waiting for this repository
|
|
302
|
+
grails-cli tokens discard --all --collection color --reason "…" # one collection's
|
|
303
|
+
grails-cli tokens discard --request <request-id> --reason "…" # one change request's
|
|
304
|
+
```
|
|
125
305
|
|
|
126
|
-
|
|
127
|
-
|
|
306
|
+
- `--dry-run` lists the changes that would be discarded (id, token, before → after) and changes nothing. Run it first
|
|
307
|
+
and read the list: there is no `--yes`, and a discard cannot be undone from the CLI.
|
|
308
|
+
- The set is closed all together or not at all, and at most 1000 at once. Past that the CLI says so and discards
|
|
309
|
+
nothing: narrow it with `--collection` or `--request`.
|
|
310
|
+
- `tokens pending --collection <name>` and `--request <id>` list exactly what the same words would discard. The request
|
|
311
|
+
id is printed under the heading of `tokens pending`.
|
|
312
|
+
- `--collection` goes with `--all` only. Naming a change id and `--all` together is refused.
|
|
313
|
+
- The answer says how many were discarded; `--json` has the ids. Exit 3 means a request or source Grails does not hold
|
|
314
|
+
(nothing was discarded) or a set over the cap.
|
|
128
315
|
|
|
129
316
|
## In CI
|
|
130
317
|
|