frakment 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frakment",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
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
- "@clack/prompts": "^1.8.1",
24
+ "chalk": "^5.3.0",
25
25
  "commander": "^15.0.0",
26
26
  "execa": "^10.2.1",
27
- "picocolors": "^1.0.0"
27
+ "ora": "^8.1.1",
28
+ "prompts": "^2.4.2"
28
29
  },
29
30
  "engines": {
30
31
  "node": ">=22.12.0"
@@ -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 * as p from "@clack/prompts";
5
+ import chalk from "chalk";
6
6
  import { execa } from "execa";
7
- import pc from "picocolors";
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, s) {
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
- s.stop(`${pc.green("✓")} fetched ${name}`);
20
+ spinner.text = `Fetching ${name}...`;
20
21
  return payload;
21
22
  } catch {
22
- s.stop(`${pc.red("✗")} ${name} not found in registry`);
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 p.confirm({
31
- initialValue: false,
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 (p.isCancel(overwrite) || !overwrite) {
35
- p.log.info(`skipped ${file.path}`);
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
- p.log.success(file.path);
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
- p.outro(pc.red("error ") + error.message);
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
- p.outro(
62
- `${pc.red(
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
- // 1. Recursively fetch the component and its registryDependencies
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, s);
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
- // 3. Install npm dependencies
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.start(`installing ${missing.join(", ")}`);
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.stop(`${pc.green("✓")} installed ${missing.length} package(s)`);
139
+ s.succeed(`Installed ${missing.length} package(s).`);
134
140
  } catch (error) {
135
- s.stop(`${pc.red("✗")} failed to install dependencies`);
136
- p.log.warn(error.message);
141
+ s.fail("Failed to install dependencies.");
142
+ logger.warn(error.message);
137
143
  }
138
144
  }
139
145
  }
140
146
 
141
- p.outro(`${pc.green("✓")} ${componentName} added`);
147
+ logger.break();
142
148
  }
@@ -2,61 +2,84 @@ 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 * as p from "@clack/prompts";
5
+ import chalk from "chalk";
6
6
  import { execa } from "execa";
7
- import pc from "picocolors";
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");
17
19
 
18
- const DEFAULT_REGISTRY = "https://frakment.vercel.app/r";
20
+ const DEFAULT_REGISTRY = "https://www.arrafi.dev/frakment/r";
19
21
  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
- export async function init() {
24
- p.intro(`${pc.dim("frakment")} init`);
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
- p.outro(pc.red("error ") + error.message);
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 p.confirm({
40
- initialValue: false,
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 (p.isCancel(overwrite)) {
44
- p.outro(pc.dim("cancelled"));
45
- return;
69
+ if (overwrite === undefined) {
70
+ logger.break();
71
+ logger.info("Cancelled.");
72
+ process.exit(0);
46
73
  }
47
- if (overwrite) {
48
- shouldWriteConfig = true;
49
- } else {
50
- p.log.info(`keeping existing ${CONFIG_FILE}`);
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.start("writing components.json");
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.stop(`${pc.green("✓")} components.json`);
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
- s.stop(`${pc.green("✓")} theme block`);
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.start("creating lib/utils.ts");
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.stop(`${pc.green("✓")} lib/utils.ts`);
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.start("creating lib/animation.ts");
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.stop(`${pc.green("✓")} lib/animation.ts`);
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
- "@phosphor-icons/react",
165
+ "lucide-react",
177
166
  "motion",
178
167
  ].filter((d) => !allDeps[d]);
179
168
 
180
169
  if (missing.length > 0) {
181
- s.start(`installing ${missing.join(", ")}`);
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.stop(`${pc.green("✓")} installed ${missing.length} package(s)`);
175
+ s.succeed(`Installed ${missing.length} package(s).`);
187
176
  } catch (error) {
188
- s.stop(`${pc.red("✗")} failed to install dependencies`);
189
- p.log.warn(error.message);
177
+ s.fail("Failed to install dependencies.");
178
+ logger.warn(error.message);
190
179
  }
191
180
  }
192
181
 
193
- p.outro(
194
- `${pc.green("✓")} frakment is ready\n\n` +
195
- ` Next ${pc.cyan("frakment add status-chip")}`
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
  }
@@ -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
+ };