frakment 0.0.2 → 0.0.4
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/bin/index.js +4 -4
- package/package.json +4 -3
- package/src/commands/add.js +42 -36
- package/src/commands/init.js +61 -75
- package/src/commands/list.js +22 -21
- package/src/utils/logger.js +12 -0
package/bin/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import chalk from "chalk";
|
|
4
5
|
import { Command } from "commander";
|
|
5
|
-
import pc from "picocolors";
|
|
6
6
|
|
|
7
7
|
import { add } from "../src/commands/add.js";
|
|
8
8
|
import { init } from "../src/commands/init.js";
|
|
@@ -23,9 +23,9 @@ program
|
|
|
23
23
|
"after",
|
|
24
24
|
`
|
|
25
25
|
Examples:
|
|
26
|
-
${
|
|
27
|
-
${
|
|
28
|
-
${
|
|
26
|
+
${chalk.dim("$ frakment init")}
|
|
27
|
+
${chalk.dim("$ frakment add button")}
|
|
28
|
+
${chalk.dim("$ frakment list")}
|
|
29
29
|
`
|
|
30
30
|
);
|
|
31
31
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frakment",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "CLI and design system for the frakment component registry",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"components",
|
|
@@ -21,10 +21,11 @@
|
|
|
21
21
|
"templates"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"
|
|
24
|
+
"chalk": "^5.3.0",
|
|
25
25
|
"commander": "^15.0.0",
|
|
26
26
|
"execa": "^10.2.1",
|
|
27
|
-
"
|
|
27
|
+
"ora": "^8.1.1",
|
|
28
|
+
"prompts": "^2.4.2"
|
|
28
29
|
},
|
|
29
30
|
"engines": {
|
|
30
31
|
"node": ">=22.12.0"
|
package/src/commands/add.js
CHANGED
|
@@ -2,38 +2,40 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import chalk from "chalk";
|
|
6
6
|
import { execa } from "execa";
|
|
7
|
-
import
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
import prompts from "prompts";
|
|
8
9
|
|
|
9
10
|
import { detectPackageManager, findProjectRoot } from "../utils/detect.js";
|
|
11
|
+
import { logger } from "../utils/logger.js";
|
|
10
12
|
|
|
11
|
-
async function fetchComponent(name, registryUrl,
|
|
12
|
-
s.start(`fetching ${name}`);
|
|
13
|
+
async function fetchComponent(name, registryUrl, spinner) {
|
|
13
14
|
try {
|
|
14
15
|
const res = await fetch(`${registryUrl}/${name}.json`);
|
|
15
16
|
if (!res.ok) {
|
|
16
17
|
throw new Error(`HTTP ${res.status}`);
|
|
17
18
|
}
|
|
18
19
|
const payload = await res.json();
|
|
19
|
-
|
|
20
|
+
spinner.text = `Fetching ${name}...`;
|
|
20
21
|
return payload;
|
|
21
22
|
} catch {
|
|
22
|
-
|
|
23
|
+
spinner.fail(`Registry fetch failed for ${name}.`);
|
|
23
24
|
process.exit(1);
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
|
-
|
|
27
27
|
async function writeFileSafely(file, cwd, options) {
|
|
28
28
|
const targetPath = path.join(cwd, file.path);
|
|
29
29
|
if (existsSync(targetPath) && !options.overwrite) {
|
|
30
|
-
const overwrite = await
|
|
31
|
-
|
|
30
|
+
const { overwrite } = await prompts({
|
|
31
|
+
initial: false,
|
|
32
32
|
message: `${file.path} already exists. Overwrite?`,
|
|
33
|
+
name: "overwrite",
|
|
34
|
+
type: "confirm",
|
|
33
35
|
});
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
return;
|
|
36
|
+
if (overwrite === undefined || !overwrite) {
|
|
37
|
+
logger.info(`Skipped ${file.path}.`);
|
|
38
|
+
return null;
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
@@ -42,36 +44,31 @@ async function writeFileSafely(file, cwd, options) {
|
|
|
42
44
|
? Buffer.from(file.content, "base64")
|
|
43
45
|
: file.content.replaceAll("\r\n", "\n");
|
|
44
46
|
await writeFile(targetPath, output);
|
|
45
|
-
|
|
47
|
+
return file.path;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
export async function add(componentName, options) {
|
|
49
|
-
p.intro(`${pc.dim("frakment")} add ${pc.bold(componentName)}`);
|
|
50
|
-
|
|
51
51
|
let cwd;
|
|
52
|
+
const preflight = ora("Preflight checks").start();
|
|
52
53
|
try {
|
|
53
54
|
cwd = findProjectRoot(process.cwd());
|
|
55
|
+
preflight.succeed("Preflight checks. Found project root.");
|
|
54
56
|
} catch (error) {
|
|
55
|
-
|
|
57
|
+
preflight.fail(error.message);
|
|
56
58
|
process.exit(1);
|
|
57
59
|
}
|
|
58
|
-
|
|
59
60
|
const configPath = path.join(cwd, "components.json");
|
|
60
61
|
if (!existsSync(configPath)) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"error "
|
|
64
|
-
)}components.json not found. Run \`frakment init\` first.`
|
|
65
|
-
);
|
|
62
|
+
logger.error(`${chalk.red("Error:")} components.json not found.`);
|
|
63
|
+
logger.info("Run `frakment init` first.");
|
|
66
64
|
process.exit(1);
|
|
67
65
|
}
|
|
68
66
|
|
|
69
67
|
const configRaw = await readFile(configPath, "utf-8");
|
|
70
68
|
const config = JSON.parse(configRaw.replace(/^\uFEFF/u, ""));
|
|
71
69
|
const registryUrl = (options.registry ?? config.registry).replace(/\/$/u, "");
|
|
72
|
-
const s = p.spinner();
|
|
73
70
|
|
|
74
|
-
|
|
71
|
+
const registrySpinner = ora("Checking registry").start();
|
|
75
72
|
const queue = [componentName];
|
|
76
73
|
const fetched = new Map();
|
|
77
74
|
const npmDeps = new Set();
|
|
@@ -81,7 +78,7 @@ export async function add(componentName, options) {
|
|
|
81
78
|
if (fetched.has(name)) {
|
|
82
79
|
continue;
|
|
83
80
|
}
|
|
84
|
-
const payload = await fetchComponent(name, registryUrl,
|
|
81
|
+
const payload = await fetchComponent(name, registryUrl, registrySpinner);
|
|
85
82
|
fetched.set(name, payload);
|
|
86
83
|
for (const d of payload.dependencies ?? []) {
|
|
87
84
|
npmDeps.add(d);
|
|
@@ -92,10 +89,7 @@ export async function add(componentName, options) {
|
|
|
92
89
|
}
|
|
93
90
|
}
|
|
94
91
|
}
|
|
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/).
|
|
92
|
+
registrySpinner.succeed("Checking registry.");
|
|
99
93
|
const isSrcLayout = config.tailwind?.css?.startsWith("src/") ?? false;
|
|
100
94
|
const resolveTargetPath = (p) => {
|
|
101
95
|
if (!isSrcLayout) {
|
|
@@ -109,14 +103,26 @@ export async function add(componentName, options) {
|
|
|
109
103
|
|
|
110
104
|
const order = [...fetched.keys()].filter((n) => n !== componentName);
|
|
111
105
|
order.push(componentName);
|
|
106
|
+
const written = [];
|
|
107
|
+
|
|
112
108
|
for (const name of order) {
|
|
113
109
|
for (const file of fetched.get(name).files ?? []) {
|
|
114
110
|
const targetFile = { ...file, path: resolveTargetPath(file.path) };
|
|
115
|
-
await writeFileSafely(targetFile, cwd, options);
|
|
111
|
+
const result = await writeFileSafely(targetFile, cwd, options);
|
|
112
|
+
if (result) {
|
|
113
|
+
written.push(result);
|
|
114
|
+
}
|
|
116
115
|
}
|
|
117
116
|
}
|
|
118
117
|
|
|
119
|
-
|
|
118
|
+
if (written.length > 0) {
|
|
119
|
+
logger.success(
|
|
120
|
+
`Created ${written.length} file${written.length === 1 ? "" : "s"}:`
|
|
121
|
+
);
|
|
122
|
+
for (const p of written) {
|
|
123
|
+
logger.log(` - ${p}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
120
126
|
if (npmDeps.size > 0) {
|
|
121
127
|
const pkgPath = path.join(cwd, "package.json");
|
|
122
128
|
const pkgRaw = await readFile(pkgPath, "utf-8");
|
|
@@ -124,19 +130,19 @@ export async function add(componentName, options) {
|
|
|
124
130
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
125
131
|
const missing = [...npmDeps].filter((d) => !allDeps[d]);
|
|
126
132
|
if (missing.length > 0) {
|
|
127
|
-
s
|
|
133
|
+
const s = ora("Installing dependencies").start();
|
|
128
134
|
const pm = detectPackageManager(cwd);
|
|
129
135
|
try {
|
|
130
136
|
const args =
|
|
131
137
|
pm === "npm" ? ["install", ...missing] : ["add", ...missing];
|
|
132
138
|
await execa(pm, args, { cwd, stdio: "ignore" });
|
|
133
|
-
s.
|
|
139
|
+
s.succeed(`Installed ${missing.length} package(s).`);
|
|
134
140
|
} catch (error) {
|
|
135
|
-
s.
|
|
136
|
-
|
|
141
|
+
s.fail("Failed to install dependencies.");
|
|
142
|
+
logger.warn(error.message);
|
|
137
143
|
}
|
|
138
144
|
}
|
|
139
145
|
}
|
|
140
146
|
|
|
141
|
-
|
|
147
|
+
logger.break();
|
|
142
148
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -2,15 +2,17 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import chalk from "chalk";
|
|
6
6
|
import { execa } from "execa";
|
|
7
|
-
import
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
import prompts from "prompts";
|
|
8
9
|
|
|
9
10
|
import {
|
|
10
11
|
detectGlobalsCss,
|
|
11
12
|
detectPackageManager,
|
|
12
13
|
findProjectRoot,
|
|
13
14
|
} from "../utils/detect.js";
|
|
15
|
+
import { logger } from "../utils/logger.js";
|
|
14
16
|
|
|
15
17
|
const __dirname = import.meta.dirname;
|
|
16
18
|
const TEMPLATES_DIR = path.resolve(__dirname, "../../templates");
|
|
@@ -20,43 +22,64 @@ const CONFIG_FILE = "components.json";
|
|
|
20
22
|
const THEME_MARKER_START = "/* ==== FRAKMENT THEME START ==== */";
|
|
21
23
|
const THEME_MARKER_END = "/* ==== FRAKMENT THEME END ==== */";
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
function escapeRegex(s) {
|
|
26
|
+
return s.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
27
|
+
}
|
|
25
28
|
|
|
29
|
+
export async function init() {
|
|
26
30
|
let cwd;
|
|
31
|
+
const preflight = ora("Preflight checks").start();
|
|
27
32
|
try {
|
|
28
33
|
cwd = findProjectRoot(process.cwd());
|
|
34
|
+
preflight.succeed("Preflight checks. Found project root.");
|
|
29
35
|
} catch (error) {
|
|
30
|
-
|
|
36
|
+
preflight.fail(error.message);
|
|
31
37
|
process.exit(1);
|
|
32
38
|
}
|
|
33
39
|
|
|
40
|
+
const cssPath = detectGlobalsCss(cwd);
|
|
41
|
+
const cssFullPath = path.join(cwd, cssPath);
|
|
42
|
+
const cssCheck = ora("Verifying Tailwind CSS config").start();
|
|
43
|
+
if (!existsSync(cssFullPath)) {
|
|
44
|
+
cssCheck.fail(`CSS file not found: ${cssPath}`);
|
|
45
|
+
logger.error("Frakment needs an existing Tailwind v4 entry file.");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
let css = await readFile(cssFullPath, "utf-8");
|
|
49
|
+
css = css.replace(/^\uFEFF/u, "");
|
|
50
|
+
if (!/^@import\s+["']tailwindcss(\/|["'])/mu.test(css)) {
|
|
51
|
+
cssCheck.fail("Frakment requires Tailwind v4.");
|
|
52
|
+
logger.error(`Could not find @import "tailwindcss" in ${cssPath}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
cssCheck.succeed(
|
|
56
|
+
`Verifying Tailwind CSS config. Found ${chalk.cyan("v4")} in ${chalk.cyan(cssPath)}.`
|
|
57
|
+
);
|
|
58
|
+
|
|
34
59
|
const configPath = path.join(cwd, CONFIG_FILE);
|
|
35
60
|
const configExists = existsSync(configPath);
|
|
36
61
|
let shouldWriteConfig = !configExists;
|
|
37
|
-
|
|
38
62
|
if (configExists) {
|
|
39
|
-
const overwrite = await
|
|
40
|
-
|
|
63
|
+
const { overwrite } = await prompts({
|
|
64
|
+
initial: false,
|
|
41
65
|
message: `${CONFIG_FILE} already exists. Overwrite?`,
|
|
66
|
+
name: "overwrite",
|
|
67
|
+
type: "confirm",
|
|
42
68
|
});
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
69
|
+
if (overwrite === undefined) {
|
|
70
|
+
logger.break();
|
|
71
|
+
logger.info("Cancelled.");
|
|
72
|
+
process.exit(0);
|
|
46
73
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
74
|
+
shouldWriteConfig = overwrite === true;
|
|
75
|
+
if (!shouldWriteConfig) {
|
|
76
|
+
logger.break();
|
|
77
|
+
logger.info(`Keeping existing ${CONFIG_FILE}.`);
|
|
51
78
|
}
|
|
52
79
|
}
|
|
53
80
|
|
|
54
|
-
const s = p.spinner();
|
|
55
|
-
const cssPath = detectGlobalsCss(cwd);
|
|
56
|
-
|
|
57
|
-
// 1. components.json (write only if new or user chose to overwrite)
|
|
58
81
|
if (shouldWriteConfig) {
|
|
59
|
-
s
|
|
82
|
+
const s = ora("Writing components.json").start();
|
|
60
83
|
const template = JSON.parse(
|
|
61
84
|
await readFile(path.join(TEMPLATES_DIR, "components.json"), "utf-8")
|
|
62
85
|
);
|
|
@@ -66,37 +89,9 @@ export async function init() {
|
|
|
66
89
|
tailwind: { ...template.tailwind, css: cssPath },
|
|
67
90
|
};
|
|
68
91
|
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
69
|
-
s.
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 2. theme (inline block)
|
|
73
|
-
const cssFullPath = path.join(cwd, cssPath);
|
|
74
|
-
if (!existsSync(cssFullPath)) {
|
|
75
|
-
p.log.error(`CSS file not found: ${cssPath}`);
|
|
76
|
-
p.outro(
|
|
77
|
-
`${pc.red(
|
|
78
|
-
"error "
|
|
79
|
-
)}Could not find globals.css. Frakment needs an existing Tailwind v4 entry file.`
|
|
80
|
-
);
|
|
81
|
-
process.exit(1);
|
|
92
|
+
s.succeed("Writing components.json.");
|
|
82
93
|
}
|
|
83
|
-
|
|
84
|
-
let css = await readFile(cssFullPath, "utf-8");
|
|
85
|
-
css = css.replace(/^\uFEFF/u, "");
|
|
86
|
-
|
|
87
|
-
if (!/^@import\s+["']tailwindcss(\/|["'])/mu.test(css)) {
|
|
88
|
-
p.outro(
|
|
89
|
-
`${pc.red(
|
|
90
|
-
"error "
|
|
91
|
-
)}Frakment requires Tailwind v4. Could not find @import "tailwindcss" in ${
|
|
92
|
-
cssPath
|
|
93
|
-
}`
|
|
94
|
-
);
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Cleanup: strip any prior theme block (marker-bounded) and
|
|
99
|
-
// any split-approach @import "./theme.css" line.
|
|
94
|
+
const themeSpinner = ora("Updating CSS variables").start();
|
|
100
95
|
css = css.replaceAll(
|
|
101
96
|
new RegExp(
|
|
102
97
|
`\\n*${escapeRegex(THEME_MARKER_START)}[\\s\\S]*?${escapeRegex(THEME_MARKER_END)}\\n*`,
|
|
@@ -107,8 +102,6 @@ export async function init() {
|
|
|
107
102
|
css = css.replaceAll(/^@import\s+["']\.\/theme\.css["'];?\s*$\n?/gmu, "");
|
|
108
103
|
css = `${css.replaceAll(/\n{3,}/gu, "\n\n").trimEnd()}\n`;
|
|
109
104
|
|
|
110
|
-
// Inject fresh theme block right after the last top-level @import
|
|
111
|
-
s.start("injecting theme block");
|
|
112
105
|
const themeContent = await readFile(
|
|
113
106
|
path.join(TEMPLATES_DIR, "theme.css"),
|
|
114
107
|
"utf-8"
|
|
@@ -123,13 +116,10 @@ export async function init() {
|
|
|
123
116
|
} else {
|
|
124
117
|
css = `${css.trimEnd()}\n\n${wrapped}\n`;
|
|
125
118
|
}
|
|
126
|
-
|
|
127
119
|
await writeFile(cssFullPath, css);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// 3. lib/utils.ts
|
|
120
|
+
themeSpinner.succeed(`Updating CSS variables in ${cssPath}`);
|
|
131
121
|
if (!existsSync(path.join(cwd, "lib/utils.ts"))) {
|
|
132
|
-
s
|
|
122
|
+
const s = ora("Creating lib/utils.ts").start();
|
|
133
123
|
await mkdir(path.join(cwd, "lib"), { recursive: true });
|
|
134
124
|
await writeFile(
|
|
135
125
|
path.join(cwd, "lib/utils.ts"),
|
|
@@ -141,12 +131,12 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
141
131
|
}
|
|
142
132
|
`
|
|
143
133
|
);
|
|
144
|
-
s.
|
|
134
|
+
s.succeed("Created 1 file:");
|
|
135
|
+
logger.log(" - lib/utils.ts");
|
|
145
136
|
}
|
|
146
137
|
|
|
147
|
-
// 3b. lib/animation.ts
|
|
148
138
|
if (!existsSync(path.join(cwd, "lib/animation.ts"))) {
|
|
149
|
-
s
|
|
139
|
+
const s = ora("Creating lib/animation.ts").start();
|
|
150
140
|
await mkdir(path.join(cwd, "lib"), { recursive: true });
|
|
151
141
|
await writeFile(
|
|
152
142
|
path.join(cwd, "lib/animation.ts"),
|
|
@@ -160,10 +150,9 @@ export const DURATION_FAST = 0.2
|
|
|
160
150
|
export const DURATION_SMOOTH = 0.45
|
|
161
151
|
`
|
|
162
152
|
);
|
|
163
|
-
s.
|
|
153
|
+
s.succeed("Created 1 file:");
|
|
154
|
+
logger.log(" - lib/animation.ts");
|
|
164
155
|
}
|
|
165
|
-
|
|
166
|
-
// 4. dependencies
|
|
167
156
|
const pkgPath = path.join(cwd, "package.json");
|
|
168
157
|
const pkgRaw = await readFile(pkgPath, "utf-8");
|
|
169
158
|
const pkg = JSON.parse(pkgRaw.replace(/^\uFEFF/u, ""));
|
|
@@ -173,29 +162,26 @@ export const DURATION_SMOOTH = 0.45
|
|
|
173
162
|
"tailwind-merge",
|
|
174
163
|
"class-variance-authority",
|
|
175
164
|
"radix-ui",
|
|
176
|
-
"
|
|
165
|
+
"lucide-react",
|
|
177
166
|
"motion",
|
|
178
167
|
].filter((d) => !allDeps[d]);
|
|
179
168
|
|
|
180
169
|
if (missing.length > 0) {
|
|
181
|
-
s
|
|
170
|
+
const s = ora("Installing dependencies").start();
|
|
182
171
|
const pm = detectPackageManager(cwd);
|
|
183
172
|
try {
|
|
184
173
|
const args = pm === "npm" ? ["install", ...missing] : ["add", ...missing];
|
|
185
174
|
await execa(pm, args, { cwd, stdio: "ignore" });
|
|
186
|
-
s.
|
|
175
|
+
s.succeed(`Installed ${missing.length} package(s).`);
|
|
187
176
|
} catch (error) {
|
|
188
|
-
s.
|
|
189
|
-
|
|
177
|
+
s.fail("Failed to install dependencies.");
|
|
178
|
+
logger.warn(error.message);
|
|
190
179
|
}
|
|
191
180
|
}
|
|
192
181
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
182
|
+
logger.break();
|
|
183
|
+
logger.log(
|
|
184
|
+
`${chalk.green("Success!")} Project initialization completed. You may now add components.`
|
|
196
185
|
);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
function escapeRegex(s) {
|
|
200
|
-
return s.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
186
|
+
logger.break();
|
|
201
187
|
}
|
package/src/commands/list.js
CHANGED
|
@@ -2,34 +2,33 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import ora from "ora";
|
|
7
7
|
|
|
8
8
|
import { findProjectRoot } from "../utils/detect.js";
|
|
9
|
+
import { logger } from "../utils/logger.js";
|
|
9
10
|
|
|
10
11
|
export async function list(options) {
|
|
11
|
-
p.intro(`${pc.dim("frakment")} list`);
|
|
12
|
-
|
|
13
12
|
let cwd;
|
|
13
|
+
const preflight = ora("Preflight checks").start();
|
|
14
14
|
try {
|
|
15
15
|
cwd = findProjectRoot(process.cwd());
|
|
16
|
+
preflight.succeed("Preflight checks. Found project root.");
|
|
16
17
|
} catch (error) {
|
|
17
|
-
|
|
18
|
+
preflight.fail(error.message);
|
|
18
19
|
process.exit(1);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
const configPath = path.join(cwd, "components.json");
|
|
22
23
|
if (!existsSync(configPath)) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
);
|
|
24
|
+
logger.error(`${chalk.red("Error:")} components.json not found.`);
|
|
25
|
+
logger.info("Run `frakment init` first.");
|
|
26
26
|
process.exit(1);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const config = JSON.parse(await readFile(configPath, "utf-8"));
|
|
30
30
|
const registryUrl = (options.registry ?? config.registry).replace(/\/$/u, "");
|
|
31
|
-
const s =
|
|
32
|
-
s.start("fetching components");
|
|
31
|
+
const s = ora("Fetching components").start();
|
|
33
32
|
|
|
34
33
|
let index;
|
|
35
34
|
try {
|
|
@@ -39,23 +38,25 @@ export async function list(options) {
|
|
|
39
38
|
}
|
|
40
39
|
index = await res.json();
|
|
41
40
|
} catch {
|
|
42
|
-
s.
|
|
41
|
+
s.fail("Failed to fetch component list.");
|
|
43
42
|
process.exit(1);
|
|
44
43
|
}
|
|
45
|
-
s.
|
|
44
|
+
s.succeed(`Found ${index.components.length} component(s).`);
|
|
46
45
|
|
|
47
46
|
if (!index.components.length) {
|
|
48
|
-
|
|
47
|
+
logger.break();
|
|
48
|
+
logger.log(chalk.dim("No components available yet."));
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const nameWidth = Math.max(...index.components.map((c) => c.name.length));
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
);
|
|
53
|
+
logger.break();
|
|
54
|
+
for (const c of index.components) {
|
|
55
|
+
logger.log(
|
|
56
|
+
` ${chalk.cyan(c.name.padEnd(nameWidth))} ${chalk.dim(c.description ?? "")}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
logger.break();
|
|
60
|
+
logger.log(`Add one with ${chalk.cyan("frakment add <name>")}`);
|
|
61
|
+
logger.break();
|
|
61
62
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
|
|
4
|
+
export const logger = {
|
|
5
|
+
break: () => console.log(""),
|
|
6
|
+
error: (...args) => console.log(chalk.red(...args)),
|
|
7
|
+
info: (...args) => console.log(chalk.cyan(...args)),
|
|
8
|
+
log: (...args) => console.log(...args),
|
|
9
|
+
spinner: (text) => ora(text),
|
|
10
|
+
success: (...args) => console.log(chalk.green(...args)),
|
|
11
|
+
warn: (...args) => console.log(chalk.yellow(...args)),
|
|
12
|
+
};
|