@trineui/cli 0.1.0-beta.1 → 0.1.1
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 +41 -0
- package/bin/trine.js +24 -0
- package/dist/add-button.js +15 -0
- package/dist/add-component.js +317 -0
- package/dist/index.js +114 -641
- package/package.json +23 -30
- package/templates/button/button.html +11 -0
- package/templates/button/button.skin.ts +128 -0
- package/templates/button/button.ts +39 -0
- package/templates/styles/tokens.css +102 -0
- package/templates/styles/trine-consumer.css +58 -0
- package/CHANGELOG.md +0 -30
- package/src/commands/add.ts +0 -101
- package/src/commands/diff.test.ts +0 -55
- package/src/commands/diff.ts +0 -104
- package/src/commands/eject.ts +0 -95
- package/src/commands/init.ts +0 -92
- package/src/commands/sync-interactive.ts +0 -108
- package/src/commands/sync.test.ts +0 -35
- package/src/commands/sync.ts +0 -113
- package/src/index.ts +0 -18
- package/src/types/manifest.ts +0 -14
- package/src/utils/__tests__/hash.test.ts +0 -35
- package/src/utils/__tests__/template.test.ts +0 -47
- package/src/utils/eject-merger.ts +0 -149
- package/src/utils/hash.ts +0 -43
- package/src/utils/manifest.ts +0 -43
- package/src/utils/template.ts +0 -26
- package/templates/button.blueprint.ts.hbs +0 -41
- package/templates/button.skin.ts.hbs +0 -35
- package/templates/checkbox.blueprint.ts.hbs +0 -57
- package/templates/checkbox.skin.ts.hbs +0 -44
- package/templates/dialog.blueprint.ts.hbs +0 -61
- package/templates/dialog.skin.ts.hbs +0 -27
- package/templates/input.blueprint.ts.hbs +0 -83
- package/templates/input.skin.ts.hbs +0 -29
- package/templates/select.blueprint.ts.hbs +0 -86
- package/templates/select.skin.ts.hbs +0 -53
- package/tsconfig.json +0 -10
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @trineui/cli
|
|
2
|
+
|
|
3
|
+
Canonical public package:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx @trineui/cli@latest add button
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Current v0 command:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
trine add button --target <app-root>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Notes:
|
|
16
|
+
|
|
17
|
+
- `button` is the only supported component in this public-style baseline
|
|
18
|
+
- omitting `--target` uses the current directory when it already matches the supported Angular app shape
|
|
19
|
+
- when the current directory is not a supported Angular app target, the CLI auto-detects a single Angular app target under the current directory and proceeds automatically
|
|
20
|
+
- when multiple Angular app targets are found, the CLI stops and asks for `--target <app-root>`
|
|
21
|
+
- external targets can run `trine add button` from the app root or pass an explicit app root such as `--target /absolute/path/to/angular-app`
|
|
22
|
+
- the canonical public package name is `@trineui/cli`
|
|
23
|
+
- the CLI command exposed through the package bin is still `trine`
|
|
24
|
+
- `apps/consumer-fixture` is the first separate-target proof and does not use the demo-only `@trine/ui/*` bridge
|
|
25
|
+
- `/tmp/trine-button-publish-proof` is the latest truly external packaged-proof repo outside the monorepo
|
|
26
|
+
- packaged/public-style proof uses a packed local tarball to simulate `npx @trineui/cli@latest add button`
|
|
27
|
+
- the packaged CLI ships compiled runtime files plus Button templates so it can execute from `node_modules` in a real `npx`-style flow
|
|
28
|
+
- consumer-owned component destination files cause a clear failure
|
|
29
|
+
- existing shared baseline files (`tokens.css` and `trine-consumer.css`) are preserved so a second component can be added into the same target repo
|
|
30
|
+
- the command copies consumer-owned source instead of wiring runtime back to `packages/ui`
|
|
31
|
+
- for `apps/demo`, `@trine/ui` resolves locally for delivered components while `@trine/ui/*` temporarily bridges non-localized components back to the authoring source
|
|
32
|
+
- the delivered shared styling baseline is `tokens.css` + `trine-consumer.css`
|
|
33
|
+
- the current proven target dependency baseline is Angular 21, Tailwind CSS v4, and `class-variance-authority`
|
|
34
|
+
- use a Node LTS line supported by Angular 21 in the target repo; odd-numbered Node releases can build with warnings
|
|
35
|
+
- when `package.json` is present in the target root, the CLI warns if Tailwind CSS v4 or `class-variance-authority` are missing
|
|
36
|
+
|
|
37
|
+
Local package proof equivalent:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx --yes --package /absolute/path/to/trineui-cli-0.1.0.tgz trine add button
|
|
41
|
+
```
|
package/bin/trine.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const distEntryPath = path.resolve(currentDir, '../dist/index.js');
|
|
10
|
+
const sourceEntryPath = path.resolve(currentDir, '../src/index.ts');
|
|
11
|
+
const entryPath = existsSync(distEntryPath) ? distEntryPath : sourceEntryPath;
|
|
12
|
+
const nodeArgs = entryPath.endsWith('.ts')
|
|
13
|
+
? ['--experimental-strip-types', entryPath, ...process.argv.slice(2)]
|
|
14
|
+
: [entryPath, ...process.argv.slice(2)];
|
|
15
|
+
|
|
16
|
+
const result = spawnSync(process.execPath, nodeArgs, {
|
|
17
|
+
stdio: 'inherit',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (result.error) {
|
|
21
|
+
throw result.error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
process.exit(result.status ?? 1);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { addComponent, } from "./add-component.js";
|
|
2
|
+
const BUTTON_SOURCE_FILES = [
|
|
3
|
+
'button/button.ts',
|
|
4
|
+
'button/button.html',
|
|
5
|
+
'button/button.skin.ts',
|
|
6
|
+
];
|
|
7
|
+
export function addButton(options) {
|
|
8
|
+
return addComponent({
|
|
9
|
+
componentName: 'button',
|
|
10
|
+
componentLabel: 'Button',
|
|
11
|
+
sourceFiles: BUTTON_SOURCE_FILES,
|
|
12
|
+
barrelLine: "export { ButtonComponent, ButtonComponent as TrineButton } from './button';",
|
|
13
|
+
uiExportLine: "export * from './button';",
|
|
14
|
+
}, options);
|
|
15
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import ts from 'typescript';
|
|
5
|
+
const STYLE_SOURCE_FILES = ['styles/tokens.css', 'styles/trine-consumer.css'];
|
|
6
|
+
const TEMPLATE_ROOT = fileURLToPath(new URL('../templates/', import.meta.url));
|
|
7
|
+
const LOCAL_REPO_DEMO_ROOT = path.resolve(TEMPLATE_ROOT, '../../../apps/demo');
|
|
8
|
+
const STYLE_IMPORT_LINE = "@import './styles/trine-consumer.css';";
|
|
9
|
+
const DEMO_BRIDGE_COMMENT = '// Temporary demo verification bridge: delivered local components resolve locally; other components still re-export from the authoring source.';
|
|
10
|
+
const DEMO_LOCAL_COMPONENTS = [
|
|
11
|
+
{
|
|
12
|
+
key: 'button',
|
|
13
|
+
exportLine: "export * from './button';",
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
const DEMO_PROXY_EXPORT_LINES = [
|
|
17
|
+
{
|
|
18
|
+
key: 'button-group',
|
|
19
|
+
line: "export * from '@trine/ui/src/components/ui/button-group';",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: 'checkbox',
|
|
23
|
+
line: "export * from '@trine/ui/src/components/ui/checkbox';",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
key: 'dialog',
|
|
27
|
+
line: "export * from '@trine/ui/src/components/ui/dialog';",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: 'input',
|
|
31
|
+
line: "export * from '@trine/ui/src/components/ui/input/input';",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: 'popover',
|
|
35
|
+
line: "export * from '@trine/ui/src/components/ui/popover';",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: 'radio',
|
|
39
|
+
line: "export * from '@trine/ui/src/components/ui/radio';",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: 'select',
|
|
43
|
+
line: "export * from '@trine/ui/src/components/ui/select';",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: 'sprint-zero-placeholder',
|
|
47
|
+
line: "export * from '@trine/ui/src/components/ui/sprint-zero-placeholder/sprint-zero-placeholder';",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
key: 'switch',
|
|
51
|
+
line: "export * from '@trine/ui/src/components/ui/switch';",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
key: 'textarea',
|
|
55
|
+
line: "export * from '@trine/ui/src/components/ui/textarea';",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: 'tabs',
|
|
59
|
+
line: "export * from '@trine/ui/src/components/ui/tabs';",
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
export function addComponent(manifest, options) {
|
|
63
|
+
const targetRoot = path.resolve(options.cwd, options.target);
|
|
64
|
+
const targetAppDir = path.join(targetRoot, 'src', 'app');
|
|
65
|
+
const targetStylesEntry = path.join(targetRoot, 'src', 'styles.scss');
|
|
66
|
+
const targetTsconfig = path.join(targetRoot, 'tsconfig.app.json');
|
|
67
|
+
assertTargetShape(manifest.componentName, targetRoot, targetAppDir, targetStylesEntry, targetTsconfig);
|
|
68
|
+
const componentDestDir = path.join(targetRoot, 'src', 'app', 'components', 'ui', manifest.componentName);
|
|
69
|
+
const stylesDestDir = path.join(targetRoot, 'src', 'styles');
|
|
70
|
+
const componentCopyTargets = manifest.sourceFiles.map((source) => ({
|
|
71
|
+
source: path.join(TEMPLATE_ROOT, source),
|
|
72
|
+
destination: path.join(componentDestDir, path.basename(source)),
|
|
73
|
+
}));
|
|
74
|
+
const conflicts = componentCopyTargets
|
|
75
|
+
.filter(({ destination }) => existsSync(destination))
|
|
76
|
+
.map(({ destination }) => toTargetRelativePath(targetRoot, destination));
|
|
77
|
+
if (conflicts.length > 0) {
|
|
78
|
+
throw new Error([
|
|
79
|
+
`trine add ${manifest.componentName} aborted because consumer-owned destination files already exist:`,
|
|
80
|
+
...conflicts.map((file) => `- ${file}`),
|
|
81
|
+
].join('\n'));
|
|
82
|
+
}
|
|
83
|
+
mkdirSync(componentDestDir, { recursive: true });
|
|
84
|
+
mkdirSync(stylesDestDir, { recursive: true });
|
|
85
|
+
for (const { source, destination } of componentCopyTargets) {
|
|
86
|
+
copyFileSync(source, destination);
|
|
87
|
+
}
|
|
88
|
+
const componentCopiedFiles = componentCopyTargets.map(({ destination }) => toTargetRelativePath(targetRoot, destination));
|
|
89
|
+
const sharedStylesResult = ensureSharedStyleBaseline(targetRoot, stylesDestDir, manifest.componentLabel);
|
|
90
|
+
const copiedFiles = [...componentCopiedFiles, ...sharedStylesResult.copiedFiles];
|
|
91
|
+
const updatedFiles = [];
|
|
92
|
+
const warnings = [...sharedStylesResult.warnings];
|
|
93
|
+
const componentBarrelPath = path.join(componentDestDir, 'index.ts');
|
|
94
|
+
if (ensureLinesFile(componentBarrelPath, [manifest.barrelLine])) {
|
|
95
|
+
updatedFiles.push(toTargetRelativePath(targetRoot, componentBarrelPath));
|
|
96
|
+
}
|
|
97
|
+
const uiRootBarrelPath = path.join(targetRoot, 'src', 'app', 'components', 'ui', 'index.ts');
|
|
98
|
+
const uiRootUpdated = isDemoTarget(targetRoot)
|
|
99
|
+
? rewriteDemoUiRootBarrel(uiRootBarrelPath)
|
|
100
|
+
: ensureLinesFile(uiRootBarrelPath, [manifest.uiExportLine]);
|
|
101
|
+
if (uiRootUpdated) {
|
|
102
|
+
updatedFiles.push(toTargetRelativePath(targetRoot, uiRootBarrelPath));
|
|
103
|
+
}
|
|
104
|
+
if (ensureTsconfigAlias(targetTsconfig, targetRoot, options.cwd)) {
|
|
105
|
+
updatedFiles.push(toTargetRelativePath(targetRoot, targetTsconfig));
|
|
106
|
+
}
|
|
107
|
+
const stylesResult = ensureStylesImport(targetStylesEntry);
|
|
108
|
+
if (stylesResult.updated) {
|
|
109
|
+
updatedFiles.push(toTargetRelativePath(targetRoot, targetStylesEntry));
|
|
110
|
+
}
|
|
111
|
+
if (stylesResult.authoringImportStillPresent) {
|
|
112
|
+
warnings.push(`${toTargetRelativePath(targetRoot, targetStylesEntry)} still imports @trine/ui/styles/trine.css for the broader demo authoring baseline.`);
|
|
113
|
+
}
|
|
114
|
+
if (isDemoTarget(targetRoot)) {
|
|
115
|
+
warnings.push('apps/demo keeps non-localized components on a temporary @trine/ui/* bridge back to packages/ui so the full demo app can still build while delivered components resolve locally.');
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
warnings.push(...readTargetDependencyWarnings(targetRoot, manifest.componentLabel));
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
componentName: manifest.componentName,
|
|
122
|
+
copiedFiles,
|
|
123
|
+
updatedFiles,
|
|
124
|
+
warnings,
|
|
125
|
+
targetRoot,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function ensureSharedStyleBaseline(targetRoot, stylesDestDir, componentLabel) {
|
|
129
|
+
const copiedFiles = [];
|
|
130
|
+
const warnings = [];
|
|
131
|
+
for (const sourceFile of STYLE_SOURCE_FILES) {
|
|
132
|
+
const templatePath = path.join(TEMPLATE_ROOT, sourceFile);
|
|
133
|
+
const destinationPath = path.join(stylesDestDir, path.basename(sourceFile));
|
|
134
|
+
const relativeDestination = toTargetRelativePath(targetRoot, destinationPath);
|
|
135
|
+
if (!existsSync(destinationPath)) {
|
|
136
|
+
copyFileSync(templatePath, destinationPath);
|
|
137
|
+
copiedFiles.push(relativeDestination);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (readFileSync(destinationPath, 'utf8') !== readFileSync(templatePath, 'utf8')) {
|
|
141
|
+
warnings.push(`${relativeDestination} already exists and was preserved. Review it manually if the delivered ${componentLabel} expects newer shared styling baseline content.`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
copiedFiles,
|
|
146
|
+
warnings,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function assertTargetShape(componentName, targetRoot, targetAppDir, targetStylesEntry, targetTsconfig) {
|
|
150
|
+
const missing = [];
|
|
151
|
+
if (!existsSync(targetRoot)) {
|
|
152
|
+
missing.push(targetRoot);
|
|
153
|
+
}
|
|
154
|
+
if (!existsSync(targetAppDir)) {
|
|
155
|
+
missing.push(targetAppDir);
|
|
156
|
+
}
|
|
157
|
+
if (!existsSync(targetStylesEntry)) {
|
|
158
|
+
missing.push(targetStylesEntry);
|
|
159
|
+
}
|
|
160
|
+
if (!existsSync(targetTsconfig)) {
|
|
161
|
+
missing.push(targetTsconfig);
|
|
162
|
+
}
|
|
163
|
+
if (missing.length > 0) {
|
|
164
|
+
throw new Error([
|
|
165
|
+
`trine add ${componentName} requires an Angular app target with src/app, src/styles.scss, and tsconfig.app.json.`,
|
|
166
|
+
...missing.map((file) => `- ${file}`),
|
|
167
|
+
].join('\n'));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function ensureLinesFile(filePath, lines) {
|
|
171
|
+
const existing = existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
|
|
172
|
+
const normalizedExisting = existing.trimEnd();
|
|
173
|
+
const currentLines = normalizedExisting === '' ? [] : normalizedExisting.split('\n');
|
|
174
|
+
let changed = false;
|
|
175
|
+
for (const line of lines) {
|
|
176
|
+
if (!currentLines.includes(line)) {
|
|
177
|
+
currentLines.push(line);
|
|
178
|
+
changed = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (!existsSync(filePath)) {
|
|
182
|
+
changed = true;
|
|
183
|
+
}
|
|
184
|
+
if (changed) {
|
|
185
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
186
|
+
writeFileSync(filePath, `${currentLines.join('\n')}\n`);
|
|
187
|
+
}
|
|
188
|
+
return changed;
|
|
189
|
+
}
|
|
190
|
+
function rewriteDemoUiRootBarrel(filePath) {
|
|
191
|
+
const uiRootDir = path.dirname(filePath);
|
|
192
|
+
const localComponentLines = DEMO_LOCAL_COMPONENTS.filter(({ key }) => existsSync(path.join(uiRootDir, key, 'index.ts')));
|
|
193
|
+
const localComponentKeys = new Set(localComponentLines.map(({ key }) => key));
|
|
194
|
+
const nextLines = [
|
|
195
|
+
DEMO_BRIDGE_COMMENT,
|
|
196
|
+
...localComponentLines.map(({ exportLine }) => exportLine),
|
|
197
|
+
...DEMO_PROXY_EXPORT_LINES.filter(({ key }) => !localComponentKeys.has(key)).map(({ line }) => line),
|
|
198
|
+
];
|
|
199
|
+
const nextContent = `${nextLines.join('\n')}\n`;
|
|
200
|
+
const currentContent = existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
|
|
201
|
+
if (currentContent === nextContent) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
205
|
+
writeFileSync(filePath, nextContent);
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
function ensureTsconfigAlias(tsconfigPath, targetRoot, cwd) {
|
|
209
|
+
const currentText = readFileSync(tsconfigPath, 'utf8');
|
|
210
|
+
const parsed = ts.parseConfigFileTextToJson(tsconfigPath, currentText);
|
|
211
|
+
if (parsed.error) {
|
|
212
|
+
throw new Error(`Unable to parse ${path.relative(cwd, tsconfigPath)} as JSONC.`);
|
|
213
|
+
}
|
|
214
|
+
const config = (parsed.config ?? {});
|
|
215
|
+
config.compilerOptions ??= {};
|
|
216
|
+
config.compilerOptions.paths ??= {};
|
|
217
|
+
const aliasTarget = isDemoTarget(targetRoot)
|
|
218
|
+
? toPosixPath(path.relative(process.cwd(), path.join(targetRoot, 'src', 'app', 'components', 'ui', 'index.ts')))
|
|
219
|
+
: toConfigRelativePath(path.relative(path.dirname(tsconfigPath), path.join(targetRoot, 'src', 'app', 'components', 'ui', 'index.ts')));
|
|
220
|
+
const wildcardTarget = 'packages/ui/*';
|
|
221
|
+
const currentAlias = config.compilerOptions.paths['@trine/ui'];
|
|
222
|
+
const currentWildcardAlias = config.compilerOptions.paths['@trine/ui/*'];
|
|
223
|
+
const aliasIsCurrent = Array.isArray(currentAlias) && currentAlias.length === 1 && currentAlias[0] === aliasTarget;
|
|
224
|
+
const wildcardIsCurrent = !isDemoTarget(targetRoot) ||
|
|
225
|
+
(Array.isArray(currentWildcardAlias) &&
|
|
226
|
+
currentWildcardAlias.length === 1 &&
|
|
227
|
+
currentWildcardAlias[0] === wildcardTarget);
|
|
228
|
+
if (aliasIsCurrent && wildcardIsCurrent) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
config.compilerOptions.paths['@trine/ui'] = [aliasTarget];
|
|
232
|
+
if (isDemoTarget(targetRoot)) {
|
|
233
|
+
config.compilerOptions.paths['@trine/ui/*'] = [wildcardTarget];
|
|
234
|
+
}
|
|
235
|
+
else if ('@trine/ui/*' in config.compilerOptions.paths) {
|
|
236
|
+
delete config.compilerOptions.paths['@trine/ui/*'];
|
|
237
|
+
}
|
|
238
|
+
writeFileSync(tsconfigPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
function ensureStylesImport(stylesPath) {
|
|
242
|
+
const current = readFileSync(stylesPath, 'utf8');
|
|
243
|
+
if (current.includes(STYLE_IMPORT_LINE)) {
|
|
244
|
+
return {
|
|
245
|
+
updated: false,
|
|
246
|
+
authoringImportStillPresent: current.includes("@import '@trine/ui/styles/trine.css';"),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const lines = current.split('\n');
|
|
250
|
+
let insertAt = -1;
|
|
251
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
252
|
+
const trimmed = lines[index].trim();
|
|
253
|
+
if (trimmed.startsWith('@use') || trimmed.startsWith('@import')) {
|
|
254
|
+
insertAt = index;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (insertAt === -1) {
|
|
258
|
+
lines.unshift(STYLE_IMPORT_LINE, '');
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
lines.splice(insertAt + 1, 0, STYLE_IMPORT_LINE);
|
|
262
|
+
}
|
|
263
|
+
writeFileSync(stylesPath, lines.join('\n'));
|
|
264
|
+
return {
|
|
265
|
+
updated: true,
|
|
266
|
+
authoringImportStillPresent: current.includes("@import '@trine/ui/styles/trine.css';"),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function isDemoTarget(targetRoot) {
|
|
270
|
+
return (existsSync(LOCAL_REPO_DEMO_ROOT) &&
|
|
271
|
+
path.resolve(targetRoot) === path.resolve(LOCAL_REPO_DEMO_ROOT));
|
|
272
|
+
}
|
|
273
|
+
function toPosixPath(filePath) {
|
|
274
|
+
return filePath.split(path.sep).join(path.posix.sep);
|
|
275
|
+
}
|
|
276
|
+
function toConfigRelativePath(filePath) {
|
|
277
|
+
const posixPath = toPosixPath(filePath);
|
|
278
|
+
return posixPath.startsWith('.') ? posixPath : `./${posixPath}`;
|
|
279
|
+
}
|
|
280
|
+
function toTargetRelativePath(targetRoot, filePath) {
|
|
281
|
+
return toPosixPath(path.relative(targetRoot, filePath));
|
|
282
|
+
}
|
|
283
|
+
function readTargetDependencyWarnings(targetRoot, componentLabel) {
|
|
284
|
+
const packageJsonPath = path.join(targetRoot, 'package.json');
|
|
285
|
+
if (!existsSync(packageJsonPath)) {
|
|
286
|
+
return [
|
|
287
|
+
'No package.json was found in the target root, so Tailwind CSS v4 and class-variance-authority prerequisites could not be checked automatically.',
|
|
288
|
+
];
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
292
|
+
const deps = {
|
|
293
|
+
...(packageJson.dependencies ?? {}),
|
|
294
|
+
...(packageJson.devDependencies ?? {}),
|
|
295
|
+
};
|
|
296
|
+
const warnings = [];
|
|
297
|
+
if (!deps['class-variance-authority']) {
|
|
298
|
+
warnings.push(`class-variance-authority is missing from the target repo. Install it before building the delivered ${componentLabel}.`);
|
|
299
|
+
}
|
|
300
|
+
const tailwindRange = deps['tailwindcss'];
|
|
301
|
+
if (!tailwindRange) {
|
|
302
|
+
warnings.push(`tailwindcss is missing from the target repo. The current proven ${componentLabel} baseline expects Tailwind CSS v4.`);
|
|
303
|
+
}
|
|
304
|
+
else if (!looksLikeTailwindV4(tailwindRange)) {
|
|
305
|
+
warnings.push(`The target repo declares tailwindcss@${tailwindRange}. The current proven ${componentLabel} baseline expects Tailwind CSS v4.`);
|
|
306
|
+
}
|
|
307
|
+
return warnings;
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return [
|
|
311
|
+
`package.json could not be parsed for dependency checks. Verify Tailwind CSS v4 and class-variance-authority manually before building the delivered ${componentLabel}.`,
|
|
312
|
+
];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function looksLikeTailwindV4(range) {
|
|
316
|
+
return /(^|[^\d])4(\D|$)/.test(range);
|
|
317
|
+
}
|