@trineui/cli 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +186 -36
- package/dist/add-component.js +6 -6
- package/dist/index.js +175 -29
- package/dist/init.js +23 -12
- package/dist/project.js +312 -85
- package/dist/prompt.js +59 -0
- package/package.json +1 -1
- package/templates/button/button.skin.ts +41 -41
- package/templates/styles/trine-baseline.css +125 -0
- package/templates/styles/tokens.css +0 -102
- package/templates/styles/trine-consumer.css +0 -58
package/README.md
CHANGED
|
@@ -1,54 +1,204 @@
|
|
|
1
1
|
# @trineui/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@trineui/cli` is the public Trine CLI for the current Button-first baseline.
|
|
4
|
+
|
|
5
|
+
Current public baseline:
|
|
6
|
+
|
|
7
|
+
- guided `init`
|
|
8
|
+
- `add button`
|
|
9
|
+
- Button is the only public component baseline in scope
|
|
10
|
+
- Angular has the strongest proof coverage right now
|
|
11
|
+
|
|
12
|
+
Canonical public flow:
|
|
4
13
|
|
|
5
14
|
```bash
|
|
15
|
+
npx @trineui/cli@latest init
|
|
6
16
|
npx @trineui/cli@latest add button
|
|
7
17
|
```
|
|
8
18
|
|
|
9
|
-
|
|
19
|
+
`init` is guided by default. Use `--yes` for non-interactive mode.
|
|
20
|
+
|
|
21
|
+
## What Works Right Now
|
|
22
|
+
|
|
23
|
+
The current public Trine baseline is intentionally narrow.
|
|
24
|
+
|
|
25
|
+
It supports:
|
|
26
|
+
|
|
27
|
+
- preparing a supported Angular app with `init`
|
|
28
|
+
- delivering a consumer-owned Button with `add button`
|
|
29
|
+
- wiring a local `@trine/ui` alias into the target app
|
|
30
|
+
- injecting the Trine styling baseline into the target app's real global stylesheet entry
|
|
31
|
+
- local customization by editing `button.skin.ts` or adding semantic variable overrides below the Trine-managed block
|
|
32
|
+
|
|
33
|
+
It does not currently support:
|
|
34
|
+
|
|
35
|
+
- more components than Button
|
|
36
|
+
- automatic prerequisite installation
|
|
37
|
+
- automatic usage-surface scaffolding
|
|
38
|
+
- broad framework coverage claims beyond the current Angular-first baseline
|
|
39
|
+
|
|
40
|
+
## Before You Start
|
|
41
|
+
|
|
42
|
+
### Supported target shape
|
|
43
|
+
|
|
44
|
+
The current proven target shape is an Angular app with:
|
|
45
|
+
|
|
46
|
+
- `src/app`
|
|
47
|
+
- `tsconfig.app.json`
|
|
48
|
+
- a valid global stylesheet entry such as:
|
|
49
|
+
- `src/styles.scss`
|
|
50
|
+
- `src/styles.css`
|
|
51
|
+
- `src/global.scss`
|
|
52
|
+
- or a build styles entry resolved from `angular.json`
|
|
53
|
+
|
|
54
|
+
`init` can distinguish `angular` and `ionic-angular`, but Angular has the strongest live proof coverage today.
|
|
55
|
+
|
|
56
|
+
### Manual prerequisites
|
|
57
|
+
|
|
58
|
+
The live CLI still expects these prerequisites to be installed manually in the target repo.
|
|
59
|
+
|
|
60
|
+
For the current Button baseline, these are hard requirements rather than optional enhancements:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install class-variance-authority
|
|
64
|
+
npm install -D tailwindcss@^4 @tailwindcss/postcss postcss
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The current baseline also expects PostCSS configuration in the target repo:
|
|
68
|
+
|
|
69
|
+
`.postcssrc.json`
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"plugins": {
|
|
74
|
+
"@tailwindcss/postcss": {}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Use a Node LTS line supported by Angular 21 in the target repo. Odd-numbered Node releases can build with warnings.
|
|
80
|
+
|
|
81
|
+
For the styling-layer contract behind these requirements, see:
|
|
82
|
+
|
|
83
|
+
- `docs/01-overview/05-styling-contract.md`
|
|
84
|
+
|
|
85
|
+
## Quickstart
|
|
86
|
+
|
|
87
|
+
### 1. Run guided init
|
|
88
|
+
|
|
89
|
+
From the app root:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx @trineui/cli@latest init
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
If you prefer non-interactive mode:
|
|
10
96
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- `init` is implemented in this repo and included in the next publish candidate, but it is not available from the live npm package until publish succeeds
|
|
97
|
+
```bash
|
|
98
|
+
npx @trineui/cli@latest init --yes
|
|
99
|
+
```
|
|
15
100
|
|
|
16
|
-
|
|
101
|
+
If you are inside a workspace root or want to target a specific app:
|
|
17
102
|
|
|
18
103
|
```bash
|
|
19
|
-
|
|
20
|
-
trine add button --target <app-root>
|
|
104
|
+
npx @trineui/cli@latest init --target /absolute/path/to/app
|
|
21
105
|
```
|
|
22
106
|
|
|
23
|
-
|
|
107
|
+
`init` currently:
|
|
108
|
+
|
|
109
|
+
- detects the target app
|
|
110
|
+
- detects `angular` vs `ionic-angular`
|
|
111
|
+
- resolves the target stylesheet entry
|
|
112
|
+
- previews file changes before mutation
|
|
113
|
+
- injects a named Trine-managed styling block into the resolved global stylesheet entry
|
|
114
|
+
- ensures `src/app/components/ui/index.ts`
|
|
115
|
+
- ensures local `@trine/ui` in `tsconfig.app.json`
|
|
116
|
+
- ensures the stylesheet block includes semantic tokens, `.dark` overrides, and Tailwind v4 `@theme inline` mapping
|
|
117
|
+
|
|
118
|
+
`init` does not currently install Tailwind/PostCSS/CVA or generate `.postcssrc.json` for you.
|
|
119
|
+
|
|
120
|
+
Rerunning `init` on an already prepared target should no-op cleanly.
|
|
121
|
+
|
|
122
|
+
### 2. Deliver Button
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npx @trineui/cli@latest add button
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Or explicitly:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npx @trineui/cli@latest add button --target /absolute/path/to/app
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`add button` currently delivers:
|
|
135
|
+
|
|
136
|
+
- `src/app/components/ui/button/button.ts`
|
|
137
|
+
- `src/app/components/ui/button/button.html`
|
|
138
|
+
- `src/app/components/ui/button/button.skin.ts`
|
|
139
|
+
- `src/app/components/ui/button/index.ts`
|
|
140
|
+
- `src/app/components/ui/index.ts`
|
|
141
|
+
|
|
142
|
+
It fails clearly if consumer-owned Button files already exist. It does not silently overwrite them.
|
|
24
143
|
|
|
25
|
-
|
|
26
|
-
- `button` is the only supported component in this public-style baseline
|
|
27
|
-
- omitting `--target` uses the current directory when it already matches the supported Angular app shape
|
|
28
|
-
- 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
|
|
29
|
-
- when multiple Angular app targets are found, the CLI stops and asks for `--target <app-root>`
|
|
30
|
-
- 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`
|
|
31
|
-
- `init` detects `angular` and `ionic-angular` targets conservatively; unsupported targets fail clearly
|
|
32
|
-
- the canonical public package name is `@trineui/cli`
|
|
33
|
-
- the CLI command exposed through the package bin is still `trine`
|
|
34
|
-
- `apps/consumer-fixture` is the first separate-target proof and does not use the demo-only `@trine/ui/*` bridge
|
|
35
|
-
- `/tmp/trine-button-publish-proof` is the latest truly external packaged-proof repo outside the monorepo
|
|
36
|
-
- packaged/public-style proof uses a packed local tarball to simulate `npx @trineui/cli@latest add button`
|
|
37
|
-
- the packaged CLI ships compiled runtime files plus Button templates so it can execute from `node_modules` in a real `npx`-style flow
|
|
38
|
-
- `init` ensures `src/styles/tokens.css`, `src/styles/trine-consumer.css`, `src/app/components/ui/index.ts`, the local `@trine/ui` alias, and local stylesheet wiring
|
|
39
|
-
- consumer-owned component destination files cause a clear failure
|
|
40
|
-
- existing shared baseline files (`tokens.css` and `trine-consumer.css`) are preserved so a second component can be added into the same target repo
|
|
41
|
-
- the command copies consumer-owned source instead of wiring runtime back to `packages/ui`
|
|
42
|
-
- for `apps/demo`, `@trine/ui` resolves locally for delivered components while `@trine/ui/*` temporarily bridges non-localized components back to the authoring source
|
|
43
|
-
- the delivered shared styling baseline is `tokens.css` + `trine-consumer.css`
|
|
44
|
-
- the current proven target dependency baseline is Angular 21, Tailwind CSS v4, and `class-variance-authority`
|
|
45
|
-
- the current proven target shape accepts a global stylesheet entry such as `src/styles.scss`, `src/styles.css`, or `src/global.scss` when it is resolved from `angular.json`
|
|
46
|
-
- use a Node LTS line supported by Angular 21 in the target repo; odd-numbered Node releases can build with warnings
|
|
47
|
-
- when `package.json` is present in the target root, the CLI warns if Tailwind CSS v4 or `class-variance-authority` are missing
|
|
144
|
+
### 3. Render a small proof
|
|
48
145
|
|
|
49
|
-
|
|
146
|
+
Import `TrineButton` from local `@trine/ui` and render a small proof surface:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { Component } from '@angular/core';
|
|
150
|
+
import { TrineButton } from '@trine/ui';
|
|
151
|
+
|
|
152
|
+
@Component({
|
|
153
|
+
selector: 'app-root',
|
|
154
|
+
imports: [TrineButton],
|
|
155
|
+
template: `
|
|
156
|
+
<trine-button>Primary action</trine-button>
|
|
157
|
+
<trine-button variant="secondary">Secondary action</trine-button>
|
|
158
|
+
<trine-button [loading]="true">Saving</trine-button>
|
|
159
|
+
`,
|
|
160
|
+
})
|
|
161
|
+
export class App {}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 4. Build the target app
|
|
50
165
|
|
|
51
166
|
```bash
|
|
52
|
-
|
|
53
|
-
npx --yes --package /absolute/path/to/trineui-cli-<version>.tgz trine add button
|
|
167
|
+
npm run build
|
|
54
168
|
```
|
|
169
|
+
|
|
170
|
+
Successful verification usually means:
|
|
171
|
+
|
|
172
|
+
- the app builds
|
|
173
|
+
- `@trine/ui` resolves to local consumer source
|
|
174
|
+
- the resolved stylesheet entry contains a Trine-managed baseline block
|
|
175
|
+
- Button renders from local delivered files
|
|
176
|
+
|
|
177
|
+
## Customize Locally
|
|
178
|
+
|
|
179
|
+
Trine uses a copy-paste ownership model.
|
|
180
|
+
|
|
181
|
+
After delivery, the consumer repo owns the visual layer.
|
|
182
|
+
|
|
183
|
+
The two main local customization paths are:
|
|
184
|
+
|
|
185
|
+
- edit `src/app/components/ui/button/button.skin.ts`
|
|
186
|
+
- add semantic variable overrides below the Trine-managed block in the resolved global stylesheet entry
|
|
187
|
+
|
|
188
|
+
This is the intended model. Trine does not keep runtime ownership of the Button visuals after delivery.
|
|
189
|
+
|
|
190
|
+
## Current Limitations
|
|
191
|
+
|
|
192
|
+
- Button-only public baseline
|
|
193
|
+
- manual prerequisites still required
|
|
194
|
+
- usage surface is not scaffolded automatically
|
|
195
|
+
- Angular has the strongest live proof coverage; Ionic Angular coverage is narrower
|
|
196
|
+
- multiple target apps still require `--target` or guided selection
|
|
197
|
+
- `src/styles.scss` and `src/global.scss` currently work, but Angular 21 surfaces a Sass deprecation warning for `@import 'tailwindcss';`
|
|
198
|
+
- the baseline is intentionally narrow and not yet a broader component platform
|
|
199
|
+
|
|
200
|
+
## Full Walkthrough
|
|
201
|
+
|
|
202
|
+
For a fuller repo-level walkthrough of the current baseline, see:
|
|
203
|
+
|
|
204
|
+
- `docs/01-overview/04-cli-getting-started.md`
|
package/dist/add-component.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { TEMPLATE_ROOT, ensureLinesFile,
|
|
3
|
+
import { TEMPLATE_ROOT, ensureLinesFile, ensureTrineStylesheetBaseline, ensureTsconfigAlias, isDemoTarget, readTargetDependencyWarnings, resolveSupportedProjectTarget, toTargetRelativePath, } from "./project.js";
|
|
4
4
|
const DEMO_BRIDGE_COMMENT = '// Temporary demo verification bridge: delivered local components resolve locally; other components still re-export from the authoring source.';
|
|
5
5
|
const DEMO_LOCAL_COMPONENTS = [
|
|
6
6
|
{
|
|
@@ -76,10 +76,9 @@ export function addComponent(manifest, options) {
|
|
|
76
76
|
copyFileSync(source, destination);
|
|
77
77
|
}
|
|
78
78
|
const componentCopiedFiles = componentCopyTargets.map(({ destination }) => toTargetRelativePath(targetRoot, destination));
|
|
79
|
-
const
|
|
80
|
-
const copiedFiles = [...componentCopiedFiles, ...sharedStylesResult.createdFiles];
|
|
79
|
+
const copiedFiles = [...componentCopiedFiles];
|
|
81
80
|
const updatedFiles = [];
|
|
82
|
-
const warnings = [
|
|
81
|
+
const warnings = [];
|
|
83
82
|
const componentBarrelPath = path.join(componentDestDir, 'index.ts');
|
|
84
83
|
if (ensureLinesFile(componentBarrelPath, [manifest.barrelLine])) {
|
|
85
84
|
updatedFiles.push(toTargetRelativePath(targetRoot, componentBarrelPath));
|
|
@@ -94,12 +93,13 @@ export function addComponent(manifest, options) {
|
|
|
94
93
|
if (ensureTsconfigAlias(targetTsconfig, targetRoot, options.cwd)) {
|
|
95
94
|
updatedFiles.push(toTargetRelativePath(targetRoot, targetTsconfig));
|
|
96
95
|
}
|
|
97
|
-
const stylesResult =
|
|
96
|
+
const stylesResult = ensureTrineStylesheetBaseline(targetRoot, targetStylesEntry);
|
|
98
97
|
if (stylesResult.updated) {
|
|
99
98
|
updatedFiles.push(toTargetRelativePath(targetRoot, targetStylesEntry));
|
|
100
99
|
}
|
|
100
|
+
warnings.push(...stylesResult.warnings);
|
|
101
101
|
if (stylesResult.authoringImportStillPresent) {
|
|
102
|
-
warnings.push(`${toTargetRelativePath(targetRoot, targetStylesEntry)}
|
|
102
|
+
warnings.push(`${toTargetRelativePath(targetRoot, targetStylesEntry)} keeps the repo-local @trine/ui/styles/trine.css authoring entry so apps/demo can still validate non-localized components during local development.`);
|
|
103
103
|
}
|
|
104
104
|
if (isDemoTarget(targetRoot)) {
|
|
105
105
|
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.');
|
package/dist/index.js
CHANGED
|
@@ -1,38 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { addButton } from "./add-button.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { applyInitPlan, planInitProject } from "./init.js";
|
|
5
|
+
import { chooseFromList, confirmAction } from "./prompt.js";
|
|
6
|
+
import { findAngularAppTargets, inspectProjectTarget, looksLikeAngularAppRoot } from "./project.js";
|
|
6
7
|
const HELP_TEXT = `Usage:
|
|
7
|
-
npx @trineui/cli@latest init [--target <app-root>]
|
|
8
|
+
npx @trineui/cli@latest init [--target <app-root>] [--yes]
|
|
8
9
|
npx @trineui/cli@latest add button [--target <app-root>]
|
|
9
|
-
trine init [--target <app-root>]
|
|
10
|
+
trine init [--target <app-root>] [--yes]
|
|
10
11
|
trine add button [--target <app-root>]
|
|
11
12
|
|
|
12
13
|
Defaults:
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- when
|
|
14
|
+
- init is guided by default and asks for confirmation before mutating project files
|
|
15
|
+
- init --yes keeps a non-interactive fast path for automation and scripting
|
|
16
|
+
- current directory is used when it already matches the supported Trine app target shape
|
|
17
|
+
- otherwise init/add auto-detect a single supported Angular app target under the current directory
|
|
18
|
+
- when multiple supported app targets are found, guided init lets you choose and add still asks for --target
|
|
16
19
|
|
|
17
20
|
Notes:
|
|
18
21
|
- v0 supports init plus add button only
|
|
19
|
-
- init owns target detection, framework detection, stylesheet resolution,
|
|
20
|
-
- add button still works without init for backward compatibility, but init is
|
|
22
|
+
- init owns target detection, framework detection, stylesheet resolution, Trine-managed global stylesheet injection, and local @trine/ui alias setup
|
|
23
|
+
- add button still works without init for backward compatibility, but init is the preferred first step
|
|
21
24
|
- the current proven target model is Angular 21 + src/app + tsconfig.app.json + a global stylesheet entry such as src/styles.scss, src/styles.css, or src/global.scss resolved directly or from angular.json
|
|
22
25
|
- v0 distinguishes angular and ionic-angular targets; unsupported frameworks fail clearly
|
|
23
|
-
- the current proven styling/runtime baseline requires Tailwind CSS v4 and class-variance-authority in the target repo
|
|
26
|
+
- the current proven styling/runtime baseline requires Tailwind CSS v4, PostCSS with @tailwindcss/postcss, and class-variance-authority in the target repo
|
|
24
27
|
- consumer-owned component files fail clearly if they already exist
|
|
25
|
-
-
|
|
28
|
+
- init injects a Trine-managed styling block into the resolved global stylesheet entry instead of copying separate shared CSS files
|
|
26
29
|
- @trine/ui is configured as a consumer-local alias inside the target app
|
|
27
30
|
- apps/demo keeps a temporary @trine/ui/* bridge for non-localized components during local repo verification`;
|
|
28
31
|
const SUPPORTED_COMPONENTS = ['button'];
|
|
29
|
-
function main(argv) {
|
|
32
|
+
async function main(argv) {
|
|
30
33
|
const [command, secondArg, ...rest] = argv;
|
|
31
34
|
if (command === 'init') {
|
|
32
|
-
const
|
|
33
|
-
const
|
|
35
|
+
const flags = parseFlags(argv.slice(1), ['--target', '--yes']);
|
|
36
|
+
const selection = flags.yes
|
|
37
|
+
? resolveNonInteractiveInitSelection(process.cwd(), flags.target)
|
|
38
|
+
: await resolveGuidedInitSelection(process.cwd(), flags.target);
|
|
39
|
+
const plan = planInitProject({
|
|
34
40
|
target: selection.target,
|
|
35
41
|
cwd: process.cwd(),
|
|
42
|
+
targetStylesEntry: selection.targetStylesEntry,
|
|
43
|
+
});
|
|
44
|
+
if (!hasInitMutations(plan)) {
|
|
45
|
+
printInitNoop(plan, selection);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!flags.yes) {
|
|
49
|
+
printInitPreview(plan, selection);
|
|
50
|
+
const shouldProceed = await confirmAction('Proceed? (Y/n)');
|
|
51
|
+
if (!shouldProceed) {
|
|
52
|
+
console.log('trine init cancelled. No changes were made.');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const result = applyInitPlan(plan, {
|
|
57
|
+
cwd: process.cwd(),
|
|
36
58
|
});
|
|
37
59
|
printInitSuccess(result, selection);
|
|
38
60
|
return;
|
|
@@ -43,23 +65,54 @@ function main(argv) {
|
|
|
43
65
|
if (!isSupportedComponent(secondArg)) {
|
|
44
66
|
throw new Error(secondArg ? `Unsupported component: ${secondArg}\n\n${HELP_TEXT}` : HELP_TEXT);
|
|
45
67
|
}
|
|
46
|
-
const
|
|
68
|
+
const flags = parseFlags(rest, ['--target']);
|
|
69
|
+
const selection = resolveTargetSelection(process.cwd(), flags.target, `trine add ${secondArg}`);
|
|
47
70
|
const result = addButton({
|
|
48
71
|
target: selection.target,
|
|
49
72
|
cwd: process.cwd(),
|
|
50
73
|
});
|
|
51
74
|
printAddSuccess(secondArg, result, selection);
|
|
52
75
|
}
|
|
53
|
-
function
|
|
76
|
+
function resolveNonInteractiveInitSelection(cwd, explicitTarget) {
|
|
77
|
+
const selection = resolveTargetSelection(cwd, explicitTarget, 'trine init');
|
|
78
|
+
const targetRoot = path.resolve(cwd, selection.target);
|
|
79
|
+
const inspection = inspectProjectTarget(targetRoot);
|
|
80
|
+
if (inspection.framework !== 'unsupported' &&
|
|
81
|
+
inspection.targetStylesEntryCandidates.length > 1 &&
|
|
82
|
+
inspection.targetStylesEntryResolution === 'default') {
|
|
83
|
+
const displayTarget = selection.mode === 'cwd' ? '.' : toDisplayTarget(cwd, targetRoot);
|
|
84
|
+
throw new Error([
|
|
85
|
+
`Multiple plausible global stylesheet entries were found for target (${displayTarget}). Re-run without --yes to choose one interactively:`,
|
|
86
|
+
...inspection.targetStylesEntryCandidates.map((candidate) => `- ${toDisplayFilePath(targetRoot, candidate)}`),
|
|
87
|
+
].join('\n'));
|
|
88
|
+
}
|
|
89
|
+
return selection;
|
|
90
|
+
}
|
|
91
|
+
function parseFlags(argv, allowedFlags) {
|
|
92
|
+
const flags = {
|
|
93
|
+
yes: false,
|
|
94
|
+
};
|
|
54
95
|
for (let index = 0; index < argv.length; index += 1) {
|
|
55
|
-
|
|
56
|
-
|
|
96
|
+
const token = argv[index];
|
|
97
|
+
if (!allowedFlags.includes(token)) {
|
|
98
|
+
throw new Error(`Unsupported flag: ${token}\n\n${HELP_TEXT}`);
|
|
99
|
+
}
|
|
100
|
+
if (token === '--yes') {
|
|
101
|
+
flags.yes = true;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (token === '--target') {
|
|
105
|
+
const target = argv[index + 1];
|
|
106
|
+
if (!target || target.startsWith('--')) {
|
|
107
|
+
throw new Error(`--target requires a value.\n\n${HELP_TEXT}`);
|
|
108
|
+
}
|
|
109
|
+
flags.target = target;
|
|
110
|
+
index += 1;
|
|
57
111
|
}
|
|
58
112
|
}
|
|
59
|
-
return
|
|
113
|
+
return flags;
|
|
60
114
|
}
|
|
61
|
-
function resolveTargetSelection(cwd,
|
|
62
|
-
const explicitTarget = readTarget(argv);
|
|
115
|
+
function resolveTargetSelection(cwd, explicitTarget, commandLabel) {
|
|
63
116
|
if (explicitTarget) {
|
|
64
117
|
return {
|
|
65
118
|
target: explicitTarget,
|
|
@@ -90,6 +143,92 @@ function resolveTargetSelection(cwd, argv, commandLabel) {
|
|
|
90
143
|
mode: 'cwd',
|
|
91
144
|
};
|
|
92
145
|
}
|
|
146
|
+
async function resolveGuidedInitSelection(cwd, explicitTarget) {
|
|
147
|
+
const targetSelection = explicitTarget
|
|
148
|
+
? {
|
|
149
|
+
target: explicitTarget,
|
|
150
|
+
mode: 'explicit',
|
|
151
|
+
}
|
|
152
|
+
: await chooseInitTarget(cwd);
|
|
153
|
+
const targetRoot = path.resolve(cwd, targetSelection.target);
|
|
154
|
+
const inspection = inspectProjectTarget(targetRoot);
|
|
155
|
+
if (inspection.framework === 'unsupported') {
|
|
156
|
+
return targetSelection;
|
|
157
|
+
}
|
|
158
|
+
if (inspection.targetStylesEntryCandidates.length > 1 &&
|
|
159
|
+
inspection.targetStylesEntryResolution === 'default') {
|
|
160
|
+
const targetStylesEntry = await chooseFromList('Multiple plausible global stylesheet entries were found:', inspection.targetStylesEntryCandidates, {
|
|
161
|
+
renderItem: (item, index) => `${String(index + 1)}. ${toDisplayFilePath(targetRoot, item)}`,
|
|
162
|
+
});
|
|
163
|
+
return {
|
|
164
|
+
...targetSelection,
|
|
165
|
+
targetStylesEntry,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return targetSelection;
|
|
169
|
+
}
|
|
170
|
+
async function chooseInitTarget(cwd) {
|
|
171
|
+
if (looksLikeAngularAppRoot(cwd)) {
|
|
172
|
+
return {
|
|
173
|
+
target: '.',
|
|
174
|
+
mode: 'cwd',
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const matches = findAngularAppTargets(cwd);
|
|
178
|
+
if (matches.length === 1) {
|
|
179
|
+
return {
|
|
180
|
+
target: matches[0],
|
|
181
|
+
mode: 'auto-detected',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (matches.length > 1) {
|
|
185
|
+
const selected = await chooseFromList('Multiple supported apps found:', matches);
|
|
186
|
+
return {
|
|
187
|
+
target: selected,
|
|
188
|
+
mode: 'selected',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
target: '.',
|
|
193
|
+
mode: 'cwd',
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function printInitPreview(plan, selection) {
|
|
197
|
+
const displayTarget = toDisplayTarget(process.cwd(), plan.targetRoot);
|
|
198
|
+
const stylesheetDisplay = toDisplayFilePath(plan.targetRoot, plan.targetStylesEntry);
|
|
199
|
+
const lines = [
|
|
200
|
+
`Detected target: ${displayTarget}`,
|
|
201
|
+
`Detected framework: ${plan.framework}`,
|
|
202
|
+
`Detected stylesheet entry: ${stylesheetDisplay}`,
|
|
203
|
+
];
|
|
204
|
+
if (selection.mode === 'auto-detected' || selection.mode === 'selected') {
|
|
205
|
+
lines.push(`Selected app: ${displayTarget}`);
|
|
206
|
+
}
|
|
207
|
+
lines.push('', 'Trine will:', ...renderPlannedInitActions(plan));
|
|
208
|
+
if (plan.warnings.length > 0) {
|
|
209
|
+
lines.push('', 'Warnings:', ...plan.warnings.map((warning) => `- ${warning}`));
|
|
210
|
+
}
|
|
211
|
+
console.log(lines.join('\n'));
|
|
212
|
+
}
|
|
213
|
+
function printInitNoop(plan, selection) {
|
|
214
|
+
const displayTarget = toDisplayTarget(process.cwd(), plan.targetRoot);
|
|
215
|
+
const stylesheetDisplay = toDisplayFilePath(plan.targetRoot, plan.targetStylesEntry);
|
|
216
|
+
const lines = [
|
|
217
|
+
'trine init found the Trine baseline already in place.',
|
|
218
|
+
`Target: ${displayTarget}`,
|
|
219
|
+
`Framework: ${plan.framework}`,
|
|
220
|
+
`Resolved stylesheet entry: ${stylesheetDisplay}`,
|
|
221
|
+
];
|
|
222
|
+
if (selection.mode === 'auto-detected' || selection.mode === 'selected') {
|
|
223
|
+
lines.push(`Selected app: ${displayTarget}`);
|
|
224
|
+
}
|
|
225
|
+
lines.push('', 'Already in place:', ...renderFileGroup(plan.reusedFiles, '- nothing to report'));
|
|
226
|
+
if (plan.warnings.length > 0) {
|
|
227
|
+
lines.push('', 'Warnings:', ...plan.warnings.map((warning) => `- ${warning}`));
|
|
228
|
+
}
|
|
229
|
+
lines.push('', 'No changes were made.');
|
|
230
|
+
console.log(lines.join('\n'));
|
|
231
|
+
}
|
|
93
232
|
function printInitSuccess(result, selection) {
|
|
94
233
|
const displayTarget = toDisplayTarget(process.cwd(), result.targetRoot);
|
|
95
234
|
const stylesheetDisplay = toDisplayFilePath(result.targetRoot, result.targetStylesEntry);
|
|
@@ -99,14 +238,14 @@ function printInitSuccess(result, selection) {
|
|
|
99
238
|
`Framework: ${result.framework}`,
|
|
100
239
|
`Resolved stylesheet entry: ${stylesheetDisplay}`,
|
|
101
240
|
];
|
|
102
|
-
if (selection.mode === 'auto-detected') {
|
|
241
|
+
if (selection.mode === 'auto-detected' || selection.mode === 'selected') {
|
|
103
242
|
lines.push(`Selected app: ${displayTarget}`);
|
|
104
243
|
}
|
|
105
244
|
lines.push('', 'Created:', ...renderFileGroup(result.createdFiles, '- nothing created'), '', 'Reused:', ...renderFileGroup(result.reusedFiles, '- nothing reused'), '', 'Updated:', ...renderFileGroup(result.updatedFiles, '- nothing updated'));
|
|
106
245
|
if (result.warnings.length > 0) {
|
|
107
246
|
lines.push('', 'Warnings:', ...result.warnings.map((warning) => `- ${warning}`));
|
|
108
247
|
}
|
|
109
|
-
lines.push('', 'Manual next steps:', '- Review prerequisite warnings and install anything still missing before building the target app.', '- Run trine add button when you want to deliver the first Trine component into this project.', '- Build the target app and confirm @trine/ui resolves through the local consumer-owned source tree.');
|
|
248
|
+
lines.push('', 'Manual next steps:', '- Review prerequisite warnings and install anything still missing before building the target app.', '- Run trine add button when you want to deliver the first Trine component into this project.', '- Build the target app and confirm @trine/ui resolves through the local consumer-owned source tree.', '- If you want token-level customization, add semantic variable overrides below the Trine-managed block in the global stylesheet instead of editing inside the managed block.');
|
|
110
249
|
console.log(lines.join('\n'));
|
|
111
250
|
}
|
|
112
251
|
function printAddSuccess(component, result, selection) {
|
|
@@ -121,7 +260,7 @@ function printAddSuccess(component, result, selection) {
|
|
|
121
260
|
if (result.warnings.length > 0) {
|
|
122
261
|
lines.push('', 'Warnings:', ...result.warnings.map((warning) => `- ${warning}`));
|
|
123
262
|
}
|
|
124
|
-
lines.push('', 'Manual next steps:', `- Ensure the target repo has Tailwind CSS v4 and class-variance-authority installed before building the delivered ${componentLabel}.`, `- Build the target app and confirm ${componentLabel} imports resolve through the local @trine/ui alias.`, `- Review the copied ${component}.skin.ts
|
|
263
|
+
lines.push('', 'Manual next steps:', `- Ensure the target repo has Tailwind CSS v4, PostCSS with @tailwindcss/postcss, and class-variance-authority installed before building the delivered ${componentLabel}.`, `- Build the target app and confirm ${componentLabel} imports resolve through the local @trine/ui alias.`, `- Review the copied ${component}.skin.ts if you want component-level local customization.`, '- Add semantic token overrides below the Trine-managed global stylesheet block if you want app-level visual customization that survives rerunning init.', '- For future setup in this repo, prefer running trine init first so the project-level Trine baseline is already in place.');
|
|
125
264
|
if (isRepoDemoVerification) {
|
|
126
265
|
lines.push('- Open /validation-shell and review the CLI delivery proof section for the temporary demo verification path.');
|
|
127
266
|
}
|
|
@@ -130,6 +269,16 @@ function printAddSuccess(component, result, selection) {
|
|
|
130
269
|
function renderFileGroup(files, emptyMessage) {
|
|
131
270
|
return files.length > 0 ? files.map((file) => `- ${file}`) : [emptyMessage];
|
|
132
271
|
}
|
|
272
|
+
function renderPlannedInitActions(plan) {
|
|
273
|
+
return [
|
|
274
|
+
...plan.createdFiles.map((file) => `- create ${file}`),
|
|
275
|
+
...plan.updatedFiles.map((file) => `- update ${file}`),
|
|
276
|
+
...plan.reusedFiles.map((file) => `- keep ${file}`),
|
|
277
|
+
];
|
|
278
|
+
}
|
|
279
|
+
function hasInitMutations(plan) {
|
|
280
|
+
return plan.createdFiles.length > 0 || plan.updatedFiles.length > 0;
|
|
281
|
+
}
|
|
133
282
|
function isSupportedComponent(value) {
|
|
134
283
|
return SUPPORTED_COMPONENTS.some((component) => component === value);
|
|
135
284
|
}
|
|
@@ -143,11 +292,8 @@ function toDisplayTarget(cwd, targetRoot) {
|
|
|
143
292
|
function toDisplayFilePath(targetRoot, filePath) {
|
|
144
293
|
return path.relative(targetRoot, filePath) || '.';
|
|
145
294
|
}
|
|
146
|
-
|
|
147
|
-
main(process.argv.slice(2));
|
|
148
|
-
}
|
|
149
|
-
catch (error) {
|
|
295
|
+
await main(process.argv.slice(2)).catch((error) => {
|
|
150
296
|
const message = error instanceof Error ? error.message : String(error);
|
|
151
297
|
console.error(message);
|
|
152
298
|
process.exitCode = 1;
|
|
153
|
-
}
|
|
299
|
+
});
|
package/dist/init.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import {
|
|
3
|
-
export function
|
|
2
|
+
import { ensureTrineStylesheetBaseline, ensureTsconfigAlias, ensureUiRootBarrel, inspectTrineStylesheetBaseline, inspectTsconfigAlias, inspectUiRootBarrel, isDemoTarget, readTargetDependencyWarnings, resolveSupportedProjectTarget, toTargetRelativePath, } from "./project.js";
|
|
3
|
+
export function planInitProject(options) {
|
|
4
4
|
const targetRoot = path.resolve(options.cwd, options.target);
|
|
5
|
-
const { framework, targetStylesEntry, targetTsconfig } = resolveSupportedProjectTarget('trine init', targetRoot
|
|
5
|
+
const { framework, targetStylesEntry, targetTsconfig } = resolveSupportedProjectTarget('trine init', targetRoot, {
|
|
6
|
+
preferredStylesEntry: options.targetStylesEntry,
|
|
7
|
+
});
|
|
6
8
|
const createdFiles = [];
|
|
7
9
|
const updatedFiles = [];
|
|
8
10
|
const reusedFiles = [];
|
|
9
11
|
const warnings = [];
|
|
10
|
-
const
|
|
11
|
-
createdFiles.push(...sharedStylesResult.createdFiles);
|
|
12
|
-
reusedFiles.push(...sharedStylesResult.reusedFiles);
|
|
13
|
-
warnings.push(...sharedStylesResult.warnings);
|
|
14
|
-
const uiRootResult = ensureUiRootBarrel(targetRoot);
|
|
12
|
+
const uiRootResult = inspectUiRootBarrel(targetRoot);
|
|
15
13
|
if (uiRootResult.created) {
|
|
16
14
|
createdFiles.push(uiRootResult.file);
|
|
17
15
|
}
|
|
@@ -19,22 +17,23 @@ export function initProject(options) {
|
|
|
19
17
|
reusedFiles.push(uiRootResult.file);
|
|
20
18
|
}
|
|
21
19
|
const tsconfigDisplayPath = toTargetRelativePath(targetRoot, targetTsconfig);
|
|
22
|
-
if (
|
|
20
|
+
if (inspectTsconfigAlias(targetTsconfig, targetRoot, options.cwd).needsUpdate) {
|
|
23
21
|
updatedFiles.push(tsconfigDisplayPath);
|
|
24
22
|
}
|
|
25
23
|
else {
|
|
26
24
|
reusedFiles.push(tsconfigDisplayPath);
|
|
27
25
|
}
|
|
28
26
|
const stylesDisplayPath = toTargetRelativePath(targetRoot, targetStylesEntry);
|
|
29
|
-
const stylesResult =
|
|
30
|
-
if (stylesResult.
|
|
27
|
+
const stylesResult = inspectTrineStylesheetBaseline(targetRoot, targetStylesEntry);
|
|
28
|
+
if (stylesResult.needsUpdate) {
|
|
31
29
|
updatedFiles.push(stylesDisplayPath);
|
|
32
30
|
}
|
|
33
31
|
else {
|
|
34
32
|
reusedFiles.push(stylesDisplayPath);
|
|
35
33
|
}
|
|
34
|
+
warnings.push(...stylesResult.warnings);
|
|
36
35
|
if (stylesResult.authoringImportStillPresent) {
|
|
37
|
-
warnings.push(`${stylesDisplayPath}
|
|
36
|
+
warnings.push(`${stylesDisplayPath} keeps the repo-local @trine/ui/styles/trine.css authoring entry so apps/demo can still validate non-localized components during local development.`);
|
|
38
37
|
}
|
|
39
38
|
if (isDemoTarget(targetRoot)) {
|
|
40
39
|
warnings.push('apps/demo keeps a temporary @trine/ui/* bridge for non-localized components so the internal demo app can still build while local Trine source is verified.');
|
|
@@ -44,6 +43,7 @@ export function initProject(options) {
|
|
|
44
43
|
}
|
|
45
44
|
return {
|
|
46
45
|
targetRoot,
|
|
46
|
+
targetTsconfig,
|
|
47
47
|
framework,
|
|
48
48
|
targetStylesEntry,
|
|
49
49
|
createdFiles,
|
|
@@ -52,3 +52,14 @@ export function initProject(options) {
|
|
|
52
52
|
warnings,
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
|
+
export function applyInitPlan(plan, options) {
|
|
56
|
+
ensureUiRootBarrel(plan.targetRoot);
|
|
57
|
+
ensureTsconfigAlias(plan.targetTsconfig, plan.targetRoot, options.cwd);
|
|
58
|
+
ensureTrineStylesheetBaseline(plan.targetRoot, plan.targetStylesEntry);
|
|
59
|
+
return plan;
|
|
60
|
+
}
|
|
61
|
+
export function initProject(options) {
|
|
62
|
+
return applyInitPlan(planInitProject(options), {
|
|
63
|
+
cwd: options.cwd,
|
|
64
|
+
});
|
|
65
|
+
}
|