atomsystem 0.1.40 → 0.1.41

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.
@@ -91,7 +91,7 @@ const Sidebar = ({ children, minWidth }: Sidebar) => {
91
91
 
92
92
 
93
93
  return (
94
- <aside ref={sidebarRef} className={`nav-side flex-col overflow-hidden h-full`} style={{ width: isOpen ? minWidth : 0, minWidth: isOpen ? minWidth : 0 }}>
94
+ <aside ref={sidebarRef} className={`nav-side flex flex-col overflow-auto h-full`} style={{ width: isOpen ? minWidth : 0, minWidth: isOpen ? minWidth : 0 }}>
95
95
  {children}
96
96
  </aside>
97
97
  )
package/dist/index.js ADDED
@@ -0,0 +1,393 @@
1
+ #!/usr/bin/env node
2
+
3
+ // node_modules/tsup/assets/esm_shims.js
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+ var getFilename = () => fileURLToPath(import.meta.url);
7
+ var getDirname = () => path.dirname(getFilename());
8
+ var __dirname = /* @__PURE__ */ getDirname();
9
+
10
+ // src/index.ts
11
+ import { Command as Command3 } from "commander";
12
+
13
+ // src/commands/add.ts
14
+ import { Command } from "commander";
15
+ import fs from "fs";
16
+ import path2 from "path";
17
+ import prompts from "prompts";
18
+ import { z } from "zod";
19
+
20
+ // src/utils/logger.ts
21
+ import chalk from "chalk";
22
+ var logger = {
23
+ error(...args) {
24
+ console.error(chalk.red(...args));
25
+ },
26
+ warn(...args) {
27
+ console.warn(chalk.yellow(...args));
28
+ },
29
+ info(...args) {
30
+ console.info(chalk.cyan(...args));
31
+ },
32
+ success(...args) {
33
+ console.log(chalk.green(...args));
34
+ },
35
+ log(...args) {
36
+ console.log(...args);
37
+ },
38
+ break() {
39
+ console.log("");
40
+ }
41
+ };
42
+
43
+ // src/commands/add.ts
44
+ var templateDir = path2.join(process.cwd(), "app", "templates");
45
+ var components = [
46
+ "Button",
47
+ "Badge",
48
+ "Card",
49
+ "Collapse",
50
+ "CodeBlock",
51
+ "Navigation",
52
+ "Sidebar",
53
+ "Input",
54
+ "Theme",
55
+ "Tab",
56
+ "Toast",
57
+ "Pagination",
58
+ "Table",
59
+ "Modal",
60
+ "TOC",
61
+ "Dropdown"
62
+ ];
63
+ var dependenciesMap = {
64
+ Theme: ["Button", "Form", "Modal"]
65
+ };
66
+ var addOptionsSchema = z.object({
67
+ components: z.array(z.string()).optional(),
68
+ all: z.boolean(),
69
+ cwd: z.string()
70
+ });
71
+ var add = new Command().name("add").description("\uD504\uB85C\uC81D\uD2B8\uC5D0 \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uCD94\uAC00\uD569\uB2C8\uB2E4.").argument("[components...]", "\uCD94\uAC00\uD560 \uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984 \uBAA9\uB85D").option("-a, --all", "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uBAA8\uB4E0 \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uCD94\uAC00\uD569\uB2C8\uB2E4.", false).option("-c, --cwd <cwd>", "\uC791\uC5C5 \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C", process.cwd()).action(async (componentsArg, opts) => {
72
+ try {
73
+ const options = addOptionsSchema.parse({
74
+ components: componentsArg,
75
+ ...opts,
76
+ cwd: path2.resolve(opts.cwd)
77
+ });
78
+ let selectedComponents = options.components || [];
79
+ if (options.all) {
80
+ selectedComponents = components;
81
+ }
82
+ if (selectedComponents.length === 0) {
83
+ const { components: promptedComponents } = await prompts({
84
+ type: "multiselect",
85
+ name: "components",
86
+ message: "\uCD94\uAC00\uD560 \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
87
+ choices: components.map((comp) => ({ title: comp, value: comp }))
88
+ });
89
+ selectedComponents = promptedComponents;
90
+ }
91
+ if (!selectedComponents || selectedComponents.length === 0) {
92
+ logger.warn("\uC120\uD0DD\uB41C \uCEF4\uD3EC\uB10C\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
93
+ return;
94
+ }
95
+ const addedComponents = /* @__PURE__ */ new Set();
96
+ for (const comp of selectedComponents) {
97
+ addComponent(comp, options.cwd, addedComponents);
98
+ }
99
+ } catch (error) {
100
+ logger.error("\uCEF4\uD3EC\uB10C\uD2B8 \uCD94\uAC00 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
101
+ logger.error(error);
102
+ }
103
+ });
104
+ function addComponent(comp, cwd, addedComponents) {
105
+ if (addedComponents.has(comp)) return;
106
+ addedComponents.add(comp);
107
+ const componentSourceDir = path2.join(templateDir, comp);
108
+ const componentPath = path2.join(componentSourceDir, `${comp}.tsx`);
109
+ const targetDir = path2.join(cwd, "app", "components", comp);
110
+ const targetPath = path2.join(targetDir, `${comp}.tsx`);
111
+ const cssFileName = `${comp}.css`.toLowerCase();
112
+ const sourceCssPath = path2.join(componentSourceDir, cssFileName);
113
+ const targetCssPath = path2.join(targetDir, cssFileName);
114
+ if (fs.existsSync(componentPath)) {
115
+ if (!fs.existsSync(targetDir)) {
116
+ fs.mkdirSync(targetDir, { recursive: true });
117
+ logger.info(`\uB514\uB809\uD1A0\uB9AC \uC0DD\uC131: ${targetDir}`);
118
+ }
119
+ fs.copyFileSync(componentPath, targetPath);
120
+ logger.success(`\u2705 ${comp} \uCEF4\uD3EC\uB10C\uD2B8(TSX)\uAC00 \uCD94\uAC00\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${targetPath}`);
121
+ if (fs.existsSync(sourceCssPath)) {
122
+ fs.copyFileSync(sourceCssPath, targetCssPath);
123
+ logger.success(`\u2705 ${comp} \uCEF4\uD3EC\uB10C\uD2B8(CSS)\uAC00 \uCD94\uAC00\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${targetCssPath}`);
124
+ updateGlobalsCss(comp, cssFileName, cwd);
125
+ }
126
+ if (dependenciesMap[comp]) {
127
+ logger.info(`\u23F3 ${comp}\uC758 \uC758\uC874\uC131 \uCEF4\uD3EC\uB10C\uD2B8 \uD655\uC778 \uC911...`);
128
+ for (const dep of dependenciesMap[comp]) {
129
+ addComponent(dep, cwd, addedComponents);
130
+ }
131
+ }
132
+ } else {
133
+ logger.error(`\u274C ${comp} \uCEF4\uD3EC\uB10C\uD2B8\uAC00 atomsystem\uC5D0 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`);
134
+ }
135
+ }
136
+ function updateGlobalsCss(comp, cssFileName, cwd) {
137
+ const globalsPath = path2.join(cwd, "app", "globals.css");
138
+ const importLine = `@import "./components/${comp}/${cssFileName}";`;
139
+ const componentSectionMarker = "/* Component Styles */";
140
+ if (fs.existsSync(globalsPath)) {
141
+ let content = fs.readFileSync(globalsPath, "utf8");
142
+ if (!content.includes(importLine)) {
143
+ if (content.includes(componentSectionMarker)) {
144
+ const markerIndex = content.indexOf(componentSectionMarker);
145
+ const afterMarker = content.slice(markerIndex);
146
+ const lastImportIndex = afterMarker.lastIndexOf("@import");
147
+ if (lastImportIndex !== -1) {
148
+ const endOfLastImport = markerIndex + lastImportIndex + afterMarker.slice(lastImportIndex).indexOf("\n") + 1;
149
+ content = content.slice(0, endOfLastImport) + importLine + "\n" + content.slice(endOfLastImport);
150
+ } else {
151
+ content = content.slice(0, markerIndex + componentSectionMarker.length) + "\n" + importLine + "\n" + content.slice(markerIndex + componentSectionMarker.length);
152
+ }
153
+ } else {
154
+ content = content.trimEnd() + "\n\n" + componentSectionMarker + "\n" + importLine + "\n";
155
+ }
156
+ fs.writeFileSync(globalsPath, content, "utf8");
157
+ logger.success(`\u2705 globals.css\uC5D0 ${comp} @import\uB97C \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`);
158
+ }
159
+ } else {
160
+ logger.warn(`\u26A0\uFE0F globals.css\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC218\uB3D9\uC73C\uB85C \uCD94\uAC00\uD574 \uC8FC\uC138\uC694:
161
+ ${importLine}`);
162
+ }
163
+ }
164
+
165
+ // src/commands/init.ts
166
+ import { Command as Command2 } from "commander";
167
+ import fs2 from "fs";
168
+ import path3 from "path";
169
+ import { exec } from "child_process";
170
+ import { promisify } from "util";
171
+ import ora from "ora";
172
+ import prompts2 from "prompts";
173
+ import { z as z2 } from "zod";
174
+ var execPromise = promisify(exec);
175
+ var initOptionsSchema = z2.object({
176
+ cwd: z2.string(),
177
+ yes: z2.boolean()
178
+ });
179
+ var init = new Command2().name("init").description("\uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654\uD558\uACE0 atomsystem\uC744 \uC124\uC815\uD569\uB2C8\uB2E4.").option("-y, --yes", "\uBAA8\uB4E0 \uC9C8\uBB38\uC5D0 'yes'\uB85C \uC751\uB2F5\uD569\uB2C8\uB2E4.", false).option("-c, --cwd <cwd>", "\uC791\uC5C5 \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C", process.cwd()).action(async (opts) => {
180
+ try {
181
+ const options = initOptionsSchema.parse({
182
+ ...opts,
183
+ cwd: path3.resolve(opts.cwd)
184
+ });
185
+ const projectRoot = options.cwd;
186
+ const packageJsonPath = path3.join(projectRoot, "package.json");
187
+ if (!fs2.existsSync(packageJsonPath)) {
188
+ logger.error("\u274C \uC624\uB958: \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
189
+ logger.info("'npm init'\uC744 \uBA3C\uC800 \uC2E4\uD589\uD55C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.");
190
+ process.exit(1);
191
+ }
192
+ const answers = options.yes ? { installPackages: true } : await prompts2({
193
+ type: "confirm",
194
+ name: "installPackages",
195
+ message: "Next.js\uC640 React\uB97C \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
196
+ initial: true
197
+ });
198
+ if (answers.installPackages) {
199
+ const pkgSpinner = ora("Next.js\uC640 React \uC124\uCE58 \uC911...").start();
200
+ try {
201
+ await execPromise(
202
+ "npm install next@latest react@latest react-dom@latest tailwindcss @tailwindcss/postcss postcss clsx react-modal typescript @types/react @types/node @types/react-dom @types/react-modal",
203
+ { cwd: projectRoot }
204
+ );
205
+ pkgSpinner.succeed("Next.js\uC640 React \uC124\uCE58 \uC644\uB8CC!");
206
+ } catch (err) {
207
+ pkgSpinner.fail("\uC124\uCE58 \uC911 \uC624\uB958 \uBC1C\uC0DD");
208
+ logger.error(err);
209
+ return;
210
+ }
211
+ const sdSpinner = ora("style-dictionary \uC124\uCE58 \uC911...").start();
212
+ try {
213
+ await execPromise("npm install style-dictionary -D", { cwd: projectRoot });
214
+ sdSpinner.succeed("style-dictionary \uC124\uCE58 \uC644\uB8CC!");
215
+ } catch (err) {
216
+ sdSpinner.fail("style-dictionary \uC124\uCE58 \uC911 \uC624\uB958 \uBC1C\uC0DD");
217
+ logger.error(err);
218
+ return;
219
+ }
220
+ try {
221
+ const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf8"));
222
+ packageJson.type = "module";
223
+ packageJson.scripts = {
224
+ ...packageJson.scripts,
225
+ "dev": "node build/build-tokens.js && next dev",
226
+ "build": "next build",
227
+ "start": "next start",
228
+ "lint": "next lint",
229
+ "token-build": "node build/build-tokens.js"
230
+ };
231
+ fs2.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
232
+ logger.success("\u2705 package.json \uC2A4\uD06C\uB9BD\uD2B8 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC.");
233
+ } catch (err) {
234
+ logger.error("\u274C package.json \uC5C5\uB370\uC774\uD2B8 \uC624\uB958:", err);
235
+ return;
236
+ }
237
+ const appDir = path3.join(projectRoot, "app");
238
+ if (!fs2.existsSync(appDir)) fs2.mkdirSync(appDir, { recursive: true });
239
+ const files = [
240
+ {
241
+ path: path3.join(appDir, "layout.tsx"),
242
+ content: `import { ThemeProvider } from "./components/Theme/Theme";
243
+ import "./globals.css";
244
+
245
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
246
+ return (
247
+ <html lang="en" suppressHydrationWarning>
248
+ <body>
249
+ <ThemeProvider />
250
+ {children}
251
+ <div id="modal-root"></div>
252
+ </body>
253
+ </html>
254
+ )
255
+ }`
256
+ },
257
+ {
258
+ path: path3.join(appDir, "globals.css"),
259
+ content: `@import "tailwindcss";
260
+ @import "../build/css/_variables.css";
261
+ @import "../css/text.css";`
262
+ },
263
+ {
264
+ path: path3.join(appDir, "page.tsx"),
265
+ content: `import { ThemeSelector } from "./components/Theme/Theme";
266
+ import { Button } from "./components/Button/Button";
267
+ import { Card, CardHeader, CardContent, CardFooter } from "./components/Card/Card";
268
+
269
+ export default function Page() {
270
+ return (
271
+ <main style={{ padding: "40px", minHeight: "100vh", background: "var(--color-bg-wrapper)", transition: "background 0.3s ease" }}>
272
+ <div style={{ maxWidth: "800px", margin: "0 auto", display: "flex", flexDirection: "column", gap: "24px" }}>
273
+ <h1 style={{ fontSize: "var(--font-sizes-h2)", color: "var(--color-text-standard)", margin: 0 }}>Hello, atomsystem!</h1>
274
+ <p style={{ fontSize: "var(--font-sizes-body)", margin: 0 }}>
275
+ \uC774 \uD654\uBA74\uC740 \uB514\uC790\uC778 \uD1A0\uD070\uC774 \uC801\uC6A9\uB41C \uC0D8\uD50C \uD654\uBA74\uC785\uB2C8\uB2E4. \uC544\uB798 \uD14C\uB9C8\uB97C \uBCC0\uACBD\uD574\uBCF4\uC138\uC694.
276
+ </p>
277
+
278
+ <ThemeSelector />
279
+
280
+ <Card>
281
+ <CardHeader>
282
+ <h2 style={{ fontSize: "var(--font-sizes-h3)", margin: 0 }}>Sample Card Components</h2>
283
+ </CardHeader>
284
+ <CardContent>
285
+ <p style={{ margin: 0, color: "var(--color-text-disabled)" }}>
286
+ \uC774 \uCE74\uB4DC\uB294 \uD14C\uB9C8 \uBCC0\uACBD \uC2DC \uBC30\uACBD\uC0C9, \uD14D\uC2A4\uD2B8 \uC0C9\uC0C1, \uD14C\uB450\uB9AC \uC0C9\uC0C1\uC774 \uC790\uB3D9\uC73C\uB85C \uBCC0\uACBD\uB429\uB2C8\uB2E4.
287
+ \uC5EC\uAE30 \uC788\uB294 \uBC84\uD2BC\uB4E4\uC740 \uC81C\uACF5\uB41C Button \uCEF4\uD3EC\uB10C\uD2B8\uB85C \uAD6C\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
288
+ </p>
289
+ </CardContent>
290
+ <CardFooter>
291
+ <div style={{ display: "flex", gap: "12px" }}>
292
+ <Button type="primary">Primary Button</Button>
293
+ <Button type="secondary">Secondary Button</Button>
294
+ <Button type="gray">Gray Button</Button>
295
+ </div>
296
+ </CardFooter>
297
+ </Card>
298
+ </div>
299
+ </main>
300
+ )
301
+ }`
302
+ },
303
+ {
304
+ path: path3.join(projectRoot, "tsconfig.json"),
305
+ content: JSON.stringify({
306
+ compilerOptions: {
307
+ target: "ES2017",
308
+ lib: ["dom", "dom.iterable", "esnext"],
309
+ allowJs: true,
310
+ skipLibCheck: true,
311
+ strict: true,
312
+ noEmit: true,
313
+ esModuleInterop: true,
314
+ module: "esnext",
315
+ moduleResolution: "bundler",
316
+ resolveJsonModule: true,
317
+ isolatedModules: true,
318
+ jsx: "preserve",
319
+ incremental: true,
320
+ plugins: [{ name: "next" }],
321
+ paths: {
322
+ "@/*": ["./*"]
323
+ }
324
+ },
325
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
326
+ exclude: ["node_modules"]
327
+ }, null, 2)
328
+ },
329
+ {
330
+ path: path3.join(projectRoot, "next.config.ts"),
331
+ content: `import type { NextConfig } from "next";
332
+
333
+ const nextConfig: NextConfig = { reactStrictMode: false };
334
+ export default nextConfig;`
335
+ },
336
+ {
337
+ path: path3.join(projectRoot, "postcss.config.mjs"),
338
+ content: `/** @type {import('postcss-load-config').Config} */
339
+ const config = { plugins: { "@tailwindcss/postcss": {} } };
340
+ export default config;`
341
+ }
342
+ ];
343
+ for (const file of files) {
344
+ fs2.writeFileSync(file.path, file.content);
345
+ logger.info(`\u2705 \uD30C\uC77C \uC0DD\uC131: ${file.path}`);
346
+ }
347
+ const buildTokensPath = path3.join(projectRoot, "build", "build-tokens.js");
348
+ if (fs2.existsSync(buildTokensPath)) {
349
+ const spin = ora("\uB514\uC790\uC778 \uD1A0\uD070 \uBE4C\uB4DC \uC911...").start();
350
+ try {
351
+ await execPromise(`node "${buildTokensPath}"`, { cwd: projectRoot });
352
+ spin.succeed("\uB514\uC790\uC778 \uD1A0\uD070 \uBE4C\uB4DC \uC644\uB8CC.");
353
+ } catch (err) {
354
+ spin.fail("\uD1A0\uD070 \uBE4C\uB4DC \uC2E4\uD328");
355
+ logger.error(err);
356
+ }
357
+ }
358
+ }
359
+ const tokenFile = path3.join(__dirname, "..", "..", "tokens", "tokens.json");
360
+ const buildFile = path3.join(__dirname, "..", "..", "build", "build-tokens.js");
361
+ const textCssFile = path3.join(__dirname, "..", "..", "css", "text.css");
362
+ copyFile(tokenFile, path3.join(projectRoot, "tokens", "tokens.json"));
363
+ copyFile(buildFile, path3.join(projectRoot, "build", "build-tokens.js"));
364
+ copyFile(textCssFile, path3.join(projectRoot, "css", "text.css"));
365
+ logger.success("\u{1F680} Atomsystem \uCD08\uAE30 \uC124\uC815 \uC644\uB8CC!");
366
+ } catch (error) {
367
+ logger.error("\uCD08\uAE30\uD654 \uC911 \uC624\uB958 \uBC1C\uC0DD:");
368
+ logger.error(error);
369
+ }
370
+ });
371
+ function copyFile(src, dest) {
372
+ const dir = path3.dirname(dest);
373
+ if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
374
+ if (fs2.existsSync(src)) {
375
+ fs2.copyFileSync(src, dest);
376
+ logger.info(`\u2705 \uBCF5\uC0AC \uC644\uB8CC: ${src} -> ${dest}`);
377
+ } else {
378
+ logger.warn(`\u26A0\uFE0F \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4 \uBCF5\uC0AC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${src}`);
379
+ }
380
+ }
381
+
382
+ // src/index.ts
383
+ import { readFileSync } from "fs";
384
+ import { join } from "path";
385
+ async function main() {
386
+ const packageJsonPath = join(__dirname, "../package.json");
387
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
388
+ const program = new Command3().name("atomsystem").description("Atom Ground Design System CLI").version(packageJson.version);
389
+ program.addCommand(add);
390
+ program.addCommand(init);
391
+ program.parse();
392
+ }
393
+ main();
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "atomsystem",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
4
4
  "type": "module",
5
5
  "homepage": "https://system.atomground.com",
6
6
  "bin": {
7
- "atomsystem-init": "cli/init.js",
8
- "atomsystem-add": "cli/add.js"
7
+ "atomsystem": "dist/index.js"
9
8
  },
10
9
  "files": [
11
10
  "tokens",
@@ -19,7 +18,9 @@
19
18
  "build": "next build",
20
19
  "start": "next start",
21
20
  "lint": "next lint",
22
- "token-build": "node build/build-tokens.js"
21
+ "token-build": "node build/build-tokens.js",
22
+ "cli:build": "tsup src/index.ts --format esm --clean --shims --target node20",
23
+ "cli:dev": "tsx src/index.ts"
23
24
  },
24
25
  "peerDependencies": {
25
26
  "react": "^18.0.0",
@@ -40,13 +41,16 @@
40
41
  "@tailwindcss/postcss": "^4.0.17",
41
42
  "@tokens-studio/sd-transforms": "^1.3.0",
42
43
  "@types/mdx": "^2.0.13",
44
+ "chalk": "^5.6.2",
43
45
  "clsx": "^2.1.1",
46
+ "commander": "^14.0.3",
44
47
  "gsap": "^3.12.7",
45
48
  "highlight.js": "^11.11.1",
46
49
  "inquirer": "^12.4.2",
47
50
  "lucide-react": "^1.7.0",
48
51
  "next": "15.2.8",
49
52
  "ora": "^9.3.0",
53
+ "prompts": "^2.4.2",
50
54
  "react": "^18.0.0",
51
55
  "react-dom": "^18.0.0",
52
56
  "react-modal": "^3.16.3",
@@ -54,10 +58,12 @@
54
58
  "remark-gfm": "^4.0.1",
55
59
  "sd-tailwindcss-transformer": "^2.0.0",
56
60
  "style-dictionary": "^4.2.0",
57
- "tailwind-merge": "^3.5.0"
61
+ "tailwind-merge": "^3.5.0",
62
+ "zod": "^4.3.6"
58
63
  },
59
64
  "devDependencies": {
60
65
  "@types/node": "^20",
66
+ "@types/prompts": "^2.4.9",
61
67
  "@types/react": "^18",
62
68
  "@types/react-dom": "^18",
63
69
  "@types/react-modal": "^3.16.3",
@@ -68,6 +74,8 @@
68
74
  "playwright": "^1.51.1",
69
75
  "postcss": "^8.5.3",
70
76
  "tailwindcss": "^4.0.17",
77
+ "tsup": "^8.5.1",
78
+ "tsx": "^4.21.0",
71
79
  "typescript": "^5",
72
80
  "vitest": "^3.1.1"
73
81
  }
package/cli/add.js DELETED
@@ -1,143 +0,0 @@
1
- #!/usr/bin/env node
2
- import inquirer from "inquirer";
3
- import fs from "fs";
4
- import path from "path";
5
- import { copyFileSync } from "fs";
6
- import { fileURLToPath } from "url";
7
-
8
- // 현재 실행중인 CLI 파일의 디렉토리 찾기
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = path.dirname(__filename);
11
-
12
- // 컴포넌트 템플릿이 위치한 디렉토리
13
- const templateDir = path.join(__dirname, "..", "app", "templates");
14
-
15
- // 사용할 수 있는 컴포넌트 목록
16
- const components = [
17
- "Button",
18
- "Badge",
19
- "Card",
20
- "Collapse",
21
- "CodeBlock",
22
- "Navigation",
23
- "Sidebar",
24
- "Input",
25
- "Theme",
26
- "Tab",
27
- "Toast",
28
- "Pagination",
29
- "Table",
30
- "Modal",
31
- "TOC",
32
- "Dropdown",
33
- ]; // 여기 추가할 컴포넌트 목록
34
-
35
-
36
- // 명령어에서 컴포넌트 이름 가져오기
37
- const args = process.argv.slice(2); // 'npx atomsystem-add Button -> [Button]'
38
- const component = args[0] || "all"; // '사용자가 입력하지 않으면 기본적으로 'all''
39
-
40
- // 일부 컴포넌트의 의존성 구조 (Theme 설치시 내부적으로 Button, Form, Modal 등이 필요함)
41
- const dependenciesMap = {
42
- Theme: ["Button", "Form", "Modal"], // Form 안에는 Radio가 들어있음
43
- };
44
-
45
- // 재귀 방지를 위한 Set
46
- const addedComponents = new Set();
47
-
48
- function addComponent(comp) {
49
- if (addedComponents.has(comp)) return;
50
- addedComponents.add(comp);
51
- const componentPath = path.join(templateDir, comp, `${comp}.tsx`);
52
- const targetDir = path.join(process.cwd(), "app", "components", comp);
53
- const targetPath = path.join(targetDir, `${comp}.tsx`);
54
-
55
- // 컴포넌트의 CSS 파일 복제 지원
56
- const cssFileName = `${comp}.css`.toLowerCase();
57
- const sourceCssPath = path.join(templateDir, comp, cssFileName);
58
- const targetCssPath = path.join(targetDir, cssFileName);
59
-
60
- if (fs.existsSync(componentPath)) {
61
- // 대상 디렉토리가 없으면 생성
62
- if (!fs.existsSync(targetDir)) {
63
- fs.mkdirSync(targetDir, { recursive: true });
64
- console.log(`Created directory: ${targetDir}`);
65
- }
66
-
67
- // 1) TSX 복사
68
- copyFileSync(componentPath, targetPath);
69
- console.log(`✅ ${comp} component typescript has been added to ${targetPath}`);
70
-
71
- // 2) CSS 복사 + globals.css에 @import 자동 추가
72
- if (fs.existsSync(sourceCssPath)) {
73
- copyFileSync(sourceCssPath, targetCssPath);
74
- console.log(`✅ ${comp} component stylesheet has been added to ${targetCssPath}`);
75
-
76
- // globals.css에 @import 추가
77
- const globalsPath = path.join(process.cwd(), "app", "globals.css");
78
- const importLine = `@import "./components/${comp}/${cssFileName}";`;
79
-
80
- if (fs.existsSync(globalsPath)) {
81
- const globalsContent = fs.readFileSync(globalsPath, "utf8");
82
-
83
- if (!globalsContent.includes(importLine)) {
84
- // "/* Component Styles */" 섹션 이후에 추가, 없으면 파일 끝에 추가
85
- let updatedContent;
86
- const componentSectionMarker = "/* Component Styles */";
87
-
88
- if (globalsContent.includes(componentSectionMarker)) {
89
- // 마커 이후의 마지막 @import 뒤에 추가
90
- const markerIndex = globalsContent.indexOf(componentSectionMarker);
91
- const afterMarker = globalsContent.slice(markerIndex);
92
- const lastImportInSection = afterMarker.lastIndexOf("@import");
93
-
94
- if (lastImportInSection !== -1) {
95
- const endOfLastImport = markerIndex + lastImportInSection + afterMarker.slice(lastImportInSection).indexOf("\n") + 1;
96
- updatedContent =
97
- globalsContent.slice(0, endOfLastImport) +
98
- importLine + "\n" +
99
- globalsContent.slice(endOfLastImport);
100
- } else {
101
- updatedContent =
102
- globalsContent.slice(0, markerIndex + componentSectionMarker.length) +
103
- "\n" + importLine + "\n" +
104
- globalsContent.slice(markerIndex + componentSectionMarker.length);
105
- }
106
- } else {
107
- // 마커가 없으면 "/* Component Styles */" 섹션과 함께 파일 끝에 추가
108
- updatedContent = globalsContent.trimEnd() + "\n\n/* Component Styles */\n" + importLine + "\n";
109
- }
110
-
111
- fs.writeFileSync(globalsPath, updatedContent, "utf8");
112
- console.log(`✅ Added @import for ${comp} to globals.css`);
113
- } else {
114
- console.log(`ℹ️ ${comp} is already imported in globals.css`);
115
- }
116
- } else {
117
- console.log(`⚠️ globals.css not found at ${globalsPath}. Please add manually:\n ${importLine}`);
118
- }
119
- }
120
- // 3) 의존성 컴포넌트 자동 복사 (Recursive Copy)
121
- if (dependenciesMap[comp]) {
122
- console.log(`\n⏳ Checking dependencies for ${comp}...`);
123
- dependenciesMap[comp].forEach((dep) => {
124
- addComponent(dep);
125
- });
126
- console.log(`✅ Finished adding dependencies for ${comp}.\n`);
127
- }
128
-
129
- } else {
130
- console.log(`❌ Component ${comp} does not exist in the atomsystem.`);
131
- }
132
- }
133
-
134
- if (component == "all") {
135
- console.log("Adding all components...");
136
-
137
- components.forEach((comp) => {
138
- addComponent(comp);
139
- });
140
- } else {
141
- // 특정 컴포넌트 추가
142
- addComponent(component);
143
- }
package/cli/init.js DELETED
@@ -1,276 +0,0 @@
1
- #!/usr/bin/env node
2
- import inquirer from "inquirer";
3
- import ora from "ora";
4
- import { exec } from "child_process";
5
- import fs, { copyFileSync, mkdirSync, readdirSync } from "fs";
6
- import path from "path";
7
- import { fileURLToPath } from "url";
8
- import { promisify } from "util";
9
-
10
- const execPromise = promisify(exec);
11
-
12
- // 현재 실행중인 CLI 파일의 디렉토리 찾기
13
- const __filename = fileURLToPath(import.meta.url);
14
- const __dirname = path.dirname(__filename);
15
-
16
- // 패키지 내부 경로 설정
17
- const packageRoot = path.join(__dirname, "..");
18
- const tokenFile = path.join(packageRoot, "tokens", "tokens.json");
19
- const buildFile = path.join(packageRoot, "build", "build-tokens.js");
20
- const textCssFile = path.join(packageRoot, "css", "text.css");
21
-
22
- // 프로젝트 루트 경로 (사용자가 실행한 곳)
23
- const projectRoot = process.cwd();
24
- const targetTokenDir = path.join(projectRoot, "tokens");
25
- const targetTokenFile = path.join(targetTokenDir, "tokens.json");
26
- const targetBuildDir = path.join(projectRoot, "build");
27
- const targetBuildFile = path.join(targetBuildDir, "build-tokens.js");
28
- const targetCssDir = path.join(projectRoot, "css");
29
- const targetTextCssFile = path.join(targetCssDir, "text.css");
30
-
31
- // 파일 복사 헬퍼: 대상 디렉토리 생성 → 파일 복사 → 로그 출력
32
- function copyTo(src, dest, label) {
33
- const dir = path.dirname(dest);
34
- if (!fs.existsSync(dir)) mkdirSync(dir, { recursive: true });
35
- copyFileSync(src, dest);
36
- console.log(`✅ Copied: ${label} -> ${dest}`);
37
- }
38
-
39
- // 사용자 프로젝트에 package.json이 존재하는지 먼저 확인 (안전장치)
40
- const packageJsonPath = path.join(projectRoot, "package.json");
41
- if (!fs.existsSync(packageJsonPath)) {
42
- console.error("❌ Error: package.json not found in the current directory.");
43
- console.error("Please run 'npm init' or 'npm init -y' before running atomsystem-init.");
44
- process.exit(1);
45
- }
46
-
47
- // 사용자에게 Next.js 설치 여부 질문
48
- inquirer
49
- .prompt([
50
- {
51
- type: "confirm",
52
- name: "installPackages",
53
- message: "Do you want to install Next.js and React?",
54
- default: true,
55
- },
56
- ])
57
- .then(async (answers) => {
58
- if (answers.installPackages) {
59
- const pkgSpinner = ora("Installing Next.js and React...").start();
60
-
61
- try {
62
- await execPromise(
63
- "npm install next@latest react@latest react-dom@latest tailwindcss @tailwindcss/postcss postcss clsx react-modal typescript @types/react @types/node @types/react-dom @types/react-modal"
64
- );
65
- pkgSpinner.succeed("Next.js and React installation complete!");
66
- } catch (err) {
67
- pkgSpinner.fail("Error during Next.js/React installation");
68
- console.error(err);
69
- return;
70
- }
71
-
72
- const sdSpinner = ora("Installing style-dictionary for atom design tokens...").start();
73
- try {
74
- await execPromise("npm install style-dictionary -D");
75
- sdSpinner.succeed("style-dictionary installation complete!");
76
- } catch (err) {
77
- sdSpinner.fail("Error during style-dictionary installation");
78
- console.error(err);
79
- return;
80
- }
81
-
82
- // package.json 업데이트
83
- const packageJsonPath = path.join(projectRoot, "package.json");
84
- try {
85
- const packageJson = JSON.parse(
86
- fs.readFileSync(packageJsonPath, "utf8")
87
- );
88
-
89
- packageJson.type = "module";
90
- packageJson.scripts = {
91
- ...packageJson.scripts,
92
- "dev": "node build/build-tokens.js && next dev",
93
- "build": "next build",
94
- "start": "next start",
95
- "lint": "next lint",
96
- "token-build": "node build/build-tokens.js",
97
- };
98
-
99
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
100
- console.log("✅ Updated package.json with scripts.");
101
- } catch (err) {
102
- console.error("❌ Error updating package.json:", err);
103
- return;
104
- }
105
-
106
- // app 디렉토리 생성 및 파일 추가
107
- const appDir = path.join(projectRoot, "app");
108
- if (!fs.existsSync(appDir)) mkdirSync(appDir);
109
-
110
- // components 및 Theme 디렉토리 생성은 add.js 가 알아서 처리하므로 여기서는 page.tsx 와 layout.tsx, globals.css 만 생성합니다.
111
- const files = [
112
- {
113
- path: path.join(appDir, "layout.tsx"),
114
- content: `import { ThemeProvider } from "./components/Theme/Theme";
115
- import "./globals.css";
116
-
117
- export default function RootLayout({ children }: { children: React.ReactNode }) {
118
- return (
119
- <html lang="en" suppressHydrationWarning>
120
- <body>
121
- <ThemeProvider />
122
- {children}
123
- <div id="modal-root"></div>
124
- </body>
125
- </html>
126
- )
127
- }`,
128
- },
129
- {
130
- path: path.join(appDir, "globals.css"),
131
- content: `@import "tailwindcss";
132
- @import "../build/css/_variables.css";
133
- @import "../css/text.css";`,
134
- },
135
- {
136
- path: path.join(appDir, "page.tsx"),
137
- content: `import { ThemeSelector } from "./components/Theme/Theme";
138
- import { Button } from "./components/Button/Button";
139
- import { Card, CardHeader, CardContent, CardFooter } from "./components/Card/Card";
140
-
141
- export default function Page() {
142
- return (
143
- <main style={{ padding: "40px", minHeight: "100vh", background: "var(--color-bg-wrapper)", transition: "background 0.3s ease" }}>
144
- <div style={{ maxWidth: "800px", margin: "0 auto", display: "flex", flexDirection: "column", gap: "24px" }}>
145
- <h1 style={{ fontSize: "var(--font-sizes-h2)", color: "var(--color-text-standard)", margin: 0 }}>Hello, atomsystem!</h1>
146
- <p style={{ fontSize: "var(--font-sizes-body)", margin: 0 }}>
147
- 이 화면은 디자인 토큰이 적용된 샘플 화면입니다. 아래 테마를 변경해보세요.
148
- </p>
149
-
150
- <ThemeSelector />
151
-
152
- <Card>
153
- <CardHeader>
154
- <h2 style={{ fontSize: "var(--font-sizes-h3)", margin: 0 }}>Sample Card Components</h2>
155
- </CardHeader>
156
- <CardContent>
157
- <p style={{ margin: 0, color: "var(--color-text-disabled)" }}>
158
- 이 카드는 테마 변경 시 배경색, 텍스트 색상, 테두리 색상이 자동으로 변경됩니다.
159
- 여기 있는 버튼들은 제공된 Button 컴포넌트로 구성되었습니다.
160
- </p>
161
- </CardContent>
162
- <CardFooter>
163
- <div style={{ display: "flex", gap: "12px" }}>
164
- <Button type="primary">Primary Button</Button>
165
- <Button type="secondary">Secondary Button</Button>
166
- <Button type="gray">Gray Button</Button>
167
- </div>
168
- </CardFooter>
169
- </Card>
170
- </div>
171
- </main>
172
- )
173
- }`,
174
- },
175
- // ✅ tsconfig.json: "@/*" 경로 별칭 설정 (예: @/build/typescript/theme)
176
- {
177
- path: path.join(projectRoot, "tsconfig.json"),
178
- content: JSON.stringify({
179
- compilerOptions: {
180
- target: "ES2017",
181
- lib: ["dom", "dom.iterable", "esnext"],
182
- allowJs: true,
183
- skipLibCheck: true,
184
- strict: true,
185
- noEmit: true,
186
- esModuleInterop: true,
187
- module: "esnext",
188
- moduleResolution: "bundler",
189
- resolveJsonModule: true,
190
- isolatedModules: true,
191
- jsx: "preserve",
192
- incremental: true,
193
- plugins: [{ name: "next" }],
194
- paths: {
195
- "@/*": ["./*"]
196
- }
197
- },
198
- include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
199
- exclude: ["node_modules"]
200
- }, null, 2),
201
- },
202
- // ✅ next.config.ts: Next.js 기본 설정
203
- {
204
- path: path.join(projectRoot, "next.config.ts"),
205
- content: `import type { NextConfig } from "next";
206
-
207
- const nextConfig: NextConfig = {
208
- reactStrictMode: false,
209
- };
210
-
211
- export default nextConfig;`,
212
- },
213
- // ✅ postcss.config.mjs: Tailwind CSS v4 PostCSS 플러그인 등록 (@theme, @import "tailwindcss" 인식)
214
- {
215
- path: path.join(projectRoot, "postcss.config.mjs"),
216
- content: `/** @type {import('postcss-load-config').Config} */
217
- const config = {
218
- plugins: {
219
- "@tailwindcss/postcss": {},
220
- },
221
- };
222
-
223
- export default config;`,
224
- },
225
- ];
226
-
227
- files.forEach(({ path, content }) => {
228
- fs.writeFileSync(path, content);
229
- console.log(`✅ Created ${path}`);
230
- });
231
-
232
- // ✅ 토큰 빌드 먼저 실행: Theme.tsx가 @/build/typescript/theme를 import하므로
233
- // 컴포넌트 복사 전에 build-tokens.js를 실행해 theme.ts를 미리 생성해야 합니다.
234
- const buildTokensPath = path.join(projectRoot, "build", "build-tokens.js");
235
- if (fs.existsSync(buildTokensPath)) {
236
- const tokenBuildSpinner = ora("Building design tokens (generating build/typescript/theme.ts)...").start();
237
- try {
238
- await execPromise(`node "${buildTokensPath}"`, { cwd: projectRoot });
239
- tokenBuildSpinner.succeed("Design tokens built successfully!");
240
- } catch (err) {
241
- tokenBuildSpinner.fail("Error during token build");
242
- console.error(err);
243
- return;
244
- }
245
- } else {
246
- console.warn("⚠️ build-tokens.js not found, skipping token build.");
247
- }
248
-
249
- // add.js를 실행하여 Theme, Card 컴포넌트 및 하위 의존성을 복사합니다.
250
- const addScriptPath = path.join(__dirname, "add.js");
251
- if (fs.existsSync(addScriptPath)) {
252
- console.log("⏳ Adding default components via add.js...");
253
- try {
254
- // Theme 설치 (Theme 내부에 Button 등이 포함되어 있음)
255
- await execPromise(`node "${addScriptPath}" Theme`, { cwd: projectRoot });
256
- // Card 설치
257
- await execPromise(`node "${addScriptPath}" Card`, { cwd: projectRoot });
258
- console.log("✅ Default components (Theme, Card, Button 등) added successfully.");
259
- } catch (err) {
260
- console.error("❌ Failed to add components:", err);
261
- }
262
- }
263
- } else {
264
- console.log("🚀 Installation skipped.");
265
- }
266
-
267
- // ✅ 필수 파일 복사 (Next.js 설치 여부와 관계없이 실행)
268
- copyTo(tokenFile, targetTokenFile, "tokens/tokens.json");
269
- copyTo(buildFile, targetBuildFile, "build/build-tokens.js");
270
- copyTo(textCssFile, targetTextCssFile, "css/text.css");
271
-
272
- console.log("🚀 Token setup complete!");
273
- })
274
- .catch((error) => {
275
- console.error("❌ Error:", error);
276
- });