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 ADDED
@@ -0,0 +1,250 @@
1
+ # Grails CLI: terms of use
2
+
3
+ Copyright (c) 2026 Grails Services LLC. All rights reserved.
4
+
5
+ `grails-cli` (the "Software") is proprietary software. It is not open source.
6
+
7
+ ## What you may do
8
+
9
+ You may install the Software and run it, free of charge, to work with Grails (https://grails.design): to read your
10
+ own code and send its design tokens and component specs to your own Grails account, from your computer or from your
11
+ CI. Using Grails itself is covered by the Grails Terms of Service (https://grails.design/terms).
12
+
13
+ ## What you may not do
14
+
15
+ No other right is granted. Without the written permission of Grails Services LLC you may not:
16
+
17
+ - copy the Software, except as installing it requires;
18
+ - modify it, or make anything derived from it;
19
+ - redistribute, sublicense, sell, rent or host it;
20
+ - use it for anything other than working with Grails.
21
+
22
+ ## No warranty
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
25
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL GRAILS SERVICES LLC BE LIABLE
26
+ FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR ITS USE.
27
+
28
+ ## Third-party software
29
+
30
+ The Grails terms above do not apply to the open-source packages that `dist/cli.js` includes. Each is used under its
31
+ own license, reproduced below as that license requires. (`typescript` is not included: it is installed next to the
32
+ Software as a dependency, under its own license.) The CLI also includes the Design Tokens Community Group's JSON
33
+ Schemas for the format and the resolver, version 2025.10, published at https://www.designtokens.org.
34
+
35
+ ### ajv 8.20.0 (MIT)
36
+
37
+ The MIT License (MIT)
38
+
39
+ Copyright (c) 2015-2021 Evgeny Poberezkin
40
+
41
+ Permission is hereby granted, free of charge, to any person obtaining a copy
42
+ of this software and associated documentation files (the "Software"), to deal
43
+ in the Software without restriction, including without limitation the rights
44
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
45
+ copies of the Software, and to permit persons to whom the Software is
46
+ furnished to do so, subject to the following conditions:
47
+
48
+ The above copyright notice and this permission notice shall be included in all
49
+ copies or substantial portions of the Software.
50
+
51
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
52
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
53
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
54
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
55
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
56
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
57
+ SOFTWARE.
58
+
59
+ ### ajv-formats 3.0.1 (MIT)
60
+
61
+ MIT License
62
+
63
+ Copyright (c) 2020 Evgeny Poberezkin
64
+
65
+ Permission is hereby granted, free of charge, to any person obtaining a copy
66
+ of this software and associated documentation files (the "Software"), to deal
67
+ in the Software without restriction, including without limitation the rights
68
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
69
+ copies of the Software, and to permit persons to whom the Software is
70
+ furnished to do so, subject to the following conditions:
71
+
72
+ The above copyright notice and this permission notice shall be included in all
73
+ copies or substantial portions of the Software.
74
+
75
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
76
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
77
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
78
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
79
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
80
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
81
+ SOFTWARE.
82
+
83
+ ### colorjs.io 0.7.1 (MIT)
84
+
85
+ MIT License
86
+
87
+ Copyright (c) 2021 Lea Verou, Chris Lilley
88
+
89
+ Permission is hereby granted, free of charge, to any person obtaining a copy
90
+ of this software and associated documentation files (the "Software"), to deal
91
+ in the Software without restriction, including without limitation the rights
92
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
93
+ copies of the Software, and to permit persons to whom the Software is
94
+ furnished to do so, subject to the following conditions:
95
+
96
+ The above copyright notice and this permission notice shall be included in all
97
+ copies or substantial portions of the Software.
98
+
99
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
100
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
101
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
102
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
103
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
104
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
105
+ SOFTWARE.
106
+
107
+ ### fast-deep-equal 3.1.3 (MIT)
108
+
109
+ MIT License
110
+
111
+ Copyright (c) 2017 Evgeny Poberezkin
112
+
113
+ Permission is hereby granted, free of charge, to any person obtaining a copy
114
+ of this software and associated documentation files (the "Software"), to deal
115
+ in the Software without restriction, including without limitation the rights
116
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
117
+ copies of the Software, and to permit persons to whom the Software is
118
+ furnished to do so, subject to the following conditions:
119
+
120
+ The above copyright notice and this permission notice shall be included in all
121
+ copies or substantial portions of the Software.
122
+
123
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
124
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
125
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
126
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
127
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
128
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
129
+ SOFTWARE.
130
+
131
+ ### fast-uri 3.1.8 (BSD-3-Clause)
132
+
133
+ Copyright (c) 2011-2021, Gary Court until https://github.com/garycourt/uri-js/commit/a1acf730b4bba3f1097c9f52e7d9d3aba8cdcaae
134
+ Copyright (c) 2021-present The Fastify team <https://github.com/fastify/fastify#team>
135
+ All rights reserved.
136
+
137
+ Redistribution and use in source and binary forms, with or without
138
+ modification, are permitted provided that the following conditions are met:
139
+ * Redistributions of source code must retain the above copyright
140
+ notice, this list of conditions and the following disclaimer.
141
+ * Redistributions in binary form must reproduce the above copyright
142
+ notice, this list of conditions and the following disclaimer in the
143
+ documentation and/or other materials provided with the distribution.
144
+ * The names of any contributors may not be used to endorse or promote
145
+ products derived from this software without specific prior written
146
+ permission.
147
+
148
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
149
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
150
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
151
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
152
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
153
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
154
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
155
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
156
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
157
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
158
+
159
+ * * *
160
+
161
+ The complete list of contributors can be found at:
162
+ - https://github.com/garycourt/uri-js/graphs/contributors
163
+
164
+ ### json-schema-traverse 1.0.0 (MIT)
165
+
166
+ MIT License
167
+
168
+ Copyright (c) 2017 Evgeny Poberezkin
169
+
170
+ Permission is hereby granted, free of charge, to any person obtaining a copy
171
+ of this software and associated documentation files (the "Software"), to deal
172
+ in the Software without restriction, including without limitation the rights
173
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
174
+ copies of the Software, and to permit persons to whom the Software is
175
+ furnished to do so, subject to the following conditions:
176
+
177
+ The above copyright notice and this permission notice shall be included in all
178
+ copies or substantial portions of the Software.
179
+
180
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
181
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
182
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
183
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
184
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
185
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
186
+ SOFTWARE.
187
+
188
+ ### nanoid 3.3.20 (MIT)
189
+
190
+ The MIT License (MIT)
191
+
192
+ Copyright 2017 Andrey Sitnik <andrey@sitnik.ru>
193
+
194
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
195
+ this software and associated documentation files (the "Software"), to deal in
196
+ the Software without restriction, including without limitation the rights to
197
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
198
+ the Software, and to permit persons to whom the Software is furnished to do so,
199
+ subject to the following conditions:
200
+
201
+ The above copyright notice and this permission notice shall be included in all
202
+ copies or substantial portions of the Software.
203
+
204
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
205
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
206
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
207
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
208
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
209
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
210
+
211
+ ### picocolors 1.1.1 (ISC)
212
+
213
+ ISC License
214
+
215
+ Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov, Anton Verinov
216
+
217
+ Permission to use, copy, modify, and/or distribute this software for any
218
+ purpose with or without fee is hereby granted, provided that the above
219
+ copyright notice and this permission notice appear in all copies.
220
+
221
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
222
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
223
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
224
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
225
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
226
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
227
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
228
+
229
+ ### postcss 8.5.28 (MIT)
230
+
231
+ The MIT License (MIT)
232
+
233
+ Copyright 2013 Andrey Sitnik <andrey@sitnik.es>
234
+
235
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
236
+ this software and associated documentation files (the "Software"), to deal in
237
+ the Software without restriction, including without limitation the rights to
238
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
239
+ the Software, and to permit persons to whom the Software is furnished to do so,
240
+ subject to the following conditions:
241
+
242
+ The above copyright notice and this permission notice shall be included in all
243
+ copies or substantial portions of the Software.
244
+
245
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
246
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
247
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
248
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
249
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
250
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,280 @@
1
- # Temporary Holding Version
1
+ # grails-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Grails Code Sync from the command line: `grails-cli` reads a component library's code and uploads its design
4
+ tokens and each component's spec to [Grails](https://grails.design), so the vault shows what the code actually
5
+ holds, next to Figma. The product is Grails Code Sync the way the Figma plugin is Grails Sync; this is its
6
+ command line. Full documentation: https://docs.grails.design/cli/overview/
7
+
8
+ **Requirements:** Node 20 or newer. The package is free to install and use with Grails; see `LICENSE.md`.
9
+
10
+ ```bash
11
+ npm i -D grails-cli # or run it without installing: npx grails-cli <command>
12
+
13
+ npx grails-cli scan /path/to/library # the manifest of components
14
+ npx grails-cli tokens /path/to/library # the design tokens Grails would read; sends nothing
15
+ npx grails-cli sync /path/to/library --dry-run # what would be uploaded; sends nothing, needs no key
16
+
17
+ export GRAILS_API_KEY=grails_sk_… # see "The key"
18
+ npx grails-cli sync /path/to/library
19
+ npx grails-cli status
20
+ ```
21
+
22
+ The examples below write `grails-cli`; use `npx grails-cli` if it is not installed globally.
23
+
24
+ ## Commands
25
+
26
+ | Command | What it does |
27
+ |---|---|
28
+ | `scan [root] [--out f] [--profile f] [--tier syntax\|typed] [--in-repo]` | The manifest (stdout without `--out`). Refuses to write it inside a git repository unless `--in-repo`. Exits 1 when the scan is incomplete. |
29
+ | `chi [root] [--profile f] [--tier …] [--json]` | The Component Health Index of every component, worst first. |
30
+ | `check-classes [root] [--unknown] [--json]` | Probable Tailwind typos; exits 1 when there is one. |
31
+ | `init [root] [--write] [--force]` | Proposes a `grails.code.json` from the code, with the evidence. |
32
+ | `tokens [root] [--profile f] [--json]` | The design tokens Grails would read (a Tailwind theme, CSS listed in the profile's `css`, token sources in its `tokens`), and every value left out. `--json` is the exact token list a sync uploads. Sends nothing, needs no key. |
33
+ | `tokens baseline [root] --out <dir> [--in-repo] [--profile f]` | The scanner's tokens as DTCG files and a resolver, a first DTCG version for the team to own (see "Moving from the scanner to DTCG"). Writes only where `--out` says, refuses a path inside a git repository unless `--in-repo`, and never touches a folder that has files. Sends nothing, needs no key. |
34
+ | `sync [root] [--dry-run] [--json] [--trigger ci\|local] [--profile f] [--tier …] [--name n] [--platform p]` | **Tokens first, then components, in one step.** Records the tokens in one request, then opens a component sync, sends the components, finalizes, and prints the result and the vault link. See "Exit codes". |
35
+ | `status` | The code sources of the key's project, with the last component sync and the last token sync of each. |
36
+ | `tokens pending [root] [--name n] [--limit 1..1000] [--json]` | The token changes people asked for in Grails, by collection, before → after. Writes nothing (see "Changes asked for in Grails"). |
37
+ | `rules [root] [--set code=figma \| --delete code \| --threshold n] [--json]` | The naming rules of the Code ↔ Figma match (see "Naming rules"). |
38
+ | `skill` | Prints the agent skill the package ships (`templates/skill/grails-cli/SKILL.md`), for a team to save where its coding agent reads skills. Sends nothing, writes nothing. |
39
+
40
+ ### `sync`
41
+
42
+ **Only from the declared branch.** `grails.code.json` says which branch IS the
43
+ source in Grails: `"branch": "main"` (or `preview`, or whatever merges land on); `init` proposes the branch the
44
+ checkout is on. A real sync:
45
+ - **without a declared branch** is refused before anything is read (exit 2), with that sentence;
46
+ - **on another branch** uploads nothing and exits **0**, so a pull request's job stays green, and says so where a CI
47
+ shows it: the terminal, the job summary, a `::notice` on the run, and the step outputs `result=skipped-branch` and
48
+ `exit_code=0` (`GITHUB_OUTPUT`). A detached checkout is judged by `CI_COMMIT_REF_NAME` / `GITHUB_REF_NAME`, so a
49
+ GitHub pull request run (`<number>/merge`) is "another branch"; a folder that is not a checkout has no branch;
50
+ - **on the declared branch** syncs as below, and writes `result=synced` (or `failed`, with its exit code).
51
+ A dry run is never held back: it stores nothing. The server enforces the version that carries this guard: the two
52
+ storing routes refuse a CLI older than 0.2.0 with 400 `GRAILS_CODE_CLI_OUTDATED` (exit 3).
53
+
54
+ **Tokens go first.** The token list is read and
55
+ recorded with `POST /v1/code/token-syncs` in one request. Grails builds the DTCG document from it and answers what
56
+ changed against the source's previous snapshot; the same content is stored once. A token step that fails (a
57
+ project Grails cannot read, the server refusing the list) does **not** stop the components: they sync anyway, and
58
+ the run exits with the token step's code. Then the components:
59
+
60
+ 1. **Opens** the sync with every component key the scan found (`declared_keys`). That is how the server tells a
61
+ component deleted from the code from one that failed to arrive.
62
+ 2. **Sends** the components in requests of at most 50 components and 1 MB (the API refuses more than 50, and
63
+ accepts 2 MB). Grails answers the open with the hash of the spec it already holds for each key; the spec
64
+ travels **only** for a key that is new or whose hash differs.
65
+ 3. **Finalizes** with the scan's own `complete`. Only a complete scan lets the server mark anything deleted, and
66
+ even then it refuses to delete 5 or more components and more than 20 % of the source in one sync.
67
+ Since 0.3.0 the finalize also carries **what each component uses** (canonical entities D1): the components of
68
+ the same source it renders, resolved with the TypeScript checker to their keys, and the tokens its styles
69
+ reference, as the collection and name of the token list just recorded. Grails keeps them as edges of the
70
+ project graph. They are sent only when the tokens were read and recorded (or there are none): otherwise Grails
71
+ keeps the usage it had. A reference no token of the source answers to (a framework default, a source missing
72
+ from `grails.code.json`) is counted in a warning and not sent.
73
+
74
+ If a sync fails after it was opened, the CLI closes it as incomplete before raising the error. Otherwise it stays
75
+ open for two hours and the next run of the same source (a CI retry) is answered "another sync is running".
76
+
77
+ The **source** (what Grails calls this library) is named after the package's own `name`, so it is the same on a
78
+ laptop and in CI; `--name` sets another one, for a monorepo whose packages share nothing else. The platform is
79
+ `react-native` when the package depends on React Native or Expo or any file imports `react-native`, and `web`
80
+ otherwise; `--platform` overrides. The commit and branch come from git when the root is in a checkout (the branch
81
+ from `CI_COMMIT_REF_NAME` or `GITHUB_REF_NAME` when the checkout is detached, as GitLab's is). `--trigger` is `ci`
82
+ when `CI=true` and `local` otherwise.
83
+
84
+ ### `--dry-run`, `--json` and the job summary
85
+
86
+ - **`--dry-run` with `GRAILS_API_KEY` set** asks Grails what each step would change, through the preview routes
87
+ (`POST /v1/code/token-syncs:preview`, `POST /v1/code/syncs:preview`), which store nothing: tokens added, changed
88
+ and removed; components added, changed, reactivated and **those that would be deleted**, with the same floor the
89
+ real sync applies.
90
+ - **`--dry-run` without the key** counts locally and sends nothing, as it always did.
91
+ - **`--json`** prints one report on stdout (`mode`, `tokens`, `components`, `exit_code`); the human lines go to
92
+ stderr, so the output can be piped.
93
+ - **In GitHub Actions** (`GITHUB_STEP_SUMMARY` is set) the same report is appended to the job summary: counts,
94
+ and the changed tokens and would-be-deleted components folded. There is no comment on a pull request.
95
+
96
+ No state is written to the repository: what Grails holds is the only source of truth, and the comparison is
97
+ always against it.
98
+
99
+ ### Code ↔ Figma
100
+
101
+ After a real sync records the tokens, `sync` asks Grails how they compare with the Figma ones
102
+ (`GET /v1/code/token-match`) and prints one line: same, differ, conflicts, by value only, only in code, only in
103
+ Figma, and the colour threshold it used (ΔE2000, 1.5 by default). The job summary adds the table, what differs,
104
+ the conflicts (code tokens that disagree about one design token, never resolved) and the **suggested** naming
105
+ rules, which are never applied: naming rules live in Grails, not in this repository.
106
+
107
+ - A project with no Figma snapshot yet says so instead of calling every token missing.
108
+ - A dry run does not ask: Grails would compare the code tokens it already stores, not the ones on disk.
109
+ - It is a report, not a step of the sync: a failure is said and kept in the report, and the exit code does not change.
110
+
111
+ ### Naming rules
112
+
113
+ A naming rule tells the match that a word of your code is another word in Figma: `gray` in the code is `neutral`
114
+ in Figma. Rules belong to one source and live in Grails, never in the repository: the CLI,
115
+ the REST API and MCP all edit the same rules.
116
+
117
+ grails-cli rules # this source's rules and the project's colour threshold
118
+ grails-cli rules --set gray=neutral # create, or move its target; the same rule twice is one rule
119
+ grails-cli rules --delete gray
120
+ grails-cli rules --threshold 2 # ΔE2000 under which two colours are equal, for the whole project
121
+
122
+ The source is named as `sync` names it (`--name`, else the package name, else the folder). Listing needs the
123
+ usual key; editing needs a key with the `write:tokens` scope. The job summary of a sync lists the rules applied
124
+ and the ones Grails suggests from tokens with the same value and one different word.
125
+
126
+ ### Changes asked for in Grails
127
+
128
+ In Grails, an admin or an editor can ask for changes to this repository's tokens: a new value, a new token,
129
+ collection or mode, a removal. **Grails never writes your repository, and neither does this CLI.**
130
+
131
+ grails-cli tokens pending # by collection, before → after, as DTCG values
132
+ grails-cli tokens pending --json # the list as Grails answers it, every value exact
133
+ grails-cli tokens pending --limit 1000 # the read answers 500 by default, at most 1000
134
+
135
+ Carry each change into your token source, or into the rules that generate it (a Style Dictionary build, a Tokens
136
+ Studio export, your CSS). ⚠️ **A change carried over still shows as pending for now:** marking it applied when a sync
137
+ shows the repository says the same is designed but not live yet (it comes with the token comparison in the sync).
138
+
139
+ An sRGB colour shows as its hex and a dimension as `24px`; anything else shows as its DTCG JSON, and `--json` has the
140
+ exact components. The source is named as `sync` names it (`--name`, else the package name). It needs the usual key (`write:code`).
141
+
142
+ ### Exit codes
143
+
144
+ A contract a CI step can branch on. Only 4 is worth retrying. A sync on another branch is 0; its own
145
+ result, `skipped-branch`, is in the step output `result` (see `sync` above), since any other code would turn the step
146
+ red.
147
+
148
+ | code | meaning | retry? |
149
+ |---|---|---|
150
+ | 0 | done: synced, nothing changed, or a dry run | – |
151
+ | 1 | the component sync ran and ended incomplete (unreadable components, or deletions held back) | no: read the warnings |
152
+ | 2 | misconfigured: a flag, `grails.code.json` (including no declared `branch` on a real sync), no key, or a project Grails cannot read (its own tailwindcss missing, or of a major other than 3 and 4) | no: fix it |
153
+ | 3 | the server refused it: the key, the plan, content it does not accept, or a CLI older than the minimum (400) | no: fix it |
154
+ | 4 | the server could not be reached or failed (network, 5xx, a 429 after one retry) | yes |
155
+
156
+ ### Where the tokens come from
157
+
158
+ **The team's own DTCG files, when `grails.code.json` names them** (the official way in):
159
+
160
+ "dtcg": { "files": ["tokens/**/*.json"], "resolver": "tokens/resolver.json" }
161
+
162
+ - **One source.** With `dtcg`, these files are the only token source. Nothing is read from Tailwind, CSS or `tokens`,
163
+ and a run that also lists `css` or `tokens` says so.
164
+ - **Sent as written.** `sync` uploads them as they are (`dtcg: { files, resolver? }` with `client: grails-cli`), and
165
+ the API reads them with `@grails/dtcg`'s `readDtcg`.
166
+ - **Checked here first**, so a wrong repository sends nothing (exit 2):
167
+ - the file count (500) and the body size (4 MiB);
168
+ - a file outside the resolver's folder (a resolver names its files from where it is, so every key is relative to it);
169
+ - two keys that differ only in case;
170
+ - JSON that does not parse.
171
+ - **What the API refuses comes back as findings** (422, or 413 for a limit). The run prints each one with the file the
172
+ person knows and its JSON pointer, and exits 3.
173
+ - **Usage.** Component uses still go with the finalize. Token uses do not: nothing tells which DTCG token a style's
174
+ `var(--x)` is, and guessing it from the spelling would be a heuristic, so the CLI does not. The run says so.
175
+
176
+ **Without `dtcg`, the scanner reads** (a convenience for a first sync):
177
+
178
+ - **A Tailwind 3 theme**, read with the **project's own** `tailwindcss` (`loadConfig` + `resolveConfig`), defaults
179
+ included. ⚠️ That executes `tailwind.config.*`, as the project's build does. Install the project's dependencies
180
+ first (`npm ci`): without them the run exits 2, and never reads the theme with a copy of its own.
181
+ - **Tailwind 4**: the `@theme` stylesheet, read with the project's `tailwindcss` (`__unstable__loadDesignSystem`),
182
+ defaults included; its CSS variables become a collection with a mode per selector (`:root` light, `.dark` dark).
183
+ - **CSS files** listed in `grails.code.json`'s `css`, read with postcss.
184
+ - **TypeScript token objects and enums** listed in `grails.code.json`'s `tokens`.
185
+
186
+ `calc()` is computed (DTCG has no expressions). Values it cannot express are left out and listed, never guessed.
187
+
188
+ **Text styles** are tokens too (`typography`, one per style, all five fields): a Tailwind 4 `--text-X` with its
189
+ `--text-X--line-height`, `--letter-spacing` and `--font-weight`; a Tailwind 3 `theme.fontSize` entry (`[size]`,
190
+ `[size, lineHeight]` or `[size, { lineHeight, letterSpacing, fontWeight }]`); or a TypeScript object with a
191
+ `fontSize` and another typography key. Each field says where it comes from: the style's own value, a token it
192
+ inherits (the family of `fontFamily.sans`), a CSS rule that is not a token (`body { font-family }`), or CSS's initial
193
+ value (weight 400, letter spacing 0). An `em` letter spacing is converted against the size and keeps its spelling. A
194
+ family written as a `next/font` variable becomes the font the layout names, marked as a heuristic. A style that
195
+ cannot be formed (no family to inherit, a size that is not a length) is left out and listed.
196
+
197
+ #### Moving from the scanner to DTCG: `tokens baseline`
198
+
199
+ grails-cli tokens baseline --out ../acme-tokens # outside the repository: nothing else to say
200
+ grails-cli tokens baseline --out tokens --in-repo # inside it: only because you asked
201
+
202
+ The scanner's tokens, written as the DTCG files and the resolver Grails' own export writes (`exportProject` from
203
+ `@grails/dtcg`, through the same conversion the API runs, `fromCodeUpload`). It is the team's first DTCG version
204
+ : review the files, commit them, and declare them in `dtcg`. From then on the team owns its
205
+ generation rules and the scanner stops being the source. The command prints the `dtcg` block to
206
+ add and lists the values left out, which are not in the files: the ones the scanner could not express (the list
207
+ `tokens` prints, with the reason for each) and what the DTCG export could not carry.
208
+
209
+ - **It writes only where `--out` says.** A folder inside the git repository also needs `--in-repo`, exactly like
210
+ `scan --out`. A folder that already has files is refused: a baseline never overwrites anything.
211
+ - **It refuses while `dtcg` is set.** Those files are the token source, and a baseline from the scanner would compete
212
+ with them (exit 2).
213
+ - **A round trip pins it:** what `readDtcg` reads back from the files equals
214
+ `snapshotProject` of the scanner's upload, a text style included: its five fields and where each comes from.
215
+
216
+ ## The key
217
+
218
+ `GRAILS_API_KEY` is read from the environment **and from nowhere else**. A flag would land in the shell history,
219
+ in the process list and in a CI log that prints the command line, so the CLI refuses any flag that looks like a
220
+ credential (`--api-key`, `--token`, …) before it reads anything. It goes in one header, is never printed, and is
221
+ scrubbed from everything the CLI prints in case an upstream message echoes it.
222
+
223
+ Create it in **Team Settings › Integrations › API Keys** with the `write:code` scope. It is a personal key: if its
224
+ owner is demoted to viewer or leaves the team the key is **deleted**, and the API answers it like a key that never
225
+ existed (`GRAILS_AUTH_INVALID_TOKEN`). The CLI says so in those words.
226
+
227
+ `GRAILS_API_URL` points the CLI at another API (default `https://api.grails.design`; the docs' `…/v1` form is
228
+ accepted). `http://` is accepted only for this machine, and a redirect is never followed, so the key cannot be sent
229
+ to an address nobody chose.
230
+
231
+ One retry on a network error, and one on a `429` after the `retry_after_seconds` the API gives. The Worker counts
232
+ rate limits but does not block yet, so no real `429` exists today: that retry is the contract's, not a measured
233
+ behaviour. Opening a sync is retried only when the connection was *refused*: if the server may have received it, a
234
+ second open would be answered "another sync is running" and the first would hold the source for two hours.
235
+
236
+ ## What leaves the repository
237
+
238
+ An allow-list, built field by field (never a copy with fields deleted), enforced again by the
239
+ API and a last time by the database:
240
+
241
+ - the source: name, platform, package name, and the repository address **without userinfo, query or fragment**
242
+ (`https://gitlab-ci-token:<token>@gitlab.com/…` and `?private_token=…` are what a CI checkout's remote can look
243
+ like; an SSH remote becomes its `https://` address, or is dropped);
244
+ - git: the commit sha and the branch;
245
+ - per component: key (`<file>#<export>`), name, file path, export name, the spec hash and **the spec**, the
246
+ stories summary (files, titles, count, tags), the coverage numbers, `unresolved` as `{ what, line }` and other
247
+ props as `{ name, kind }`;
248
+ - at finalize, per component: the keys of the components of the same source it renders, and the collection and
249
+ name of each token its styles reference.
250
+
251
+ The spec is the component's public API: prop names, their values, types as declared, anatomy names and resolved
252
+ style values. It is what a design system documents. **Not** sent: source code, the CHI (the server computes it),
253
+ `unresolved[].detail` (it quotes a fragment of the code), `otherProps[].tsType`, the scan root, any absolute path.
254
+ `sync --dry-run` prints what would go, in counts and bytes, and sends nothing.
255
+
256
+ **Tokens:** token names, their declared type, a literal or an alias per mode, and the file each came from, relative
257
+ to the root. Not sent: file contents, comments, absolute paths, or any environment value a Tailwind config reads
258
+ outside its theme. `grails-cli tokens --json` prints exactly what would go.
259
+
260
+ **With `dtcg`, the files go whole.** The token half sends the DTCG files `dtcg.files` matches, and the resolver, as
261
+ they are written: every `$description`, `$extensions` and `$deprecated` in them goes too. Grails keeps the
262
+ descriptions and the extensions with each token. Their keys are paths relative to the resolver's folder (or to the
263
+ root), never absolute. Only the files the globs match leave; `node_modules` and dot folders are never walked.
264
+ `grails-cli tokens --json` prints the upload exactly.
265
+
266
+ ## The GitHub workflow
267
+
268
+ `templates/grails-cli-sync.yml` (in this package, and shown in Grails under Team Settings › Integrations) runs
269
+ `npx --yes grails-cli sync --trigger ci` on a push to the branch under `branches:` (`main` by default), which must
270
+ be the one `grails.code.json` declares. Copy it to `.github/workflows/grails-cli-sync.yml`. Its step has
271
+ `id: grails`, so a later step reads `steps.grails.outputs.result`.
272
+ The key reaches it through the step's `env:` from a repository secret named `GRAILS_API_KEY`, and never appears in a
273
+ `run:` line. Do not run it on `pull_request`: a fork's run has no secrets and no business syncing.
274
+
275
+ A new release of the CLI can change what a push uploads. To choose when that happens, pin a version in the
276
+ workflow: `npx --yes grails-cli@<version> sync --trigger ci`.
277
+
278
+ ## License
279
+
280
+ Proprietary. You may install and use `grails-cli` to work with Grails; no other right is granted. See `LICENSE.md`.