@kazee-ds/init 0.0.0-stage → 0.1.3
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 +32 -2
- package/bin/kazee-init.mjs +45 -0
- package/package.json +36 -3
- package/src/content.mjs +43 -0
- package/src/setup.mjs +353 -0
- package/templates/AGENTS.md +63 -0
- package/templates/consumer-lint.md +135 -0
package/README.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @kazee-ds/init
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Preview-first setup for an existing standalone React or Vue application. Public release 0.1.2 pins core packages at 0.4.0. Verify the available npm versions before installing. Earlier CLI releases targeted private GitLab.
|
|
4
|
+
|
|
5
|
+
From the design-system checkout, run:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
node packages/init/bin/kazee-init.mjs --dir /absolute/path/to/application
|
|
9
|
+
node packages/init/bin/kazee-init.mjs --dir /absolute/path/to/application --apply
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Supply `--framework react|vue` or `--package-manager npm|pnpm|yarn` when detection
|
|
13
|
+
is ambiguous or unavailable. Explicit choices do not override conflicting
|
|
14
|
+
lockfiles. Yarn means Classic only; modern Yarn requires the manual guide.
|
|
15
|
+
|
|
16
|
+
Init adds missing exact Kazee dependencies, merges a public npm scope mapping,
|
|
17
|
+
appends a framework-specific managed section to `AGENTS.md`, and generates
|
|
18
|
+
`kazee-setup.md`. It preserves unrelated configuration and rejects conflicting
|
|
19
|
+
versions, registry mappings, edited managed content, workspaces, Next, and Nuxt.
|
|
20
|
+
|
|
21
|
+
Preview never writes files. Apply preflights all four files and attempts to
|
|
22
|
+
restore changes after ordinary write failures. Multi-file writes are not
|
|
23
|
+
crash-atomic. Symlinked and hardlinked write targets are rejected.
|
|
24
|
+
|
|
25
|
+
Public installs need no read credentials. Init neither creates auth entries nor prints existing credentials; unrelated registry settings are preserved.
|
|
26
|
+
No installation, network requests, source edits, lockfile updates, Git operations,
|
|
27
|
+
or CI changes occur inside init.
|
|
28
|
+
|
|
29
|
+
Follow the generated guide to install dependencies, import CSS once, merge the
|
|
30
|
+
lint config, and run application checks. ESLint 10 and appropriate parsers remain
|
|
31
|
+
the application's responsibility. Bundled versions are not a latest-version query.
|
|
32
|
+
|
|
33
|
+
See `docs/consumer-init.md` in the repository for verification and limitations.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { createPlan, applyPlan, renderPlan } from '../src/setup.mjs';
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const { values } = parseArgs({
|
|
7
|
+
options: {
|
|
8
|
+
dir: { type: 'string' },
|
|
9
|
+
framework: { type: 'string' },
|
|
10
|
+
'package-manager': { type: 'string' },
|
|
11
|
+
apply: { type: 'boolean', default: false },
|
|
12
|
+
help: { type: 'boolean', short: 'h' },
|
|
13
|
+
},
|
|
14
|
+
strict: true,
|
|
15
|
+
allowPositionals: false,
|
|
16
|
+
});
|
|
17
|
+
if (values.help) {
|
|
18
|
+
console.log(
|
|
19
|
+
'kazee-init [--dir PATH] [--framework react|vue] [--package-manager npm|pnpm|yarn] [--apply]\nPreview is the default. Yarn Classic only. Private registry access is a prerequisite.',
|
|
20
|
+
);
|
|
21
|
+
} else {
|
|
22
|
+
const plan = createPlan({
|
|
23
|
+
dir: values.dir,
|
|
24
|
+
framework: values.framework,
|
|
25
|
+
packageManager: values['package-manager'],
|
|
26
|
+
});
|
|
27
|
+
console.log(renderPlan(plan));
|
|
28
|
+
if (plan.conflicts.length) process.exitCode = 1;
|
|
29
|
+
else if (values.apply) {
|
|
30
|
+
applyPlan(plan);
|
|
31
|
+
console.log(
|
|
32
|
+
'Applied. Dependency installation, global CSS, and ESLint/CI integration still require the steps above.',
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// Never echo argv, manifest values, registry values, or arbitrary parse errors.
|
|
38
|
+
const safe = error.code?.startsWith('ERR_PARSE_ARGS')
|
|
39
|
+
? 'Invalid arguments. Run kazee-init --help.'
|
|
40
|
+
: error instanceof Error && !error.code
|
|
41
|
+
? error.message
|
|
42
|
+
: 'Unable to inspect or write the target. Check directory access and file types.';
|
|
43
|
+
console.error(safe);
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kazee-ds/init",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Preview-first setup for Kazee Design System consumers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"kazee-init": "./bin/kazee-init.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"templates"
|
|
13
|
+
],
|
|
14
|
+
"license": "UNLICENSED",
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public",
|
|
17
|
+
"registry": "https://registry.npmjs.org/"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22.16.0"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"semver": "^7.8.5"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"publint": "^0.3.25"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://gitlab.com/kazee.indonesia/kazee-design-system.git",
|
|
31
|
+
"directory": "packages/init"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://kazee-design-system-023417.gitlab.io",
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "node --check bin/kazee-init.mjs && node --check src/setup.mjs && node --check src/content.mjs",
|
|
36
|
+
"test": "node --test test/*.test.mjs",
|
|
37
|
+
"lint:package": "publint --strict"
|
|
38
|
+
}
|
|
6
39
|
}
|
package/src/content.mjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
export const RELEASE = Object.freeze({ framework: '0.5.0', styles: '0.5.0', lint: '0.4.0' });
|
|
4
|
+
export const REGISTRY = 'https://registry.npmjs.org/';
|
|
5
|
+
const INIT_VERSION = JSON.parse(
|
|
6
|
+
readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
|
|
7
|
+
).version;
|
|
8
|
+
export const START = '<!-- kazee-ds:init:start -->';
|
|
9
|
+
export const END = '<!-- kazee-ds:init:end -->';
|
|
10
|
+
|
|
11
|
+
export function managedRules(framework) {
|
|
12
|
+
const template = readFileSync(
|
|
13
|
+
new URL('../templates/AGENTS.md', import.meta.url),
|
|
14
|
+
'utf8',
|
|
15
|
+
).replaceAll('\r\n', '\n');
|
|
16
|
+
const other = framework === 'react' ? 'Vue' : 'React';
|
|
17
|
+
const filtered = template.replace(new RegExp(`## ${other} API\\n[\\s\\S]*?(?=\\n## |$)`), '');
|
|
18
|
+
return `${START}\n<!-- Template revision: 2026-10-28; init ${INIT_VERSION}; ${framework} -->\n${filtered.trim()}\n${END}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function installCommand(manager, framework) {
|
|
22
|
+
const runtime = `@kazee-ds/${framework}@${RELEASE.framework} @kazee-ds/styles@${RELEASE.styles}`;
|
|
23
|
+
const lint = `@kazee-ds/eslint-config@${RELEASE.lint}`;
|
|
24
|
+
if (manager === 'npm')
|
|
25
|
+
return `npm install --save-exact ${runtime}\nnpm install --save-dev --save-exact ${lint}`;
|
|
26
|
+
if (manager === 'pnpm')
|
|
27
|
+
return `pnpm add --save-exact ${runtime}\npnpm add --save-dev --save-exact ${lint}`;
|
|
28
|
+
return `yarn add --exact ${runtime}\nyarn add --dev --exact ${lint}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function setupGuide(framework, manager) {
|
|
32
|
+
const lintGuide = readFileSync(
|
|
33
|
+
new URL('../templates/consumer-lint.md', import.meta.url),
|
|
34
|
+
'utf8',
|
|
35
|
+
).replaceAll('\r\n', '\n');
|
|
36
|
+
const heading =
|
|
37
|
+
framework === 'react'
|
|
38
|
+
? '### Complete React/TypeScript example'
|
|
39
|
+
: '### Complete Vue/TypeScript example';
|
|
40
|
+
const section = lintGuide.slice(lintGuide.indexOf(heading));
|
|
41
|
+
const example = section.match(/```js\n([\s\S]*?)```/)[1];
|
|
42
|
+
return `# Kazee setup — ${framework}\n\nGenerated by @kazee-ds/init ${INIT_VERSION}. These are supported pinned versions, not a latest-version query.\n\n## Install dependencies\n\nPackages are distributed on public npm. No read token or login is required. Confirm the pinned versions exist on npm before installation.\n\n\`\`\`sh\n${installCommand(manager, framework)}\n\`\`\`\n\nCommit the updated package manifest and the application's existing lockfile after installation. Init does not install dependencies or update lockfiles.\n\n${manager === 'yarn' ? 'Yarn guidance is limited to Yarn Classic. Init does not create modern Yarn .yarnrc.yml authentication settings.\n\n' : ''}## Manual step: import shared CSS\n\nAdd this once to the application's existing global entry point:\n\n\`\`\`js\nimport '@kazee-ds/styles/index.css';\n\`\`\`\n\nIt includes the tokens. Keep the application's existing stylesheet imports. This step has not been performed by init.\n\n## Manual step: enable lint\n\nUse ESLint 10 with the application's existing parsers. This example assumes typescript-eslint${framework === 'vue' ? ' and eslint-plugin-vue' : ''} are installed. Adapt source globs to the application, preserve existing checks, and include consumerConfig in the exported flat config after parser setup.\n\n\`\`\`js\n${example}\`\`\`\n\nInit has not modified ESLint config or CI. Run lint in the application's CI; installing the package alone does not activate its rules.\n\n## Optional MCP reference\n\nConnect the Kazee MCP server using the client-specific configuration in the MCP guide. Start with get_catalog_versions and pass the exact installed framework package version to docs/examples tools. Init has not edited MCP client configuration or enabled a server.\n\n## Verify\n\nRun the application's existing lint, typecheck, and build scripts. Check Button activation/disabled behavior, Select keyboard selection, and shared styling. Report missing scripts instead of claiming they passed.\n\n- [Public Storybook](https://kazee-design-system-023417.gitlab.io)\n- [Installation guide](https://gitlab.com/kazee.indonesia/kazee-design-system/-/blob/main/docs/installation.md)\n- [Consumer lint guide](https://gitlab.com/kazee.indonesia/kazee-design-system/-/blob/main/docs/consumer-lint.md)\n\nThe source repository may remain private; published npm packages and Storybook are public.\n`;
|
|
43
|
+
}
|
package/src/setup.mjs
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import {
|
|
2
|
+
lstatSync,
|
|
3
|
+
readFileSync,
|
|
4
|
+
writeFileSync,
|
|
5
|
+
unlinkSync,
|
|
6
|
+
realpathSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
openSync,
|
|
9
|
+
closeSync,
|
|
10
|
+
ftruncateSync,
|
|
11
|
+
fstatSync,
|
|
12
|
+
} from 'node:fs';
|
|
13
|
+
import { resolve, dirname, join } from 'node:path';
|
|
14
|
+
import semver from 'semver';
|
|
15
|
+
import {
|
|
16
|
+
RELEASE,
|
|
17
|
+
REGISTRY,
|
|
18
|
+
START,
|
|
19
|
+
END,
|
|
20
|
+
managedRules,
|
|
21
|
+
setupGuide,
|
|
22
|
+
installCommand,
|
|
23
|
+
} from './content.mjs';
|
|
24
|
+
|
|
25
|
+
const FILES = ['package.json', '.npmrc', 'AGENTS.md', 'kazee-setup.md'];
|
|
26
|
+
const SECTIONS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
27
|
+
const LOCKS = {
|
|
28
|
+
'package-lock.json': 'npm',
|
|
29
|
+
'npm-shrinkwrap.json': 'npm',
|
|
30
|
+
'pnpm-lock.yaml': 'pnpm',
|
|
31
|
+
'yarn.lock': 'yarn',
|
|
32
|
+
};
|
|
33
|
+
const newline = (text) => (text?.includes('\r\n') ? '\r\n' : '\n');
|
|
34
|
+
|
|
35
|
+
function readSafe(path) {
|
|
36
|
+
let stat;
|
|
37
|
+
try {
|
|
38
|
+
stat = lstatSync(path);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error.code === 'ENOENT') return null;
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink > 1) {
|
|
44
|
+
throw new Error(`Expected a regular, unlinked file: ${path}`);
|
|
45
|
+
}
|
|
46
|
+
const bytes = readFileSync(path);
|
|
47
|
+
if (bytes.includes(0) || !Buffer.from(bytes.toString('utf8')).equals(bytes)) {
|
|
48
|
+
throw new Error(`Expected UTF-8 text: ${path}`);
|
|
49
|
+
}
|
|
50
|
+
return bytes.toString('utf8');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function manifest(text) {
|
|
54
|
+
try {
|
|
55
|
+
const pkg = JSON.parse(text);
|
|
56
|
+
if (!pkg || typeof pkg !== 'object' || Array.isArray(pkg)) throw new Error();
|
|
57
|
+
for (const section of SECTIONS) {
|
|
58
|
+
if (
|
|
59
|
+
pkg[section] !== undefined &&
|
|
60
|
+
(!pkg[section] ||
|
|
61
|
+
typeof pkg[section] !== 'object' ||
|
|
62
|
+
Array.isArray(pkg[section]) ||
|
|
63
|
+
Object.values(pkg[section]).some((v) => typeof v !== 'string'))
|
|
64
|
+
)
|
|
65
|
+
throw new Error();
|
|
66
|
+
}
|
|
67
|
+
return pkg;
|
|
68
|
+
} catch {
|
|
69
|
+
throw new Error('package.json must be valid JSON with dependency maps of string versions.');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function checkTarget(dir, pkg) {
|
|
74
|
+
if (pkg.workspaces || existsSync(join(dir, 'pnpm-workspace.yaml')))
|
|
75
|
+
throw new Error('Workspace targets are not supported in init 0.1.0.');
|
|
76
|
+
for (let parent = dirname(dir); parent !== dirname(parent); parent = dirname(parent)) {
|
|
77
|
+
if (existsSync(join(parent, 'pnpm-workspace.yaml')))
|
|
78
|
+
throw new Error('Nested workspace targets are not supported.');
|
|
79
|
+
const parentFile = join(parent, 'package.json');
|
|
80
|
+
if (existsSync(parentFile)) {
|
|
81
|
+
try {
|
|
82
|
+
if (JSON.parse(readFileSync(parentFile, 'utf8')).workspaces)
|
|
83
|
+
throw new Error('Nested workspace targets are not supported.');
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error.message.includes('workspace targets')) throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const deps = Object.assign({}, ...SECTIONS.map((s) => pkg[s]));
|
|
90
|
+
if (deps.next || deps.nuxt || deps['@nuxt/kit'])
|
|
91
|
+
throw new Error(
|
|
92
|
+
'Next/Nuxt adapters are outside init 0.1.0; use the manual installation guide.',
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function detectFramework(pkg, choice) {
|
|
97
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
98
|
+
const found = ['react', 'vue'].filter((name) => deps[name]);
|
|
99
|
+
if (choice && !['react', 'vue'].includes(choice))
|
|
100
|
+
throw new Error('Unsupported framework; choose react or vue.');
|
|
101
|
+
if (choice && !found.includes(choice))
|
|
102
|
+
throw new Error('Selected framework must already be a direct dependency.');
|
|
103
|
+
if (!choice && found.length !== 1)
|
|
104
|
+
throw new Error('Select a framework explicitly with --framework react or vue.');
|
|
105
|
+
return choice || found[0];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function detectManager(dir, pkg, choice) {
|
|
109
|
+
if (choice && !['npm', 'pnpm', 'yarn'].includes(choice))
|
|
110
|
+
throw new Error('Unsupported package manager.');
|
|
111
|
+
const declared = pkg.packageManager?.match(/^(npm|pnpm|yarn)@(\d+)\./);
|
|
112
|
+
if (pkg.packageManager && !declared) throw new Error('Unsupported packageManager declaration.');
|
|
113
|
+
if (declared?.[1] === 'yarn' && declared[2] !== '1')
|
|
114
|
+
throw new Error('Only Yarn Classic is supported; use the manual guide for modern Yarn.');
|
|
115
|
+
if (existsSync(join(dir, '.yarnrc.yml')))
|
|
116
|
+
throw new Error('Modern Yarn configuration requires the manual installation guide.');
|
|
117
|
+
const found = [
|
|
118
|
+
...new Set(
|
|
119
|
+
Object.entries(LOCKS)
|
|
120
|
+
.filter(([file]) => existsSync(join(dir, file)))
|
|
121
|
+
.map(([, manager]) => manager),
|
|
122
|
+
),
|
|
123
|
+
];
|
|
124
|
+
const evidence = new Set([
|
|
125
|
+
...found,
|
|
126
|
+
...(declared ? [declared[1]] : []),
|
|
127
|
+
...(choice ? [choice] : []),
|
|
128
|
+
]);
|
|
129
|
+
if (evidence.size > 1)
|
|
130
|
+
throw new Error('Package manager conflict between selection, packageManager, or lockfiles.');
|
|
131
|
+
if (!evidence.size)
|
|
132
|
+
throw new Error(
|
|
133
|
+
'Select a package manager explicitly with --package-manager npm, pnpm, or yarn.',
|
|
134
|
+
);
|
|
135
|
+
if (
|
|
136
|
+
evidence.has('yarn') &&
|
|
137
|
+
!declared &&
|
|
138
|
+
(!existsSync(join(dir, 'yarn.lock')) ||
|
|
139
|
+
!readFileSync(join(dir, 'yarn.lock'), 'utf8').includes('# yarn lockfile v1'))
|
|
140
|
+
) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
'Cannot verify Yarn Classic from lockfile; declare packageManager yarn@1.x or use the manual guide.',
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return [...evidence][0];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function editRegistry(text, conflicts) {
|
|
149
|
+
let result = text || '';
|
|
150
|
+
const eol = newline(text);
|
|
151
|
+
for (const [key, value] of [['@kazee-ds:registry', REGISTRY]]) {
|
|
152
|
+
const values = result
|
|
153
|
+
.split(/\r?\n/)
|
|
154
|
+
.map((line) => line.trim())
|
|
155
|
+
.filter((line) => !line.startsWith('#') && !line.startsWith(';'))
|
|
156
|
+
.filter((line) => line.split('=')[0].trim() === key)
|
|
157
|
+
.map((line) => line.slice(line.indexOf('=') + 1).trim());
|
|
158
|
+
if (values.length > 1 || values.some((v) => v !== value))
|
|
159
|
+
conflicts.push(`.npmrc: conflicting setting for ${key} (value redacted).`);
|
|
160
|
+
else if (!values.length)
|
|
161
|
+
result += `${result && !result.endsWith('\n') ? eol : ''}${key}=${value}${eol}`;
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function editRules(text, framework, conflicts) {
|
|
167
|
+
const original = text || '';
|
|
168
|
+
const eol = newline(text);
|
|
169
|
+
const block = managedRules(framework).replaceAll('\n', eol);
|
|
170
|
+
const starts = original.split(START).length - 1;
|
|
171
|
+
const ends = original.split(END).length - 1;
|
|
172
|
+
if (!starts && !ends)
|
|
173
|
+
return `${original}${original ? `${original.endsWith('\n') ? '' : eol}${eol}` : ''}${block}${eol}`;
|
|
174
|
+
const begin = original.indexOf(START);
|
|
175
|
+
const finish = original.indexOf(END);
|
|
176
|
+
if (
|
|
177
|
+
starts !== 1 ||
|
|
178
|
+
ends !== 1 ||
|
|
179
|
+
finish < begin ||
|
|
180
|
+
original.slice(begin, finish + END.length) !== block
|
|
181
|
+
) {
|
|
182
|
+
conflicts.push(
|
|
183
|
+
'AGENTS.md: existing managed section differs or has malformed/duplicate markers; review manually.',
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
return original;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function createPlan({
|
|
190
|
+
dir = process.cwd(),
|
|
191
|
+
framework: choice,
|
|
192
|
+
packageManager: managerChoice,
|
|
193
|
+
} = {}) {
|
|
194
|
+
const target = resolve(dir);
|
|
195
|
+
if (lstatSync(target).isSymbolicLink() || !lstatSync(target).isDirectory())
|
|
196
|
+
throw new Error('Target must be a regular application directory.');
|
|
197
|
+
const root = realpathSync(target);
|
|
198
|
+
const before = Object.fromEntries(FILES.map((file) => [file, readSafe(join(root, file))]));
|
|
199
|
+
if (before['package.json'] === null) throw new Error('No package.json in the target directory.');
|
|
200
|
+
const pkg = manifest(before['package.json']);
|
|
201
|
+
checkTarget(root, pkg);
|
|
202
|
+
const framework = detectFramework(pkg, choice);
|
|
203
|
+
const packageManager = detectManager(root, pkg, managerChoice);
|
|
204
|
+
const conflicts = [];
|
|
205
|
+
const manual = [
|
|
206
|
+
'Import shared CSS once in the global entry.',
|
|
207
|
+
'Merge consumerConfig after existing parser configuration and enable lint in CI.',
|
|
208
|
+
'Run application lint, typecheck, build, and interaction checks.',
|
|
209
|
+
];
|
|
210
|
+
const eslint = SECTIONS.map((s) => pkg[s]?.eslint).filter(Boolean);
|
|
211
|
+
if (!eslint.length)
|
|
212
|
+
manual.unshift('Install ESLint 10 and the framework parsers before enabling Kazee lint.');
|
|
213
|
+
const deps = Object.assign({}, ...SECTIONS.map((s) => pkg[s]));
|
|
214
|
+
const peers =
|
|
215
|
+
framework === 'react'
|
|
216
|
+
? [
|
|
217
|
+
['react', '>=19.0.0'],
|
|
218
|
+
['react-dom', '>=19.0.0'],
|
|
219
|
+
]
|
|
220
|
+
: [['vue', '>=3.4.0']];
|
|
221
|
+
for (const [name, required] of peers) {
|
|
222
|
+
if (!deps[name])
|
|
223
|
+
manual.push(
|
|
224
|
+
`Install or review the required ${name} peer dependency (${required}); init does not change framework versions.`,
|
|
225
|
+
);
|
|
226
|
+
else if (
|
|
227
|
+
!semver.validRange(deps[name]) ||
|
|
228
|
+
!semver.minVersion(deps[name]) ||
|
|
229
|
+
!semver.subset(deps[name], required)
|
|
230
|
+
)
|
|
231
|
+
conflicts.push(
|
|
232
|
+
`package.json: ${name} must satisfy the published peer contract (${required}); resolve manually.`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (!deps['typescript-eslint'] && !deps['@typescript-eslint/parser'])
|
|
236
|
+
manual.push(
|
|
237
|
+
'The TypeScript parser is not declared; install or review the application parser setup.',
|
|
238
|
+
);
|
|
239
|
+
if (framework === 'vue' && !deps['eslint-plugin-vue'] && !deps['vue-eslint-parser'])
|
|
240
|
+
manual.push('A Vue SFC parser is not declared; install or review the Vue ESLint setup.');
|
|
241
|
+
for (const range of eslint) {
|
|
242
|
+
if (
|
|
243
|
+
!semver.validRange(range) ||
|
|
244
|
+
!semver.minVersion(range) ||
|
|
245
|
+
!semver.subset(range, '>=10.0.0 <11.0.0')
|
|
246
|
+
)
|
|
247
|
+
conflicts.push(
|
|
248
|
+
'package.json: ESLint must be constrained to supported stable major 10; resolve manually.',
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
for (const [name, version, section] of [
|
|
252
|
+
[`@kazee-ds/${framework}`, RELEASE.framework, 'dependencies'],
|
|
253
|
+
['@kazee-ds/styles', RELEASE.styles, 'dependencies'],
|
|
254
|
+
['@kazee-ds/eslint-config', RELEASE.lint, 'devDependencies'],
|
|
255
|
+
]) {
|
|
256
|
+
const locations = SECTIONS.filter((s) => pkg[s]?.[name] !== undefined);
|
|
257
|
+
if (locations.some((s) => s !== section || pkg[s][name] !== version))
|
|
258
|
+
conflicts.push(`package.json: review existing ${name} version or dependency section.`);
|
|
259
|
+
else if (!locations.length) {
|
|
260
|
+
pkg[section] ||= {};
|
|
261
|
+
pkg[section][name] = version;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const indent = before['package.json'].match(/\n([\t ]+)"/)?.[1] || ' ';
|
|
265
|
+
const pkgText =
|
|
266
|
+
JSON.stringify(pkg, null, indent).replaceAll('\n', newline(before['package.json'])) +
|
|
267
|
+
(before['package.json'].endsWith('\n') ? newline(before['package.json']) : '');
|
|
268
|
+
const guide = setupGuide(framework, packageManager).replaceAll(
|
|
269
|
+
'\n',
|
|
270
|
+
newline(before['kazee-setup.md']),
|
|
271
|
+
);
|
|
272
|
+
if (before['kazee-setup.md'] !== null && before['kazee-setup.md'] !== guide)
|
|
273
|
+
conflicts.push('kazee-setup.md: existing file differs; review manually.');
|
|
274
|
+
const after = {
|
|
275
|
+
'package.json':
|
|
276
|
+
JSON.stringify(pkg) === JSON.stringify(manifest(before['package.json']))
|
|
277
|
+
? before['package.json']
|
|
278
|
+
: pkgText,
|
|
279
|
+
'.npmrc': editRegistry(before['.npmrc'], conflicts),
|
|
280
|
+
'AGENTS.md': editRules(before['AGENTS.md'], framework, conflicts),
|
|
281
|
+
'kazee-setup.md': guide,
|
|
282
|
+
};
|
|
283
|
+
return {
|
|
284
|
+
root,
|
|
285
|
+
framework,
|
|
286
|
+
packageManager,
|
|
287
|
+
conflicts,
|
|
288
|
+
manual,
|
|
289
|
+
changes: FILES.map((name) => ({
|
|
290
|
+
name,
|
|
291
|
+
before: before[name],
|
|
292
|
+
after: after[name],
|
|
293
|
+
changed: before[name] !== after[name],
|
|
294
|
+
})),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function renderPlan(plan, applied = false) {
|
|
299
|
+
return `${applied ? 'Applied' : 'Preview (no files written)'}: ${plan.framework}, ${plan.packageManager}\n${plan.changes.map((c) => `${c.changed ? 'CHANGE' : 'UNCHANGED'} ${c.name}`).join('\n')}\n${plan.conflicts.map((s) => `BLOCKED ${s}`).join('\n')}\n\nNext: install public npm dependencies (no read token required):\n${installCommand(plan.packageManager, plan.framework)}\n\nManual steps:\n${plan.manual.map((s) => `- ${s}`).join('\n')}\n`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function applyPlan(plan, { write = writeFileSync } = {}) {
|
|
303
|
+
if (plan.conflicts.length)
|
|
304
|
+
throw new Error('Resolve preview conflicts before applying; no files written.');
|
|
305
|
+
if (realpathSync(plan.root) !== plan.root || lstatSync(plan.root).isSymbolicLink())
|
|
306
|
+
throw new Error('Target directory changed since preview.');
|
|
307
|
+
if (plan.changes.length !== FILES.length || plan.changes.some((c, i) => c.name !== FILES[i]))
|
|
308
|
+
throw new Error('Invalid plan file targets.');
|
|
309
|
+
for (const change of plan.changes) {
|
|
310
|
+
if (readSafe(join(plan.root, change.name)) !== change.before)
|
|
311
|
+
throw new Error(`${change.name} changed since preview; no files written.`);
|
|
312
|
+
}
|
|
313
|
+
const touched = [];
|
|
314
|
+
try {
|
|
315
|
+
for (const change of plan.changes.filter((c) => c.changed)) {
|
|
316
|
+
const fd = openSync(join(plan.root, change.name), change.before === null ? 'wx' : 'r+');
|
|
317
|
+
try {
|
|
318
|
+
const opened = fstatSync(fd);
|
|
319
|
+
const current = lstatSync(join(plan.root, change.name));
|
|
320
|
+
if (
|
|
321
|
+
!current.isFile() ||
|
|
322
|
+
current.isSymbolicLink() ||
|
|
323
|
+
opened.nlink > 1 ||
|
|
324
|
+
opened.ino !== current.ino ||
|
|
325
|
+
(process.platform !== 'win32' && opened.dev !== current.dev)
|
|
326
|
+
)
|
|
327
|
+
throw new Error('File target changed during apply.');
|
|
328
|
+
touched.push(change);
|
|
329
|
+
ftruncateSync(fd, 0);
|
|
330
|
+
write(fd, change.after, { encoding: 'utf8' });
|
|
331
|
+
} finally {
|
|
332
|
+
closeSync(fd);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
} catch {
|
|
336
|
+
const failed = [];
|
|
337
|
+
for (const change of touched.reverse()) {
|
|
338
|
+
const path = join(plan.root, change.name);
|
|
339
|
+
try {
|
|
340
|
+
readSafe(path);
|
|
341
|
+
if (change.before === null) {
|
|
342
|
+
if (existsSync(path)) unlinkSync(path);
|
|
343
|
+
} else writeFileSync(path, change.before, 'utf8');
|
|
344
|
+
} catch {
|
|
345
|
+
failed.push(change.name);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
throw new Error(
|
|
349
|
+
`Write failed. Attempted files: ${touched.map((c) => c.name).join(', ')}. ${failed.length ? `Restoration failed for: ${failed.join(', ')}; inspect manually.` : 'All attempted files restored.'}`,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
return plan;
|
|
353
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Kazee Design System consumer rules
|
|
2
|
+
|
|
3
|
+
Apply these rules to UI work in this application alongside its existing project instructions. Keep the application's established framework, package manager, routing, localization, data flow, and API contracts.
|
|
4
|
+
|
|
5
|
+
## Check the installed contract first
|
|
6
|
+
|
|
7
|
+
- Read this application's `package.json` and lockfile before changing UI. Use its installed package version as the API contract.
|
|
8
|
+
- Use the installed version's public catalog. Published 0.2.0 exposes eight components per framework; the prepared 0.3.0 catalog has 32. Do not assume a component exists in an older version.
|
|
9
|
+
- When Kazee MCP is connected, read `get_catalog_versions`, then `list_components`, `get_component_docs`, and `get_component_examples` using this framework and exact installed version. Otherwise inspect the installed package's public types when uncertain. A prop accepted by React Aria, Reka UI, or another UI library is not automatically supported by Kazee components.
|
|
10
|
+
- Start file discovery in application source, manifests, and the installed Kazee package's public entry points/types. Do not recursively list all of `node_modules` or investigate React Aria/Reka UI internals for routine component usage.
|
|
11
|
+
- Investigate dependency internals only when a concrete type, build, or runtime failure remains unexplained by the public contract. Identify that failure and the relevant file before expanding the search. When the requested behavior, build, and typecheck pass, report the result and any unrun checks instead of continuing speculative API research.
|
|
12
|
+
- Browse states in the public [Storybook](https://kazee-design-system-023417.gitlab.io). The catalog may differ from the installed version; check types before copying an example.
|
|
13
|
+
- Follow the [installation guide](https://gitlab.com/kazee.indonesia/kazee-design-system/-/blob/main/docs/installation.md) for package setup. Reading the guide requires GitLab project access; installing packages requires registry credentials. Storybook is public. Never print, commit, or embed registry tokens in frontend code.
|
|
14
|
+
|
|
15
|
+
## Kazee MCP
|
|
16
|
+
|
|
17
|
+
MCP is a read-only reference, not an installer or validator. It needs no token at runtime. Never provide token values to a tool. Candidate catalogs do not establish registry publication. Unknown versions must not be silently replaced with newer APIs. Run the application's actual checks after using examples and report missing or unrun validation.
|
|
18
|
+
|
|
19
|
+
## Reuse the foundation
|
|
20
|
+
|
|
21
|
+
- Use Kazee `Button` and `Select` for matching new UI in areas adopting this design system. Preserve existing application abstractions that already wrap them correctly.
|
|
22
|
+
- Import from `@kazee-ds/react` or `@kazee-ds/vue` public entry points. Do not copy package source into the application or import private build files.
|
|
23
|
+
- Do not bypass these components by importing React Aria or Reka UI directly to recreate their behavior. Keep unrelated existing integrations outside the task unchanged.
|
|
24
|
+
- Import `@kazee-ds/styles/index.css` once at the application's global CSS entry. It includes the tokens; do not add a duplicate token stylesheet.
|
|
25
|
+
- Use the public props for supported component styling. Do not rely on `className` or `style` on Kazee wrappers, or override their internal classes to create undocumented variants. Style application layout on surrounding elements.
|
|
26
|
+
- Common Button variants include `primary`, `secondary`, and `danger`; sizes are `sm`, `md`, and `lg`. Read the exact versioned contract for additional variants, icon-only and pending support. Defaults are `primary` and `md`; use `danger` for destructive actions.
|
|
27
|
+
|
|
28
|
+
## React API
|
|
29
|
+
|
|
30
|
+
- Import `Button` and `Select` from `@kazee-ds/react`.
|
|
31
|
+
- Button activation: `onPress`; disabled state: `isDisabled`. Do not substitute `onClick` or `disabled`.
|
|
32
|
+
- Select requires `label` and `options`. Options have a string `id`, a string `label`, and optional `isDisabled`.
|
|
33
|
+
- Controlled Select: `value` (string or `null`) and `onChange` (receives the selected string ID). Uncontrolled Select: `defaultValue`. Do not mix controlled and uncontrolled state.
|
|
34
|
+
- Use only documented public props. Do not invent `isLoading`, `loading`, `asChild`, `href`, or additional variants.
|
|
35
|
+
|
|
36
|
+
## Vue API
|
|
37
|
+
|
|
38
|
+
- Import `Button` and `Select` from `@kazee-ds/vue`.
|
|
39
|
+
- Button activation: `@press`; disabled state: `disabled`. Do not substitute React prop names.
|
|
40
|
+
- Select requires `label` and `options`. Options have a string `id`, a string `label`, and optional `isDisabled` (including in Vue).
|
|
41
|
+
- Controlled Select: `v-model` with a string or `undefined`. Uncontrolled Select: `defaultValue`. Do not mix controlled and uncontrolled state.
|
|
42
|
+
- Use only documented public props. Do not invent `loading`, `asChild`, `href`, or additional variants.
|
|
43
|
+
|
|
44
|
+
## Tokens, themes, and accessibility
|
|
45
|
+
|
|
46
|
+
- Use existing semantic color variables for new UI adopting the system, such as `--ds-color-surface-default`, `--ds-color-text-default`, and `--ds-color-border-subtle`. Check the installed token CSS before referencing a variable. Do not invent token names or use primitive color variables as semantic colors.
|
|
47
|
+
- Reuse available spacing and radius tokens, such as `--ds-space-4` and `--ds-radius-md`, where they fit. Follow the application's existing conventions for values the foundation does not yet define.
|
|
48
|
+
- Preserve the application's existing theme mechanism. If integrating Kazee light/dark modes, coordinate `data-theme="light"` or `data-theme="dark"` at the document root so portal content receives the same theme. Do not create a second theme toggle or interpolate unchecked tenant input into CSS.
|
|
49
|
+
- Keep Select's visible label and meaningful Button text. React icon-only buttons need an accessible name via the supported `aria-label` prop; verify the installed contract for other cases.
|
|
50
|
+
- Set Button `type="submit"` explicitly when it submits a form; the default is `button`.
|
|
51
|
+
- Preserve keyboard access, focus indication, disabled states, and the application's translations. For pending requests, use existing application feedback and guard request handlers; do not add unsupported loading props to Kazee components.
|
|
52
|
+
|
|
53
|
+
## When a component or capability is missing
|
|
54
|
+
|
|
55
|
+
- State the gap. Use an existing suitable application component or an accessible native element for the requested screen, and use available tokens where appropriate.
|
|
56
|
+
- Keep a necessary fallback local to the application. Do not introduce another component library or redesign unrelated screens as part of routine adoption.
|
|
57
|
+
- A shared Kazee component needs a separate contribution in the design-system repository with a shared React/Vue contract and its quality gates. Do not present an application fallback as a published Kazee component.
|
|
58
|
+
|
|
59
|
+
## Verify the change
|
|
60
|
+
|
|
61
|
+
- Run the application's relevant build, typecheck, lint, and existing tests using its own scripts. Do not assume design-system monorepo commands exist here.
|
|
62
|
+
- For changed interactions, check Button activation and disabled behavior, Select selection, and keyboard focus. Check existing light/dark modes and responsive layout where affected.
|
|
63
|
+
- Report what was actually verified and any checks that could not run. These instructions guide agent behavior; they do not install lint rules or enforce CI by themselves.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Consumer lint rules
|
|
2
|
+
|
|
3
|
+
[`@kazee-ds/eslint-config`](../packages/eslint-config) provides a reusable ESLint flat configuration. Version `0.1.0` implements two rules for the Button/Select foundation shipped in React/Vue `0.1.1` and styles/tokens `0.1.2`. It is an ESM package for ESLint 10 and Node 22.16 or later, with TypeScript declarations and no runtime dependencies. Adding `AGENTS.md` alone does not enable these checks.
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
| Rule | Reports |
|
|
8
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
9
|
+
| `kazee/public-imports` | Imports from private or nonexistent exports of the four foundation packages, and direct imports from React Aria, React Stately, or Reka UI |
|
|
10
|
+
| `kazee/component-api` | Selected unsupported props/events and static Button size/variant values on components imported directly from Kazee |
|
|
11
|
+
|
|
12
|
+
The import rule checks static imports, re-exports, literal dynamic imports, and unshadowed literal `require` calls. Public CSS/JSON exports and framework `package.json` exports remain allowed.
|
|
13
|
+
|
|
14
|
+
The component rule recognizes React JSX imports (including aliases and namespace imports) and Vue SFC `<script setup>` imports used in templates (including aliases, kebab-case names, and namespace imports). It distinguishes local components from imported Kazee components, including React lexical shadowing and Vue template variables.
|
|
15
|
+
|
|
16
|
+
Examples of reported mistakes:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { Button } from '@kazee-ds/react';
|
|
20
|
+
|
|
21
|
+
// Use onPress and isDisabled.
|
|
22
|
+
const save = (
|
|
23
|
+
<Button onClick={saveWorkspace} disabled={busy}>
|
|
24
|
+
Save
|
|
25
|
+
</Button>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Use primary, secondary, or danger; sm, md, or lg.
|
|
29
|
+
const other = (
|
|
30
|
+
<Button variant="outline" size="xl">
|
|
31
|
+
Save
|
|
32
|
+
</Button>
|
|
33
|
+
);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```vue
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
import { Button, Select } from '@kazee-ds/vue';
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<!-- Use disabled and @press. -->
|
|
43
|
+
<Button :is-disabled="busy" @click="save">Save</Button>
|
|
44
|
+
<!-- Use v-model for controlled selection. -->
|
|
45
|
+
<Select :value="workspace" label="Workspace" :options="options" />
|
|
46
|
+
</template>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The rule also rejects loading props, `asChild`, `href`, and styling props on these Kazee components. Apply application layout and feedback outside the component. It reports diagnostics only; it does not autofix events, change application state, or rewrite imports.
|
|
50
|
+
|
|
51
|
+
## Enable in a consuming application
|
|
52
|
+
|
|
53
|
+
Configure authenticated GitLab Registry access using the [installation guide](installation.md). Keep the application's existing ESLint configuration, parsers, plugins, package manager, and lockfile, then install:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
npm install --save-dev --save-exact @kazee-ds/eslint-config@0.1.0
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Merge the configuration object into the application's existing flat config, after its framework/parser setup:
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import { consumerConfig } from '@kazee-ds/eslint-config';
|
|
63
|
+
|
|
64
|
+
// Add this object to your existing exported flat-config array.
|
|
65
|
+
const kazeeRules = consumerConfig({
|
|
66
|
+
files: ['src/**/*.{js,jsx,ts,tsx,vue}'],
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`kazeeRules` must actually be included in the exported configuration; declaring it alone does not enable anything. Adjust `files` to the application's source directories (for example `app/` or `components/`). Scope it to areas adopting Kazee if the application still has unrelated headless-library integrations. The config intentionally forbids those imports in its selected files.
|
|
71
|
+
|
|
72
|
+
Use an ESLint 10 flat-config setup with a parser that supports the application's syntax: a TypeScript/JSX parser for React, or `vue-eslint-parser` with its TypeScript parser configured for Vue SFCs. The rules use `context.sourceCode` and Vue parser services; they do not configure parsers or add the general framework lint rules themselves.
|
|
73
|
+
|
|
74
|
+
Run the application's lint command and ensure it runs in the consuming project's CI. Update the lint package version and lockfile when adopting a different Kazee contract. Existing applications that copied the earlier standalone file can switch their config import to `@kazee-ds/eslint-config`, then remove the unused copy. The monorepo's old `tooling/eslint/consumer.mjs` entry point remains a re-export for compatibility.
|
|
75
|
+
|
|
76
|
+
### Complete React/TypeScript example
|
|
77
|
+
|
|
78
|
+
The following minimal `eslint.config.mjs` assumes ESLint 10 and `typescript-eslint` are already installed. Merge the Kazee config into the application's fuller rules rather than discarding its existing checks.
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
import tseslint from 'typescript-eslint';
|
|
82
|
+
import { consumerConfig } from '@kazee-ds/eslint-config';
|
|
83
|
+
|
|
84
|
+
export default [
|
|
85
|
+
{ ignores: ['dist/**', 'node_modules/**'] },
|
|
86
|
+
...tseslint.configs.recommended,
|
|
87
|
+
{
|
|
88
|
+
files: ['src/**/*.{ts,tsx}'],
|
|
89
|
+
languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } },
|
|
90
|
+
},
|
|
91
|
+
consumerConfig({ files: ['src/**/*.{ts,tsx}'] }),
|
|
92
|
+
];
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Complete Vue/TypeScript example
|
|
96
|
+
|
|
97
|
+
This minimal `eslint.config.mjs` assumes ESLint 10, `eslint-plugin-vue`, and `typescript-eslint` are already installed. The application owns the parsers; installing Kazee's lint package does not replace them.
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
import tseslint from 'typescript-eslint';
|
|
101
|
+
import vue from 'eslint-plugin-vue';
|
|
102
|
+
import { consumerConfig } from '@kazee-ds/eslint-config';
|
|
103
|
+
|
|
104
|
+
export default [
|
|
105
|
+
{ ignores: ['dist/**', 'node_modules/**'] },
|
|
106
|
+
...tseslint.configs.recommended,
|
|
107
|
+
...vue.configs['flat/recommended'],
|
|
108
|
+
{
|
|
109
|
+
files: ['**/*.vue'],
|
|
110
|
+
languageOptions: { parserOptions: { parser: tseslint.parser } },
|
|
111
|
+
},
|
|
112
|
+
consumerConfig({ files: ['src/**/*.{ts,vue}'] }),
|
|
113
|
+
];
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Verification in this repository
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
pnpm test:consumer-lint
|
|
120
|
+
pnpm lint
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The test suite checks valid usage, reported mistakes, and false-positive cases. It lives in `packages/eslint-config/test` and runs through the normal package tests in CI's `unit-and-packages` job; `test:consumer-lint` remains a focused alias. The root configuration consumes the workspace package for applications and the existing token/style source scope; it keeps headless imports available to the React/Vue package implementations themselves. Manual sandbox projects are excluded from the root lint command, and can be checked separately with a config using the same rules and appropriate parsers.
|
|
124
|
+
|
|
125
|
+
Version `0.1.0` was published and reinstalled from GitLab in isolated React and Vue consumers. Valid usage passed, incorrect APIs/private imports were rejected, and declarations, typechecks, and builds passed. See the [release and verification record](decisions/0007-eslint-config-private-release.md) for evidence and limits.
|
|
126
|
+
|
|
127
|
+
## Limits
|
|
128
|
+
|
|
129
|
+
- This is not full prop/type validation. Keep framework typechecking and interaction tests.
|
|
130
|
+
- Computed import paths, JSX spreads, Vue `v-bind` objects, dynamic directive arguments, and dynamic size/variant values are not evaluated.
|
|
131
|
+
- Components re-exported through an application barrel, Vue Options API registration, globally registered components, and dynamic `<component :is>` usage are outside the initial component rule's scope.
|
|
132
|
+
- Rules do not inspect CSS values, prove a global stylesheet import exists, or require every native Button/Select to be replaced.
|
|
133
|
+
- These checks guide code usage, not whether an agent read its instruction file or searched dependency internals.
|
|
134
|
+
|
|
135
|
+
See the [agent setup guide](agent-setup.md) for complementary instructions.
|