@campo-design/lint 0.0.0-stage → 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joshua Litchfield
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,295 @@
1
- # Temporary Holding Version
1
+ # @campo-design/lint
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
+ Campo's rules, as lint, for products built with Campo: a Stylelint config for
4
+ CSS, and an ESLint plugin for JavaScript, JSX and templates.
5
+
6
+ Campo's rules only hold where something checks them. Inside the Campo repo,
7
+ `pnpm check` and `pnpm lint` do. A product that installs Campo's packages
8
+ used to get the rules as documentation, with nothing checking them. This
9
+ package moves the checks to where product code is written: the editor, a
10
+ pre-commit hook, CI, and an agent's own session.
11
+
12
+ It holds no list of tokens, icons or components of its own. It reads them
13
+ from the Campo packages the product has installed (they're peer
14
+ dependencies), so a product's lint always matches its Campo version: a token
15
+ added in the next release is allowed as soon as the product upgrades, and one
16
+ that's removed fails.
17
+
18
+ ## Install
19
+
20
+ ```sh
21
+ npm install --save-dev @campo-design/lint stylelint eslint
22
+ ```
23
+
24
+ `@campo-design/tokens`, `@campo-design/icons`, `@campo-design/webawesome` and
25
+ `@awesome.me/webawesome` are peer dependencies: a product building with Campo
26
+ has them already. Install only the linter you use; both are optional peers.
27
+ It needs `@campo-design/tokens` and `@campo-design/webawesome` 0.3.0 or
28
+ later: the contract check (`checkUsage`) and the manifest's `useInstead`,
29
+ which names the token to use instead of a primitive, first shipped there.
30
+
31
+ Tested with Stylelint 17 and 18, and ESLint 9 and 10 (flat config), in a
32
+ product installed from the published packages. The peer ranges allow
33
+ Stylelint 16 and later; versions outside the tested ones may work, but
34
+ nothing has checked them.
35
+
36
+ ## Stylelint
37
+
38
+ ```js
39
+ // stylelint.config.js
40
+ export default {
41
+ extends: ['stylelint-config-recommended', '@campo-design/lint/stylelint'],
42
+ };
43
+ ```
44
+
45
+ Put `@campo-design/lint/stylelint` last in `extends`. A later config
46
+ overrides an earlier one rule by rule, so a config after Campo's could turn a
47
+ Campo rule off or loosen it without anyone noticing. Last, Campo's rules win;
48
+ a deliberate exception goes in your own `rules` or an `overrides` entry,
49
+ where it shows.
50
+
51
+ The config holds Campo's rules only. `stylelint-config-recommended` adds the
52
+ bug-catching ones (invalid properties, unknown units); it's the product's
53
+ choice. To lint the `<style>` blocks and `style=""` attributes of HTML pages
54
+ too, add [`postcss-html`](https://github.com/ota-meshi/postcss-html):
55
+
56
+ ```js
57
+ overrides: [{ files: ['**/*.html'], customSyntax: 'postcss-html' }],
58
+ ```
59
+
60
+ What the config turns on:
61
+
62
+ | Rule | Fails on |
63
+ | --- | --- |
64
+ | [`campo/semantic-tokens`](#camposemantic-tokens) | `var(--campo-color-neutral-900)`, a primitive, or `var(--campo-spacing-gap-medium)`, a token that doesn't exist |
65
+ | `color-no-hex`, `color-named`, `function-disallowed-list` | `#1a1a1a`, `white`, `oklch(…)` |
66
+ | `declaration-property-unit-disallowed-list` | `padding: 16px`, `transition-duration: 200ms`, for every property a token family exists for |
67
+ | [`campo/no-raw-through-custom-property`](#campono-raw-through-custom-property) | `--gap: 16px; gap: var(--gap)` |
68
+ | `declaration-property-value-disallowed-list` | `font-weight: 600` |
69
+ | `declaration-no-important` | `!important` |
70
+ | [`campo/no-wa-override`](#campono-wa-override) | `--wa-color-brand-fill-loud: …` |
71
+ | [`campo/no-color-scheme-query`](#campono-color-scheme-query) | `@media (prefers-color-scheme: dark)` |
72
+
73
+ It also requires a reason on every `stylelint-disable`, after `--`:
74
+ `/* stylelint-disable-next-line color-no-hex -- A specimen of the raw value. */`.
75
+
76
+ What stays allowed on purpose: layout values (`max-width: 65ch`, grid
77
+ templates, breakpoints), `em` values (they scale with the text), `0`, `%`,
78
+ keywords (`transparent`, `currentColor`, `inherit`), reading a `--wa-*` token,
79
+ and a local custom property for a size two rules share.
80
+
81
+ ## ESLint
82
+
83
+ ```js
84
+ // eslint.config.js
85
+ import campo from '@campo-design/lint/eslint';
86
+
87
+ export default [
88
+ { files: ['**/*.{js,jsx}'], ...campo.configs.recommended },
89
+ ];
90
+ ```
91
+
92
+ Put Campo's entry after the others, for the same reason as in Stylelint: in
93
+ a flat config, a later entry overrides an earlier one for the files both
94
+ match, so Campo's rules stay on unless you turn one off where it shows.
95
+
96
+ JavaScript holds Campo's rules in its strings: CSS in template literals and
97
+ style objects, markup in lit's `html` templates, JSX or `innerHTML`, and import
98
+ paths. The rules read those. For JSX, turn it on in the parser
99
+ (`languageOptions.parserOptions.ecmaFeatures.jsx`); for TypeScript, use
100
+ [typescript-eslint](https://typescript-eslint.io)'s parser. The rules are the
101
+ same.
102
+
103
+ | Rule | Fails on |
104
+ | --- | --- |
105
+ | [`campo/semantic-tokens`](#camposemantic-tokens) | A primitive or an invented `--campo-*` token in a string, template or style object |
106
+ | [`campo/no-raw-values`](#campono-raw-values) | `style={{ padding: 8 }}`, `el.style.gap = '16px'`, ``css`.x { color: #333; }` `` |
107
+ | [`campo/no-important`](#campono-important) | `!important` in CSS written in script |
108
+ | [`campo/no-wa-override`](#campono-wa-override) | `setProperty('--wa-…')`, `{ '--wa-…': … }`, `--wa-…:` in a template |
109
+ | [`campo/no-color-scheme-query`](#campono-color-scheme-query) | `matchMedia('(prefers-color-scheme: dark)')` |
110
+ | [`campo/phosphor-icons`](#campophosphor-icons) | `<wa-icon name="circle-check">` or `<WaIcon name="circle-check">`, a Font Awesome name |
111
+ | [`campo/contract-gaps`](#campocontract-gaps) | `<wa-callout variant="info">`, or a combination the contract lists as a gap, on `<wa-*>` or on React's `<WaButton>` |
112
+ | [`campo/no-webawesome-stylesheet`](#campono-webawesome-stylesheet) | `import '@awesome.me/webawesome/dist/styles/webawesome.css'` |
113
+ | [`campo/no-autoloader`](#campono-autoloader) | `import '@awesome.me/webawesome'` or the autoloader |
114
+ | [`campo/no-palette-class`](#campono-palette-class) | `wa-palette-*` and `wa-brand-*` classes |
115
+
116
+ ## The rules
117
+
118
+ Each rule is a mistake that renders fine today and breaks at the next theme
119
+ change or redesign. `AGENTS.md`'s *Building with Campo* explains each one,
120
+ with its reason.
121
+
122
+ ### `campo/semantic-tokens`
123
+
124
+ Stylelint and ESLint. A `var(--campo-*)` must be a semantic token. A
125
+ primitive reaches past the tier, so a theme or redesign doesn't reach it; the
126
+ message names the semantic tokens that point at it, so you can pick by role.
127
+ A name Campo doesn't define looks right and resolves to nothing: check it in
128
+ `@campo-design/tokens/manifest.json`.
129
+
130
+ ### `campo/no-raw-through-custom-property`
131
+
132
+ Stylelint. The unit rule stops `gap: 16px` but can't see
133
+ `--gap: 16px; gap: var(--gap)`, because a custom property has no type. This
134
+ rule follows a local custom property declared in the same file to where it's
135
+ used, and reports a raw unit that lands in a property a token owns. A local
136
+ custom property for a size is fine where it lands in `inline-size` or `top`.
137
+
138
+ ### `campo/no-raw-values`
139
+
140
+ ESLint. Raw colors, `px`/`rem` lengths, `ms`/`s` durations and numeric
141
+ weights in style objects (`style={{ }}`, `Object.assign(el.style, { })`,
142
+ `el.style.x = …`) and in CSS written in a string or template. A bare number
143
+ in a style object counts as `px` where React renders it as one
144
+ (`padding: 8`), not where it's unitless (`lineHeight: 1.5`). A string is only
145
+ read as CSS when it holds a whole declaration (`gap: 16px;`) or an inline
146
+ style, so `'Time: 5s'` isn't flagged.
147
+
148
+ ### `campo/no-important`
149
+
150
+ ESLint. `!important` in CSS written in script, the counterpart of
151
+ Stylelint's `declaration-no-important`. Campo's CSS is in layers, so product
152
+ CSS wins without it.
153
+
154
+ ### `campo/no-wa-override`
155
+
156
+ Stylelint and ESLint. Declaring or setting a `--wa-*` token. Campo's bridge
157
+ maps every one to a Campo token; an override anywhere else bypasses Campo and
158
+ breaks at the next theme change. Get as close as the component's variants and
159
+ appearances allow, then name the gap as a Campo token decision. Reading a
160
+ `--wa-*` token is fine.
161
+
162
+ ### `campo/no-color-scheme-query`
163
+
164
+ Stylelint and ESLint. `prefers-color-scheme`, in a media query or in script.
165
+ Campo's themes repoint the semantic tokens, so a screen built from them is
166
+ right in both; a query adds a second, unchecked version of the theme.
167
+
168
+ ### `campo/phosphor-icons`
169
+
170
+ ESLint. `<wa-icon name>` takes a [Phosphor](https://phosphoricons.com) name.
171
+ Web Awesome's docs use Font Awesome names, and one of those draws nothing,
172
+ with no error. Common ones that differ: `check-circle` (not `circle-check`),
173
+ `x-circle` (not `circle-xmark`), `warning` (not `triangle-exclamation`),
174
+ `info` (not `circle-info`). In React, the same goes for `<WaIcon name>` from
175
+ `@campo-design/react/icon`, under whatever name it's imported as.
176
+
177
+ ### `campo/contract-gaps`
178
+
179
+ ESLint. A Web Awesome component used with a combination its Campo contract
180
+ lists as a gap (it renders in stock Web Awesome colours, not Campo's), or a
181
+ value the component doesn't have, such as `variant="info"`. The message says
182
+ what to use instead. A value only known at run time (`variant=${v}`,
183
+ `variant={v}`) is skipped, not guessed, with every bridge release from lint's
184
+ floor on, including those before 0.4.1, whose own check read `${v}` as a
185
+ value. The contracts are in
186
+ `@campo-design/webawesome/contracts`.
187
+
188
+ In React, a component imported from `@campo-design/react` is read as the
189
+ element it renders: `<WaButton variant="danger" appearance="accent">` is
190
+ checked like `<wa-button variant="danger" appearance="accent">`, and the
191
+ message names it as you wrote it (`<WaButton> (<wa-button>) … is a gap`).
192
+ That's also true under a renamed import (`WaButton as Button`), and for
193
+ components imported from Web Awesome's own React wrappers
194
+ (`@awesome.me/webawesome/dist/react/<name>`). A component of your own that
195
+ happens to be called `WaButton` isn't checked.
196
+
197
+ `@campo-design/react`'s types already reject a gap in TypeScript. This rule
198
+ is the same check for code the types don't stop: plain JavaScript, a line
199
+ silenced with `// @ts-expect-error` or `// @ts-ignore`, and Web Awesome's
200
+ wrappers. Both read the same contracts, so they can't disagree. A value cast
201
+ inside an expression (`variant={'danger' as any}`) is a run-time value to
202
+ this rule, so it isn't checked.
203
+
204
+ ### `campo/no-webawesome-stylesheet`
205
+
206
+ ESLint. Web Awesome's `webawesome.css`. Import
207
+ `@campo-design/webawesome/theme.css`, and `@campo-design/css` for native
208
+ elements.
209
+
210
+ ### `campo/no-autoloader`
211
+
212
+ ESLint. Web Awesome's autoloader, or the package's root entry, which starts
213
+ it. Import each component you use from
214
+ `@awesome.me/webawesome/dist/components/<name>/<name>.js`.
215
+
216
+ ### `campo/no-palette-class`
217
+
218
+ ESLint. `wa-palette-*` and `wa-brand-*` classes. Campo decides color through
219
+ its tokens; `<html>` takes `wa-theme-default campo-wa` only.
220
+
221
+ ## Setup across a product
222
+
223
+ ```sh
224
+ npx campo-lint setup # the current folder
225
+ npx campo-lint setup src public # or the folders you name
226
+ npx campo-lint setup --json # for tools and agents
227
+ ```
228
+
229
+ Campo's setup is spread over a product's files: the theme classes on
230
+ `<html>` in a page, `@campo-design/webawesome/theme.css` in a stylesheet or
231
+ script, `connectColorScheme()` and `registerCampoIcons()` (or, in React,
232
+ `setupCampo()`) in a script, and an import for each Web Awesome component
233
+ used. A linter reads one file at a time, so it can't see a step that's
234
+ missing from all of them. `campo-lint setup` reads them together. It
235
+ reports each problem at its file and line, each missing step with what to
236
+ add, and exits 1 when it finds any, so it can run in CI next to lint:
237
+
238
+ ```
239
+ index.html:3 <wa-icon> (setupImport)
240
+ Import it from @awesome.me/webawesome/dist/components/<name>/<name>.js (in React, @campo-design/react/<name>).
241
+ Missing from the product (2 files checked):
242
+ @campo-design/webawesome/theme.css (setupThemeCss)
243
+ Import @campo-design/webawesome/theme.css once, in the product's entry stylesheet or script.
244
+ ```
245
+
246
+ It reads stylesheets, pages, templates and scripts (`.css`, `.scss`,
247
+ `.html`, `.njk`, `.md`, `.mdx`, `.js`, `.jsx`, `.ts`, `.tsx`, `.vue`,
248
+ `.svelte`, `.astro`), skipping `node_modules`, build output (`dist`, `build`,
249
+ `out`, `_site`) and hidden folders. Code shown as text, in `<code>`, `<pre>`
250
+ or a Markdown code block, is skipped, so a page that teaches the setup can
251
+ quote it. A product that uses no Web Awesome has nothing to check.
252
+
253
+ | Rule | Fails on |
254
+ | --- | --- |
255
+ | `setupThemeClass` | `<html>` without both `wa-theme-default` and `campo-wa` |
256
+ | `setupThemeCss` | `@campo-design/webawesome/theme.css` never imported |
257
+ | `setupColorScheme` | `connectColorScheme()` never called (or `setupCampo()` in React) |
258
+ | `setupIcons` | `registerCampoIcons()` never called (or `setupCampo()` in React) |
259
+ | `setupImport` | a `<wa-*>` element used but never imported |
260
+ | `setupStylesheet` | Web Awesome's `webawesome.css` imported |
261
+ | `setupAutoloader` | the autoloader, or the package's root entry, which starts it |
262
+ | `setupPalette` | a `wa-palette-*` or `wa-brand-*` class |
263
+ | `setupReactImport` | a component from Web Awesome's own React wrappers |
264
+
265
+ ## What it doesn't check
266
+
267
+ - **Setup, in the editor.** The Stylelint and ESLint rules can't see a step
268
+ missing from every file; `campo-lint setup` (above) checks that, as a
269
+ command rather than in the editor.
270
+ - **Picking a token by its role.** `gap.related` against `gap.flow`, or
271
+ `text.subtle` against `text.disabled`, is judgment. The token
272
+ `$description`s in the manifest are what to read.
273
+ - **Whether a recipe fits.** Run `npx @campo-design/recipes find "<what
274
+ you're building>"`.
275
+
276
+ ## Exceptions
277
+
278
+ When an exception is deliberate, say why at the place it's made:
279
+ `/* stylelint-disable-next-line <rule> -- <reason> */`, or
280
+ `// eslint-disable-next-line campo/<rule> -- <reason>`. If you need the
281
+ exception because Campo has no token or variant for what you're building,
282
+ that's a gap for Campo to decide; check *Not mapped yet* in
283
+ `@campo-design/webawesome`'s README, then raise it.
284
+
285
+ ## In the Campo repo
286
+
287
+ Campo lints itself with this package: the root `stylelint.config.mjs`
288
+ extends `@campo-design/lint/stylelint`, and `eslint.config.mjs` applies the
289
+ recommended ESLint config to the docs' and site's browser scripts. Campo's
290
+ scoring (`scripts/token-score.mjs`, behind `pnpm metrics`, the bench grader
291
+ and the docs and site check) reads the same definitions from
292
+ `@campo-design/lint/core`. So a rule changes in one place, `src/core.js`, and
293
+ means the same thing in a product's lint, Campo's own checks and the bench.
294
+
295
+ Tests: `pnpm --filter @campo-design/lint test`.
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ // campo-lint: checks for a whole product, beside the Stylelint config and
3
+ // ESLint plugin, which read one file at a time.
4
+ //
5
+ // npx campo-lint setup [folder…] Campo's fixed setup across the product
6
+ // npx campo-lint setup --json the same, as JSON
7
+ //
8
+ // It exits 1 when it finds a problem, so it can gate CI.
9
+ import { readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
10
+ import { join, relative, resolve } from 'node:path';
11
+ import { pathToFileURL } from 'node:url';
12
+ import { SETUP_FIXES, setupFindings } from '../src/setup.js';
13
+
14
+ // Files Campo's setup can appear in: stylesheets, pages, templates, scripts.
15
+ const EXTENSIONS = /\.(css|scss|html|njk|md|mdx|js|mjs|cjs|jsx|ts|tsx|vue|svelte|astro)$/;
16
+ // Dependencies and build output: not the product's own source.
17
+ const SKIP = new Set(['node_modules', 'dist', 'build', 'out', 'coverage', '_site', 'storybook-static']);
18
+
19
+ const USAGE = `Usage: campo-lint setup [folder…] [--json]
20
+
21
+ Checks Campo's fixed setup across a product's files: the theme classes on
22
+ <html>, @campo-design/webawesome/theme.css, connectColorScheme() and
23
+ registerCampoIcons() (or setupCampo() in React), an import for each Web
24
+ Awesome component used, and none of webawesome.css, the autoloader, palette
25
+ classes or Web Awesome's own React wrappers. Folders default to the current
26
+ one; node_modules, build output and hidden folders are skipped.`;
27
+
28
+ /** Every source file under the given paths. */
29
+ export function productFiles(paths) {
30
+ const walk = (path) => {
31
+ if (!statSync(path).isDirectory()) return EXTENSIONS.test(path) ? [path] : [];
32
+ return readdirSync(path)
33
+ .filter((name) => !name.startsWith('.') && !SKIP.has(name))
34
+ .flatMap((name) => walk(join(path, name)));
35
+ };
36
+ return paths.flatMap((p) => walk(resolve(p)));
37
+ }
38
+
39
+ /** The report a person reads: problems in files first, then missing steps. */
40
+ export function formatReport(findings, fileCount, base = process.cwd()) {
41
+ if (!findings.length) return `No setup problems (${fileCount} files checked).`;
42
+ const lines = [];
43
+ const inFiles = findings.filter((f) => f.file);
44
+ const missing = findings.filter((f) => !f.file);
45
+ for (const f of inFiles) lines.push(`${relative(base, f.file)}:${f.line} ${f.detail} (${f.rule})\n ${SETUP_FIXES[f.rule]}`);
46
+ if (missing.length) {
47
+ lines.push(`Missing from the product (${fileCount} files checked):`);
48
+ for (const f of missing) lines.push(` ${f.detail} (${f.rule})\n ${SETUP_FIXES[f.rule]}`);
49
+ }
50
+ lines.push(`\n${findings.length} setup problem${findings.length === 1 ? '' : 's'}. The setup is under "Use" in @campo-design/webawesome's README.`);
51
+ return lines.join('\n');
52
+ }
53
+
54
+ // Run as a command, directly or through npx's link in node_modules/.bin;
55
+ // not when a test imports productFiles or formatReport.
56
+ const invoked = process.argv[1] && pathToFileURL(realpathSync(process.argv[1])).href === import.meta.url;
57
+ const [command, ...rest] = process.argv.slice(2);
58
+ if (invoked) {
59
+ if (command !== 'setup' || rest.includes('--help')) {
60
+ console.log(USAGE);
61
+ process.exit(command === 'setup' || command === '--help' || !command ? 0 : 2);
62
+ }
63
+ const json = rest.includes('--json');
64
+ const folders = rest.filter((a) => !a.startsWith('--'));
65
+ const files = productFiles(folders.length ? folders : ['.']);
66
+ const findings = setupFindings(files.map((file) => ({ file, text: readFileSync(file, 'utf8') })));
67
+ if (json) {
68
+ const base = process.cwd();
69
+ console.log(JSON.stringify({ files: files.length, findings: findings.map((f) => ({ ...f, file: f.file && relative(base, f.file), fix: SETUP_FIXES[f.rule] })) }, null, 2));
70
+ } else {
71
+ console.log(formatReport(findings, files.length));
72
+ }
73
+ process.exit(findings.length ? 1 : 0);
74
+ }
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@campo-design/lint",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Campo's lint rules for products: a Stylelint config and an ESLint plugin that fail on raw values, primitives, invented tokens, Web Awesome overrides, hand-rolled dark mode, non-Phosphor icons and contract gaps.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "campo-lint": "./bin/campo-lint.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "src"
13
+ ],
14
+ "exports": {
15
+ "./stylelint": "./src/stylelint/index.js",
16
+ "./stylelint/plugin": "./src/stylelint/plugin.js",
17
+ "./eslint": "./src/eslint/index.js",
18
+ "./core": "./src/core.js",
19
+ "./setup": "./src/setup.js"
20
+ },
21
+ "sideEffects": false,
22
+ "peerDependencies": {
23
+ "@awesome.me/webawesome": "^3.14.0",
24
+ "@campo-design/icons": ">=0.1.0 <1.0.0",
25
+ "@campo-design/tokens": ">=0.3.0 <1.0.0",
26
+ "@campo-design/webawesome": ">=0.3.0 <1.0.0",
27
+ "eslint": ">=9.0.0",
28
+ "stylelint": ">=16.0.0"
29
+ },
30
+ "peerDependenciesMeta": {
31
+ "eslint": {
32
+ "optional": true
33
+ },
34
+ "stylelint": {
35
+ "optional": true
36
+ }
37
+ },
38
+ "devDependencies": {
39
+ "@awesome.me/webawesome": "3.14.0",
40
+ "eslint": "^10.14.0",
41
+ "stylelint": "^17.16.0",
42
+ "@campo-design/icons": "^0.1.0",
43
+ "@campo-design/tokens": "^0.3.1",
44
+ "@campo-design/webawesome": "^0.4.1"
45
+ },
46
+ "scripts": {
47
+ "test": "node --test test/*.test.js"
48
+ }
6
49
  }