frakment 0.0.0-stage → 0.0.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/LICENSE +190 -0
- package/README.md +87 -2
- package/bin/index.js +50 -0
- package/package.json +30 -4
- package/src/commands/add.js +142 -0
- package/src/commands/init.js +201 -0
- package/src/commands/list.js +61 -0
- package/src/utils/detect.js +38 -0
- package/templates/components.json +11 -0
- package/templates/lib/animation.ts +12 -0
- package/templates/theme.css +653 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Frakment License
|
|
2
|
+
Version 1.0
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 hanshapd <https://github.com/hanshapd>
|
|
5
|
+
|
|
6
|
+
This is a source-available license. It is NOT an open-source license
|
|
7
|
+
as defined by the Open Source Initiative (OSI).
|
|
8
|
+
|
|
9
|
+
In short:
|
|
10
|
+
|
|
11
|
+
- Use Frakment freely for personal, educational, and non-commercial
|
|
12
|
+
purposes. Just keep the attribution.
|
|
13
|
+
|
|
14
|
+
- Use Frakment commercially only after you get one pull request
|
|
15
|
+
merged into the main repository. That is the only path.
|
|
16
|
+
|
|
17
|
+
- Always keep the attribution. Always mark modifications.
|
|
18
|
+
|
|
19
|
+
====================================================================
|
|
20
|
+
1. DEFINITIONS
|
|
21
|
+
====================================================================
|
|
22
|
+
|
|
23
|
+
"Software" means the source code, components, CLI, MCP server, agent
|
|
24
|
+
skills, documentation, and any associated files in this repository,
|
|
25
|
+
whether used in original or modified form.
|
|
26
|
+
|
|
27
|
+
"Personal Use" means use by an individual for personal projects,
|
|
28
|
+
learning, experimentation, hobby work, or internal tools that do not
|
|
29
|
+
generate revenue.
|
|
30
|
+
|
|
31
|
+
"Educational Use" means use by students, teachers, or institutions
|
|
32
|
+
for teaching, coursework, academic research, or non-commercial
|
|
33
|
+
educational activities.
|
|
34
|
+
|
|
35
|
+
"Commercial Use" means any use that:
|
|
36
|
+
|
|
37
|
+
(a) is part of a product, service, or website that generates revenue
|
|
38
|
+
directly or indirectly (including ads, subscriptions, paid
|
|
39
|
+
features, consulting, or paid support);
|
|
40
|
+
(b) is deployed in production by a for-profit entity; or
|
|
41
|
+
(c) is used to build something that is sold, licensed, or monetized
|
|
42
|
+
in any form.
|
|
43
|
+
|
|
44
|
+
"Qualifying Contribution" means a pull request that has been
|
|
45
|
+
reviewed and merged into the main branch of
|
|
46
|
+
github.com/hanshapd/frakment, where all of the following are true:
|
|
47
|
+
|
|
48
|
+
(a) The commit author is you (your GitHub username or email is
|
|
49
|
+
recorded in the commit metadata).
|
|
50
|
+
(b) The pull request makes at least one substantive change: fixes a
|
|
51
|
+
bug, adds a feature, adds or corrects documentation, adds a
|
|
52
|
+
test, translates content, or fixes a typo.
|
|
53
|
+
(c) The pull request has not been reverted within 30 days of being
|
|
54
|
+
merged.
|
|
55
|
+
|
|
56
|
+
There is no minimum number of lines changed. A single-line typo fix
|
|
57
|
+
qualifies. A documentation clarification qualifies. A test addition
|
|
58
|
+
qualifies.
|
|
59
|
+
|
|
60
|
+
A pull request that was closed without merging does NOT qualify. A
|
|
61
|
+
pull request that was merged and later reverted within 30 days does
|
|
62
|
+
NOT qualify.
|
|
63
|
+
|
|
64
|
+
====================================================================
|
|
65
|
+
2. GRANT OF RIGHTS
|
|
66
|
+
====================================================================
|
|
67
|
+
|
|
68
|
+
Subject to the terms of this license, you are granted permission to:
|
|
69
|
+
|
|
70
|
+
(a) View, read, and study the source code.
|
|
71
|
+
(b) Fork, clone, and copy the repository.
|
|
72
|
+
(c) Modify the source code.
|
|
73
|
+
(d) Distribute the original or modified version.
|
|
74
|
+
|
|
75
|
+
These rights apply without additional conditions for Personal Use and
|
|
76
|
+
Educational Use, provided you satisfy the Attribution Requirements in
|
|
77
|
+
Section 3.
|
|
78
|
+
|
|
79
|
+
These rights apply for Commercial Use only after you have made a
|
|
80
|
+
Qualifying Contribution, in addition to the Attribution Requirements.
|
|
81
|
+
|
|
82
|
+
====================================================================
|
|
83
|
+
3. ATTRIBUTION REQUIREMENTS (ALWAYS)
|
|
84
|
+
====================================================================
|
|
85
|
+
|
|
86
|
+
Any use, copy, modification, or distribution of the Software, in
|
|
87
|
+
whole or in part, for Personal, Educational, or Commercial Use, must:
|
|
88
|
+
|
|
89
|
+
(a) Include the following attribution in the README, documentation,
|
|
90
|
+
footer, or another reasonably visible user-facing location:
|
|
91
|
+
|
|
92
|
+
Based on Frakment by hanshapd
|
|
93
|
+
https://github.com/hanshapd/frakment
|
|
94
|
+
|
|
95
|
+
(b) Include this LICENSE file unmodified in all copies and
|
|
96
|
+
derivative works.
|
|
97
|
+
|
|
98
|
+
(c) Clearly mark any modified versions as modified, with a brief
|
|
99
|
+
description of the changes (e.g., in a CHANGELOG or README).
|
|
100
|
+
|
|
101
|
+
====================================================================
|
|
102
|
+
4. COMMERCIAL USE REQUIREMENT
|
|
103
|
+
====================================================================
|
|
104
|
+
|
|
105
|
+
Before using the Software for any Commercial Use, you (or the legal
|
|
106
|
+
entity you represent) must have made one Qualifying Contribution as
|
|
107
|
+
defined in Section 1. This is the only path to Commercial Use.
|
|
108
|
+
|
|
109
|
+
--------------------------------------------------------------------
|
|
110
|
+
4.1 SCOPE OF SATISFACTION
|
|
111
|
+
--------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
Once a Qualifying Contribution has been made, the requirement is
|
|
114
|
+
satisfied permanently for:
|
|
115
|
+
|
|
116
|
+
- the individual who made the contribution, OR
|
|
117
|
+
- the legal entity (company, organization, or team) on whose
|
|
118
|
+
behalf the contribution was made,
|
|
119
|
+
|
|
120
|
+
for all future Commercial Use of the Software by that individual or
|
|
121
|
+
entity. No further contributions are required for future commercial
|
|
122
|
+
use by the same party.
|
|
123
|
+
|
|
124
|
+
--------------------------------------------------------------------
|
|
125
|
+
4.2 EVIDENCE ON REQUEST
|
|
126
|
+
--------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
If hanshapd requests proof that the Commercial Use requirement has
|
|
129
|
+
been satisfied, you must provide, within 30 days, a link to the
|
|
130
|
+
merged pull request on GitHub.
|
|
131
|
+
|
|
132
|
+
Refusal or failure to provide evidence on request voids the
|
|
133
|
+
permission for Commercial Use until evidence is provided.
|
|
134
|
+
|
|
135
|
+
--------------------------------------------------------------------
|
|
136
|
+
4.3 NO REQUIREMENT FOR NON-COMMERCIAL USE
|
|
137
|
+
--------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
Personal Use and Educational Use do NOT require a Qualifying
|
|
140
|
+
Contribution. Attribution (Section 3) is the only requirement for
|
|
141
|
+
those uses.
|
|
142
|
+
|
|
143
|
+
====================================================================
|
|
144
|
+
5. RESTRICTIONS
|
|
145
|
+
====================================================================
|
|
146
|
+
|
|
147
|
+
You may not:
|
|
148
|
+
|
|
149
|
+
(a) Remove, hide, or alter copyright notices or the attribution
|
|
150
|
+
required by Section 3.
|
|
151
|
+
(b) Claim authorship of this Software or of any derivative work.
|
|
152
|
+
(c) Use the names "Frakment", "frakment", or "hanshapd", or any
|
|
153
|
+
associated logos or branding, to endorse or promote derivative
|
|
154
|
+
works without prior written permission.
|
|
155
|
+
(d) Sublicense this Software under different terms. Any party that
|
|
156
|
+
receives a copy from you does so subject to this same license.
|
|
157
|
+
|
|
158
|
+
====================================================================
|
|
159
|
+
6. NO TRADEMARK LICENSE
|
|
160
|
+
====================================================================
|
|
161
|
+
|
|
162
|
+
This license does not grant any rights to use the names "Frakment",
|
|
163
|
+
"frakment", or "hanshapd", or any associated logos or marks, except
|
|
164
|
+
as strictly necessary for the attribution described in Section 3.
|
|
165
|
+
|
|
166
|
+
====================================================================
|
|
167
|
+
7. WARRANTY
|
|
168
|
+
====================================================================
|
|
169
|
+
|
|
170
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
171
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
172
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
|
|
173
|
+
NON-INFRINGEMENT.
|
|
174
|
+
|
|
175
|
+
IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY
|
|
176
|
+
CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
177
|
+
TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE
|
|
178
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
179
|
+
|
|
180
|
+
====================================================================
|
|
181
|
+
8. ACCEPTANCE
|
|
182
|
+
====================================================================
|
|
183
|
+
|
|
184
|
+
By accessing, using, copying, modifying, or distributing this project,
|
|
185
|
+
you agree to be bound by the terms above. If you do not agree, you are
|
|
186
|
+
not granted permission to use this project and must delete all copies
|
|
187
|
+
in your possession.
|
|
188
|
+
|
|
189
|
+
For permission requests or questions about this license, contact
|
|
190
|
+
hanshapd on GitHub: https://github.com/hanshapd
|
package/README.md
CHANGED
|
@@ -1,3 +1,88 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frakment CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Monochrome design system and component registry CLI.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js >= 22.12.0
|
|
8
|
+
- Tailwind CSS v4 (`@import "tailwindcss";` in your `globals.css`)
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
### `frakment init`
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx frakment@latest init
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
What it does:
|
|
19
|
+
|
|
20
|
+
1. Writes `components.json` at project root (CLI config)
|
|
21
|
+
2. Injects the theme block into `globals.css` between `/* ==== FRAKMENT THEME START/END ==== */` markers (design tokens + 9 `@utility` + `.dark` + `@custom-variant dark`)
|
|
22
|
+
3. Preserves anything outside the markers — your `@import` statements, overrides, and custom CSS stay untouched
|
|
23
|
+
4. Creates `lib/utils.ts` (the `cn` helper) if missing
|
|
24
|
+
5. Installs 6 dependencies: `clsx`, `tailwind-merge`, `class-variance-authority`, `radix-ui`, `lucide-react`, `motion`
|
|
25
|
+
|
|
26
|
+
**Idempotent.** Safe to re-run:
|
|
27
|
+
|
|
28
|
+
- Existing `components.json` is preserved unless you choose to overwrite
|
|
29
|
+
- Marker block is replaced in place (no duplicates)
|
|
30
|
+
- Legacy inline theme blocks (from older `init` versions) are auto-migrated
|
|
31
|
+
|
|
32
|
+
### `frakment add <component>`
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx frakment@latest add button
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Fetches the component (plus all `registryDependencies` recursively) from the registry, writes all files, and installs any missing npm dependencies.
|
|
39
|
+
|
|
40
|
+
Flags:
|
|
41
|
+
|
|
42
|
+
- `--registry <url>` — override the registry URL (default: from `components.json`)
|
|
43
|
+
- `--overwrite` — overwrite existing files without prompting
|
|
44
|
+
|
|
45
|
+
### `frakment list`
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx frakment@latest list
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Lists all available components from the registry.
|
|
52
|
+
|
|
53
|
+
## Registry
|
|
54
|
+
|
|
55
|
+
The default registry URL is written to `components.json` during `init`. If the public registry is not live yet, pass `--registry <url>` explicitly:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx frakment@latest add badge --registry http://localhost:8765
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Troubleshooting
|
|
62
|
+
|
|
63
|
+
### "Frakment requires Tailwind v4"
|
|
64
|
+
|
|
65
|
+
Your `globals.css` must contain `@import "tailwindcss";`. Frakment relies on Tailwind v4 features (`@theme`, `@utility`, `@custom-variant`). Tailwind v3 is not supported.
|
|
66
|
+
|
|
67
|
+
### "Could not find globals.css"
|
|
68
|
+
|
|
69
|
+
The CLI looks for one of:
|
|
70
|
+
|
|
71
|
+
- `app/globals.css`
|
|
72
|
+
- `src/app/globals.css`
|
|
73
|
+
- `styles/globals.css`
|
|
74
|
+
- `src/styles/globals.css`
|
|
75
|
+
|
|
76
|
+
If none exist, create one with at minimum:
|
|
77
|
+
|
|
78
|
+
```css
|
|
79
|
+
@import "tailwindcss";
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `src/` directory projects
|
|
83
|
+
|
|
84
|
+
If your `globals.css` lives at `src/app/globals.css`, component files are written to `src/components/`, `src/lib/`, `src/hooks/`. Binary assets under `public/` are written to `public/` at the project root (Next.js serves `public/` from the root regardless of `src/` layout).
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
Source-available. See [LICENSE](./LICENSE). Commercial use requires at least one merged pull request to [arrafipd/frakment](https://github.com/arrafipd/frakment).
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
|
|
7
|
+
import { add } from "../src/commands/add.js";
|
|
8
|
+
import { init } from "../src/commands/init.js";
|
|
9
|
+
import { list } from "../src/commands/list.js";
|
|
10
|
+
|
|
11
|
+
const __dirname = import.meta.dirname;
|
|
12
|
+
const pkg = JSON.parse(
|
|
13
|
+
readFileSync(path.resolve(__dirname, "../package.json"), "utf-8")
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const program = new Command();
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.name("frakment")
|
|
20
|
+
.description("Monochrome design system and component registry")
|
|
21
|
+
.version(pkg.version)
|
|
22
|
+
.addHelpText(
|
|
23
|
+
"after",
|
|
24
|
+
`
|
|
25
|
+
Examples:
|
|
26
|
+
${pc.dim("$ frakment init")}
|
|
27
|
+
${pc.dim("$ frakment add status-chip")}
|
|
28
|
+
${pc.dim("$ frakment list")}
|
|
29
|
+
`
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command("init")
|
|
34
|
+
.description("Initialize frakment in your project")
|
|
35
|
+
.action(init);
|
|
36
|
+
|
|
37
|
+
program
|
|
38
|
+
.command("add <component>")
|
|
39
|
+
.description("Add a component from the frakment registry")
|
|
40
|
+
.option("--registry <url>", "Custom registry URL")
|
|
41
|
+
.option("--overwrite", "Overwrite existing files", false)
|
|
42
|
+
.action(add);
|
|
43
|
+
|
|
44
|
+
program
|
|
45
|
+
.command("list")
|
|
46
|
+
.description("List available components")
|
|
47
|
+
.option("--registry <url>", "Custom registry URL")
|
|
48
|
+
.action(list);
|
|
49
|
+
|
|
50
|
+
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frakment",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "CLI and design system for the frakment component registry",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"components",
|
|
7
|
+
"frakment",
|
|
8
|
+
"monochrome",
|
|
9
|
+
"registry",
|
|
10
|
+
"tailwindcss"
|
|
11
|
+
],
|
|
12
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
13
|
+
"author": "arrafipd (https://github.com/arrafipd)",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"bin": {
|
|
16
|
+
"frakment": "./bin/index.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"bin",
|
|
20
|
+
"src",
|
|
21
|
+
"templates"
|
|
22
|
+
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@clack/prompts": "^1.8.1",
|
|
25
|
+
"commander": "^15.0.0",
|
|
26
|
+
"execa": "^10.2.1",
|
|
27
|
+
"picocolors": "^1.0.0"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=22.12.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import * as p from "@clack/prompts";
|
|
6
|
+
import { execa } from "execa";
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
|
|
9
|
+
import { detectPackageManager, findProjectRoot } from "../utils/detect.js";
|
|
10
|
+
|
|
11
|
+
async function fetchComponent(name, registryUrl, s) {
|
|
12
|
+
s.start(`fetching ${name}`);
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(`${registryUrl}/${name}.json`);
|
|
15
|
+
if (!res.ok) {
|
|
16
|
+
throw new Error(`HTTP ${res.status}`);
|
|
17
|
+
}
|
|
18
|
+
const payload = await res.json();
|
|
19
|
+
s.stop(`${pc.green("✓")} fetched ${name}`);
|
|
20
|
+
return payload;
|
|
21
|
+
} catch {
|
|
22
|
+
s.stop(`${pc.red("✗")} ${name} not found in registry`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function writeFileSafely(file, cwd, options) {
|
|
28
|
+
const targetPath = path.join(cwd, file.path);
|
|
29
|
+
if (existsSync(targetPath) && !options.overwrite) {
|
|
30
|
+
const overwrite = await p.confirm({
|
|
31
|
+
initialValue: false,
|
|
32
|
+
message: `${file.path} already exists. Overwrite?`,
|
|
33
|
+
});
|
|
34
|
+
if (p.isCancel(overwrite) || !overwrite) {
|
|
35
|
+
p.log.info(`skipped ${file.path}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
40
|
+
const output =
|
|
41
|
+
file.encoding === "base64"
|
|
42
|
+
? Buffer.from(file.content, "base64")
|
|
43
|
+
: file.content.replaceAll("\r\n", "\n");
|
|
44
|
+
await writeFile(targetPath, output);
|
|
45
|
+
p.log.success(file.path);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function add(componentName, options) {
|
|
49
|
+
p.intro(`${pc.dim("frakment")} add ${pc.bold(componentName)}`);
|
|
50
|
+
|
|
51
|
+
let cwd;
|
|
52
|
+
try {
|
|
53
|
+
cwd = findProjectRoot(process.cwd());
|
|
54
|
+
} catch (error) {
|
|
55
|
+
p.outro(pc.red("error ") + error.message);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const configPath = path.join(cwd, "components.json");
|
|
60
|
+
if (!existsSync(configPath)) {
|
|
61
|
+
p.outro(
|
|
62
|
+
`${pc.red(
|
|
63
|
+
"error "
|
|
64
|
+
)}components.json not found. Run \`frakment init\` first.`
|
|
65
|
+
);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const configRaw = await readFile(configPath, "utf-8");
|
|
70
|
+
const config = JSON.parse(configRaw.replace(/^\uFEFF/u, ""));
|
|
71
|
+
const registryUrl = (options.registry ?? config.registry).replace(/\/$/u, "");
|
|
72
|
+
const s = p.spinner();
|
|
73
|
+
|
|
74
|
+
// 1. Recursively fetch the component and its registryDependencies
|
|
75
|
+
const queue = [componentName];
|
|
76
|
+
const fetched = new Map();
|
|
77
|
+
const npmDeps = new Set();
|
|
78
|
+
|
|
79
|
+
while (queue.length) {
|
|
80
|
+
const name = queue.shift();
|
|
81
|
+
if (fetched.has(name)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const payload = await fetchComponent(name, registryUrl, s);
|
|
85
|
+
fetched.set(name, payload);
|
|
86
|
+
for (const d of payload.dependencies ?? []) {
|
|
87
|
+
npmDeps.add(d);
|
|
88
|
+
}
|
|
89
|
+
for (const d of payload.registryDependencies ?? []) {
|
|
90
|
+
if (!fetched.has(d)) {
|
|
91
|
+
queue.push(d);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 2. Write registry deps first (imports resolve), then main component
|
|
97
|
+
// Detect src/ layout: component files go under src/, but public/ assets
|
|
98
|
+
// stay at project root (Next.js serves public/ from root regardless of src/).
|
|
99
|
+
const isSrcLayout = config.tailwind?.css?.startsWith("src/") ?? false;
|
|
100
|
+
const resolveTargetPath = (p) => {
|
|
101
|
+
if (!isSrcLayout) {
|
|
102
|
+
return p;
|
|
103
|
+
}
|
|
104
|
+
if (p.startsWith("public/")) {
|
|
105
|
+
return p;
|
|
106
|
+
}
|
|
107
|
+
return `src/${p}`;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const order = [...fetched.keys()].filter((n) => n !== componentName);
|
|
111
|
+
order.push(componentName);
|
|
112
|
+
for (const name of order) {
|
|
113
|
+
for (const file of fetched.get(name).files ?? []) {
|
|
114
|
+
const targetFile = { ...file, path: resolveTargetPath(file.path) };
|
|
115
|
+
await writeFileSafely(targetFile, cwd, options);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 3. Install npm dependencies
|
|
120
|
+
if (npmDeps.size > 0) {
|
|
121
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
122
|
+
const pkgRaw = await readFile(pkgPath, "utf-8");
|
|
123
|
+
const pkg = JSON.parse(pkgRaw.replace(/^\uFEFF/u, ""));
|
|
124
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
125
|
+
const missing = [...npmDeps].filter((d) => !allDeps[d]);
|
|
126
|
+
if (missing.length > 0) {
|
|
127
|
+
s.start(`installing ${missing.join(", ")}`);
|
|
128
|
+
const pm = detectPackageManager(cwd);
|
|
129
|
+
try {
|
|
130
|
+
const args =
|
|
131
|
+
pm === "npm" ? ["install", ...missing] : ["add", ...missing];
|
|
132
|
+
await execa(pm, args, { cwd, stdio: "ignore" });
|
|
133
|
+
s.stop(`${pc.green("✓")} installed ${missing.length} package(s)`);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
s.stop(`${pc.red("✗")} failed to install dependencies`);
|
|
136
|
+
p.log.warn(error.message);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
p.outro(`${pc.green("✓")} ${componentName} added`);
|
|
142
|
+
}
|