sparesdev 0.0.2 → 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/bin/index.js CHANGED
@@ -3,27 +3,20 @@ import { add } from "../core/add.js";
3
3
  import { init } from "../core/init.js";
4
4
  import { list } from "../core/list.js";
5
5
  import { doctor } from "../core/doctor.js";
6
- import { frontend } from "../core/frontend.js";
7
6
 
8
7
  const args = process.argv.slice(2);
9
8
 
10
- // aliases
9
+ // alias support
11
10
  const layerMap = {
12
- c: "components",
13
- l: "layouts",
14
- b: "blocks",
15
- p: "pages"
11
+ c: "component",
12
+ l: "layout"
16
13
  };
17
14
 
18
15
  // ------------------------
19
- // INIT / DOCTOR / LIST
16
+ // INIT / DOCTOR
20
17
  // ------------------------
21
18
  if (args[0] === "init") {
22
- const mode = args.includes("--full")
23
- ? "full"
24
- : "minimal";
25
-
26
- await init(mode);
19
+ await init();
27
20
  process.exit();
28
21
  }
29
22
 
@@ -32,95 +25,65 @@ if (args[0] === "doctor") {
32
25
  process.exit();
33
26
  }
34
27
 
35
- if (args[0] === "list") {
36
- list();
37
- process.exit();
38
- }
39
-
40
28
  // ------------------------
41
- // SIMPLE ADD
42
- // sparesdev add button
43
- // sparesdev add frontend
29
+ // 🔥 GLOBAL LIST (NO LAYER)
30
+ // sparesdev list
44
31
  // ------------------------
45
- if (args[0] === "add") {
46
- const item = args[1];
47
-
48
- if (!item) {
49
- console.log("❌ Provide a component, block, page, or frontend");
50
- process.exit(1);
51
- }
52
-
53
- // frontend starter
54
- if (item === "frontend") {
55
- await frontend();
56
- process.exit();
57
- }
58
-
59
- add("all", args.slice(1));
32
+ if (args[0] === "list") {
33
+ list(); // show EVERYTHING
60
34
  process.exit();
61
35
  }
62
36
 
63
37
  // ------------------------
64
- // CATEGORY ADD
65
- // sparesdev components add button
38
+ // LAYER COMMANDS
66
39
  // ------------------------
67
40
  let [layer, command, ...rest] = args;
68
41
 
69
42
  layer = layerMap[layer] || layer;
70
43
 
71
- const validLayers = [
72
- "components",
73
- "layouts",
74
- "blocks",
75
- "pages"
76
- ];
77
-
78
- if (validLayers.includes(layer)) {
79
- switch (command) {
80
- case "add":
81
- add(layer, rest);
82
- break;
44
+ // fallback: sparesdev add button → global
45
+ if (!command) {
46
+ command = layer;
47
+ layer = "global";
48
+ rest = args.slice(1);
49
+ }
83
50
 
84
- case "list":
85
- list(layer);
86
- break;
51
+ const validLayers = ["global", "storefront"];
87
52
 
88
- default:
89
- console.log("❌ Invalid command");
90
- }
53
+ if (!validLayers.includes(layer)) {
54
+ console.log(`
55
+ ❌ Invalid layer
91
56
 
92
- process.exit();
57
+ Use:
58
+ sparesdev global add button
59
+ sparesdev storefront add product-card
60
+ `);
61
+ process.exit(1);
93
62
  }
94
63
 
95
64
  // ------------------------
96
- // HELP
65
+ // ROUTING
97
66
  // ------------------------
98
- console.log(`
67
+ switch (command) {
68
+ case "add":
69
+ add(layer, rest);
70
+ break;
71
+
72
+ case "list":
73
+ list(layer); // 👈 layer-specific
74
+ break;
75
+
76
+ default:
77
+ console.log(`
99
78
  sparesdev CLI
100
79
 
101
80
  Usage:
102
- sparesdev init
103
- sparesdev doctor
81
+ sparesdev global add button
82
+ sparesdev storefront add product-card
104
83
  sparesdev list
105
84
 
106
- sparesdev add frontend
107
- sparesdev add button
108
- sparesdev add login-page
109
-
110
- Optional category:
111
- sparesdev components add button
112
- sparesdev layouts add header
113
- sparesdev blocks add hero
114
- sparesdev pages add login-page
115
-
116
- Commands:
117
- init Initialize project
118
- doctor Check setup health
119
- list List all available templates
120
-
121
85
  Aliases:
122
- c = components
123
- l = layouts
124
- b = blocks
125
- p = pages
126
- `);
86
+ sparesdev g add button
87
+ sparesdev s add product-card
88
+ `);
89
+ }
package/core/add.js CHANGED
@@ -6,11 +6,12 @@ import { createRequire } from "module";
6
6
 
7
7
  const require = createRequire(import.meta.url);
8
8
 
9
+ // 🔥 normalize helper (fixes breDCRumb issue)
9
10
  function normalize(str) {
10
11
  return str.toLowerCase().replace(/[^a-z0-9]/g, "");
11
12
  }
12
13
 
13
- export function add(layer, components, options = {}) {
14
+ export function add(layer, components) {
14
15
  if (!components.length) {
15
16
  console.error("❌ Provide component names");
16
17
  process.exit(1);
@@ -18,37 +19,85 @@ export function add(layer, components, options = {}) {
18
19
 
19
20
  const config = getConfig();
20
21
  const lang = "typescript";
21
- const packageName = "sparesdev";
22
+ const packageName = "spares";
23
+
24
+ if (!fs.existsSync("src/tokens.css")) {
25
+ console.log("⚠️ Tailwind v4 tokens missing (src/tokens.css)");
26
+ }
27
+
28
+ checkDeps();
22
29
 
23
30
  const packageRoot = path.dirname(
24
31
  require.resolve(`${packageName}/package.json`)
25
32
  );
33
+
26
34
  const projectRoot = process.cwd();
27
35
 
28
- const installed = [];
36
+ const utilsPath = path.join(packageRoot, "templates", lang, "utils");
37
+ const targetUtils = path.join(
38
+ projectRoot,
39
+ config.utilsPath || "src/utils"
40
+ );
41
+
42
+ if (!fs.existsSync(targetUtils)) {
43
+ fs.mkdirSync(targetUtils, { recursive: true });
44
+ console.log("📁 created utils folder");
45
+ }
29
46
 
30
- const searchLayers =
31
- layer === "all"
32
- ? ["components", "layouts", "blocks", "pages"]
33
- : [layer];
47
+ if (fs.existsSync(utilsPath)) {
48
+ const cnPath = path.join(targetUtils, "cn.ts");
34
49
 
35
- const basePaths = searchLayers.map(section => ({
36
- type: section,
37
- path: path.join(packageRoot, "templates", lang, section),
38
- target: path.join(projectRoot, "src", section)
39
- }));
50
+ if (!fs.existsSync(cnPath)) {
51
+ copyRecursive(utilsPath, targetUtils);
52
+ console.log(" utils (cn) added");
53
+ } else {
54
+ console.log("ℹ️ utils already exists, skipping");
55
+ }
56
+ }
57
+
58
+ const installed = [];
40
59
 
41
60
  for (const nameInput of components) {
42
61
  const name = nameInput;
43
62
 
63
+ let basePaths = [];
64
+
65
+ if (layer === "global") {
66
+ basePaths = [
67
+ {
68
+ type: "components",
69
+ path: path.join(packageRoot, "templates", lang, "global", "components"),
70
+ target: path.join(projectRoot, "src/global/components")
71
+ },
72
+ {
73
+ type: "layouts",
74
+ path: path.join(packageRoot, "templates", lang, "global", "layouts"),
75
+ target: path.join(projectRoot, "src/global/layouts")
76
+ }
77
+ ];
78
+ }
79
+
80
+ if (layer === "storefront") {
81
+ basePaths = [
82
+ {
83
+ type: "storefront",
84
+ path: path.join(packageRoot, "templates", lang, "storefront"),
85
+ target: path.join(projectRoot, "src/storefront")
86
+ }
87
+ ];
88
+ }
89
+
44
90
  let found = null;
45
91
 
46
92
  for (const bp of basePaths) {
47
93
  if (!fs.existsSync(bp.path)) continue;
94
+
48
95
  const available = fs.readdirSync(bp.path);
96
+
49
97
  const match = available.find(
50
- item => normalize(item) === normalize(name)
98
+ c => normalize(c) === normalize(name)
51
99
  );
100
+
52
101
  if (match) {
53
102
  found = {
54
103
  match,
@@ -60,15 +109,37 @@ export function add(layer, components, options = {}) {
60
109
  }
61
110
  }
62
111
 
112
+ // ❌ not found → suggest
63
113
  if (!found) {
64
- console.log(`❌ ${name} not found`);
114
+ let suggestions = [];
115
+
116
+ for (const bp of basePaths) {
117
+ if (!fs.existsSync(bp.path)) continue;
118
+
119
+ const available = fs.readdirSync(bp.path);
120
+
121
+ const suggestion = available.find(c =>
122
+ normalize(c).includes(normalize(name))
123
+ );
124
+
125
+ if (suggestion) suggestions.push(suggestion);
126
+ }
127
+
128
+ if (suggestions.length) {
129
+ console.log(`❌ ${name} not found. Did you mean: ${suggestions[0]}?`);
130
+ } else {
131
+ console.log(`❌ ${name} not found`);
132
+ }
133
+
65
134
  continue;
66
135
  }
67
136
 
68
137
  fs.mkdirSync(path.dirname(found.dest), { recursive: true });
69
- copyRecursive(found.src, found.dest, options);
138
+
139
+ copyRecursive(found.src, found.dest);
70
140
  installed.push(found.match);
71
- console.log(`✅ ${found.match} → src/${found.type}`);
141
+
142
+ console.log(`✅ ${found.match} → ${layer}/${found.type}`);
72
143
  }
73
144
 
74
145
  if (installed.length) {
@@ -76,4 +147,26 @@ export function add(layer, components, options = {}) {
76
147
  } else {
77
148
  console.log("❌ No components installed");
78
149
  }
150
+ }
151
+
152
+ // -------------------------------
153
+ function checkDeps() {
154
+ try {
155
+ const pkg = JSON.parse(fs.readFileSync("package.json", "utf-8"));
156
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
157
+
158
+ const required = ["clsx", "tailwind-merge"];
159
+ const missing = required.filter(d => !deps?.[d]);
160
+
161
+ if (missing.length) {
162
+ console.log(`
163
+ ⚠️ Missing dependencies: ${missing.join(", ")}
164
+
165
+ Run:
166
+ npm install ${missing.join(" ")}
167
+ `);
168
+ }
169
+ } catch {
170
+ console.log("⚠️ Could not read package.json");
171
+ }
79
172
  }
package/core/init.js CHANGED
@@ -1,109 +1,315 @@
1
- #!/usr/bin/env node
2
1
  import fs from "fs";
3
- import path from "path";
4
- import { copyRecursive } from "../utils/copy.js";
5
- import { createRequire } from "module";
2
+ import { execSync } from "child_process";
6
3
 
7
- const require = createRequire(import.meta.url);
4
+ // -----------------------------
5
+ // 📦 Dependencies
6
+ // -----------------------------
7
+ function getMissingDependencies() {
8
+ const required = [
9
+ "react",
10
+ "react-dom",
11
+ "react-router-dom",
12
+ "clsx",
13
+ "tailwind-merge",
14
+ "class-variance-authority"
15
+ ];
8
16
 
9
- // -------------------------------
10
- // STEP 3 → CREATE CONFIG
11
- // -------------------------------
12
- export function createConfig() {
13
- const configPath = path.join(process.cwd(), "sparesdev.config.json");
17
+ try {
18
+ const pkg = JSON.parse(fs.readFileSync("package.json", "utf-8"));
19
+
20
+ const installed = {
21
+ ...pkg.dependencies,
22
+ ...pkg.devDependencies
23
+ };
24
+
25
+ return required.filter(dep => !installed?.[dep]);
26
+ } catch {
27
+ return required;
28
+ }
29
+ }
14
30
 
15
- if (fs.existsSync(configPath)) {
16
- console.log("ℹ️ Config already exists, skipping");
31
+ // -----------------------------
32
+ // ⚙️ Vite Config
33
+ // -----------------------------
34
+ function patchViteConfig() {
35
+ const viteConfigPath = "vite.config.ts";
36
+
37
+ if (!fs.existsSync(viteConfigPath)) {
38
+ console.log("⚠️ vite.config.ts not found, skipping patch");
17
39
  return;
18
40
  }
19
41
 
20
- const config = {
21
- language: "typescript",
22
- version: "3.0",
23
- style: "default",
24
- componentsPath: "src/components",
25
- utilsPath: "src/utils"
26
- };
42
+ let content = fs.readFileSync(viteConfigPath, "utf-8");
27
43
 
28
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
29
- console.log("✅ sparesdev.config.json created");
30
- }
44
+ // Tailwind import
45
+ if (!content.includes("@tailwindcss/vite")) {
46
+ content = content.replace(
47
+ /import react from ['"]@vitejs\/plugin-react['"]/,
48
+ `import react from '@vitejs/plugin-react'\nimport tailwindcss from '@tailwindcss/vite'`
49
+ );
50
+ }
31
51
 
32
- // -------------------------------
33
- // INIT FUNCTION
34
- // -------------------------------
35
- export async function init(mode = "minimal") {
36
- console.log(`\n🎯 Initializing sparesdev (${mode} mode)...\n`);
52
+ // Path import
53
+ if (!content.includes('import path from "path"')) {
54
+ content = `import path from "path"\n` + content;
55
+ }
37
56
 
38
- // STEP 1 → create config only
39
- createConfig();
57
+ // Tailwind plugin
58
+ if (!content.includes("tailwindcss()")) {
59
+ content = content.replace(
60
+ /plugins:\s*\[([^\]]*)\]/,
61
+ "plugins: [$1, tailwindcss()]"
62
+ );
63
+ }
40
64
 
41
- if (mode === "minimal") {
42
- console.log("🎉 Minimal setup completed. No dependencies or files modified.\n");
43
- return;
65
+ // Alias
66
+ if (!content.includes("resolve:")) {
67
+ content = content.replace(
68
+ /export default defineConfig\(\{/,
69
+ `export default defineConfig({
70
+ resolve: {
71
+ alias: {
72
+ "@": path.resolve(__dirname, "./src"),
73
+ },
74
+ },`
75
+ );
44
76
  }
45
77
 
46
- // -------------------------------
47
- // Full setup
48
- // -------------------------------
49
- console.log("🚀 Running full setup...\n");
78
+ fs.writeFileSync(viteConfigPath, content);
50
79
 
51
- const projectRoot = process.cwd();
52
- const packageName = "sparesdev";
53
- const lang = "typescript";
80
+ console.log("✅ vite.config.ts configured");
81
+ }
54
82
 
55
- let packageRoot;
56
- try {
57
- packageRoot = path.dirname(
58
- require.resolve(`${packageName}/package.json`)
59
- );
60
- } catch {
61
- console.log(" sparesdev package not found. Full setup aborted.");
83
+ // -----------------------------
84
+ // 📁 Project Structure
85
+ // -----------------------------
86
+ function ensureSrcFolder() {
87
+ if (!fs.existsSync("src")) {
88
+ fs.mkdirSync("src", { recursive: true });
89
+ console.log("📁 Created src folder");
90
+ }
91
+ }
92
+
93
+ // -----------------------------
94
+ // 🎨 Tokens
95
+ // -----------------------------
96
+ function createTokensCss() {
97
+ const tokensPath = "src/tokens.css";
98
+
99
+ if (fs.existsSync(tokensPath)) {
100
+ console.log("ℹ️ tokens.css already exists");
62
101
  return;
63
102
  }
64
103
 
65
- // -------------------------------
66
- // Install utils if missing
67
- // -------------------------------
68
- const utilsPath = path.join(packageRoot, "templates", lang, "utils");
69
- const targetUtils = path.join(projectRoot, "src", "utils");
104
+ const content = `@theme {
105
+
106
+ /* Screens */
107
+ --breakpoint-sm: 640px;
108
+ --breakpoint-md: 768px;
109
+ --breakpoint-lg: 1024px;
110
+
111
+ /* Fonts */
112
+ --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
113
+
114
+ /* Text */
115
+ --text-xs: 10px;
116
+ --text-sm: 12px;
117
+ --text-base: 14px;
118
+ --text-lg: 16px;
119
+ --text-xl: 17.5px;
120
+
121
+ /* Colors */
122
+ --color-background: 255 255 255;
123
+ --color-text: 0 0 0;
124
+
125
+ /* Radius */
126
+ --radius-sm: 4px;
127
+ --radius-base: 8px;
128
+ --radius-lg: 12px;
129
+
130
+ /* Shadows */
131
+ --shadow-sm: 0 1px 3px -1px rgba(0, 0, 0, 0.16);
70
132
 
71
- if (!fs.existsSync(targetUtils)) fs.mkdirSync(targetUtils, { recursive: true });
133
+ /* Spacing */
134
+ --spacing-1: 4px;
135
+ --spacing-2: 8px;
136
+ --spacing-4: 16px;
137
+ }
138
+ `;
139
+
140
+ fs.writeFileSync(tokensPath, content);
141
+ console.log("✅ Created src/tokens.css");
142
+ }
143
+
144
+ // -----------------------------
145
+ // 🎯 Index CSS (overwrite)
146
+ // -----------------------------
147
+ function createIndexCss() {
148
+ const indexPath = "src/index.css";
149
+
150
+ const content = `@import "tailwindcss";
151
+ @import "./tokens.css";
152
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');
153
+
154
+ body {
155
+ background: rgb(var(--color-background));
156
+ color: rgb(var(--color-text));
157
+ font-feature-settings: "rlig" 1, "calt" 1;
158
+ font-family: var(--font-sans);
159
+ }
160
+
161
+ .hide-scrollbar {
162
+ -ms-overflow-style: none;
163
+ scrollbar-width: none;
164
+ }
165
+
166
+ .hide-scrollbar::-webkit-scrollbar {
167
+ display: none;
168
+ }
169
+ `;
72
170
 
73
- const cnPath = path.join(targetUtils, "cn.ts");
74
- if (!fs.existsSync(cnPath)) {
75
- copyRecursive(utilsPath, targetUtils);
76
- console.log("✅ utils (cn.ts) added");
77
- } else {
78
- console.log("ℹ️ utils already exists, skipping");
171
+ fs.writeFileSync(indexPath, content);
172
+
173
+ console.log("✅ src/index.css replaced");
174
+ }
175
+
176
+ // -----------------------------
177
+ // ⚙️ TS Config (FIXED)
178
+ // -----------------------------
179
+ function patchTsconfigAlias() {
180
+ const tsconfigPath = "tsconfig.app.json";
181
+
182
+ if (!fs.existsSync(tsconfigPath)) {
183
+ console.log("⚠️ tsconfig.app.json not found, skipping");
184
+ return;
79
185
  }
80
186
 
81
- // -------------------------------
82
- // Install tokens.css if missing
83
- // -------------------------------
84
- const tokensSrc = path.join(packageRoot, "templates", lang, "tokens.css");
85
- const tokensDest = path.join(projectRoot, "src", "tokens.css");
86
-
87
- if (!fs.existsSync(tokensDest) && fs.existsSync(tokensSrc)) {
88
- copyRecursive(tokensSrc, tokensDest);
89
- console.log("✅ tokens.css added");
90
- } else if (fs.existsSync(tokensDest)) {
91
- console.log("ℹ️ tokens.css already exists, skipping");
187
+ let content = fs.readFileSync(tsconfigPath, "utf-8");
188
+
189
+ if (content.includes('"@/*"')) {
190
+ console.log("ℹ️ tsconfig alias already configured");
191
+ return;
92
192
  }
93
193
 
94
- // -------------------------------
95
- // Patch Vite / TS config (if missing)
96
- // -------------------------------
97
- const viteConfig = path.join(projectRoot, "vite.config.ts");
98
- if (fs.existsSync(viteConfig)) {
99
- console.log("ℹ️ Vite config exists, skipping patch");
100
- } else {
101
- const viteTemplate = path.join(packageRoot, "templates", lang, "vite.config.ts");
102
- if (fs.existsSync(viteTemplate)) {
103
- copyRecursive(viteTemplate, viteConfig);
104
- console.log("✅ Vite config added");
194
+ content = content.replace(
195
+ /"compilerOptions"\s*:\s*\{/,
196
+ `"compilerOptions": {
197
+ "baseUrl": ".",
198
+ "paths": {
199
+ "@/*": ["src/*"]
200
+ },`
201
+ );
202
+
203
+ fs.writeFileSync(tsconfigPath, content);
204
+
205
+ console.log("✅ tsconfig alias configured");
206
+ }
207
+
208
+ // -----------------------------
209
+ // 🔁 ScrollToTop
210
+ // -----------------------------
211
+ function createScrollToTop() {
212
+ const filePath = "src/ScrollToTop.tsx";
213
+
214
+ const content = `import { useEffect } from "react";
215
+ import { useLocation } from "react-router-dom";
216
+
217
+ function ScrollToTop() {
218
+ const { pathname } = useLocation();
219
+
220
+ useEffect(() => {
221
+ window.scrollTo(0, 0);
222
+ }, [pathname]);
223
+
224
+ return null;
225
+ }
226
+
227
+ export default ScrollToTop;
228
+ `;
229
+
230
+ fs.writeFileSync(filePath, content);
231
+
232
+ console.log("✅ Created ScrollToTop.tsx");
233
+ }
234
+
235
+ // -----------------------------
236
+ // 🚀 main.tsx
237
+ // -----------------------------
238
+ function createMainTsx() {
239
+ const filePath = "src/main.tsx";
240
+
241
+ const content = `import { StrictMode } from "react";
242
+ import { createRoot } from "react-dom/client";
243
+ import { BrowserRouter } from "react-router-dom";
244
+
245
+ import "./index.css";
246
+ import App from "./App.tsx";
247
+ import ScrollToTop from "./ScrollToTop.tsx";
248
+
249
+ createRoot(document.getElementById("root")!).render(
250
+ <StrictMode>
251
+ <BrowserRouter>
252
+ <ScrollToTop />
253
+ <App />
254
+ </BrowserRouter>
255
+ </StrictMode>
256
+ );
257
+ `;
258
+
259
+ fs.writeFileSync(filePath, content);
260
+
261
+ console.log("✅ Replaced src/main.tsx");
262
+ }
263
+
264
+ // -----------------------------
265
+ // 🧠 INIT
266
+ // -----------------------------
267
+ export async function init() {
268
+ const config = {
269
+ language: "typescript",
270
+ version: "3.0",
271
+ style: "default",
272
+ componentsPath: "src/components",
273
+ utilsPath: "src/utils"
274
+ };
275
+
276
+ fs.writeFileSync(
277
+ "sparesdev.config.json",
278
+ JSON.stringify(config, null, 2)
279
+ );
280
+
281
+ console.log("\n✅ Initialized SparesDev\n");
282
+
283
+ const missingDeps = getMissingDependencies();
284
+
285
+ try {
286
+ if (missingDeps.length) {
287
+ console.log(`📦 Installing: ${missingDeps.join(", ")}\n`);
288
+ execSync(`npm install ${missingDeps.join(" ")}`, {
289
+ stdio: "inherit"
290
+ });
291
+ } else {
292
+ console.log("✅ Core dependencies already installed\n");
105
293
  }
106
- }
107
294
 
108
- console.log("\n🎉 Full setup completed.\n");
295
+ console.log("🎨 Installing Tailwind...\n");
296
+
297
+ execSync(
298
+ "npm install -D tailwindcss @tailwindcss/vite",
299
+ { stdio: "inherit" }
300
+ );
301
+
302
+ ensureSrcFolder();
303
+ patchViteConfig();
304
+ patchTsconfigAlias();
305
+ createTokensCss();
306
+ createIndexCss();
307
+ createScrollToTop();
308
+ createMainTsx();
309
+
310
+ console.log("\n🎉 SparesDev setup completed\n");
311
+ } catch (err) {
312
+ console.log("\n❌ Setup failed");
313
+ console.error(err.message);
314
+ }
109
315
  }
package/core/list.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import { createRequire } from "module";
4
-
5
4
  const require = createRequire(import.meta.url);
6
5
 
7
6
  // -------------------------------
@@ -27,7 +26,7 @@ function printTree(dir, indent = " ") {
27
26
  // -------------------------------
28
27
  export function list(layer) {
29
28
  const lang = "typescript";
30
- const packageName = "sparesdev";
29
+ const packageName = "spares";
31
30
 
32
31
  let packageRoot;
33
32
 
@@ -41,55 +40,45 @@ export function list(layer) {
41
40
  }
42
41
 
43
42
  // -------------------------------
44
- // 📁 Sections
43
+ // 📁 Paths setup
45
44
  // -------------------------------
46
- const sections = [
47
- {
48
- key: "components",
49
- label: "Components"
50
- },
51
- {
52
- key: "layouts",
53
- label: "Layouts"
54
- },
55
- {
56
- key: "blocks",
57
- label: "Blocks"
58
- },
59
- {
60
- key: "pages",
61
- label: "Pages"
62
- }
63
- ];
45
+ let paths = [];
46
+
47
+ if (!layer || layer === "global") {
48
+ paths.push({
49
+ label: "Components",
50
+ dir: path.join(packageRoot, "templates", lang, "global", "components")
51
+ });
52
+
53
+ paths.push({
54
+ label: "Layouts",
55
+ dir: path.join(packageRoot, "templates", lang, "global", "layouts")
56
+ });
57
+ }
64
58
 
65
- const selectedSections = layer
66
- ? sections.filter(section => section.key === layer)
67
- : sections;
59
+ if (!layer || layer === "storefront") {
60
+ paths.push({
61
+ label: "Storefront",
62
+ dir: path.join(packageRoot, "templates", lang, "storefront")
63
+ });
64
+ }
68
65
 
69
66
  console.log("");
70
67
 
71
68
  let hasAny = false;
72
69
 
73
- for (const section of selectedSections) {
74
- const dir = path.join(
75
- packageRoot,
76
- "templates",
77
- lang,
78
- section.key
79
- );
80
-
81
- if (!fs.existsSync(dir)) continue;
82
-
83
- const items = fs.readdirSync(dir);
70
+ for (const p of paths) {
71
+ if (!fs.existsSync(p.dir)) continue;
84
72
 
73
+ const items = fs.readdirSync(p.dir);
85
74
  if (!items.length) continue;
86
75
 
87
76
  hasAny = true;
88
77
 
89
- console.log(`📦 ${section.label}:\n`);
78
+ console.log(`📦 ${p.label}:\n`);
90
79
 
91
80
  for (const item of items) {
92
- const fullPath = path.join(dir, item);
81
+ const fullPath = path.join(p.dir, item);
93
82
  const isDir = fs.statSync(fullPath).isDirectory();
94
83
 
95
84
  console.log(` - ${item}${isDir ? "/" : ""}`);
@@ -103,6 +92,6 @@ export function list(layer) {
103
92
  }
104
93
 
105
94
  if (!hasAny) {
106
- console.log("❌ No templates found\n");
95
+ console.log("❌ No components found\n");
107
96
  }
108
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparesdev",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "bin": {
package/utils/copy.js CHANGED
@@ -1,42 +1,19 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
 
4
- /**
5
- * copyRecursive(src, dest, options)
6
- * options:
7
- * - force: overwrite existing files
8
- * - safe: skip if exists
9
- */
10
- export function copyRecursive(src, dest, options = {}) {
11
- if (!fs.existsSync(src)) {
12
- console.log(`❌ Source not found: ${src}`);
13
- return;
14
- }
15
-
4
+ export function copyRecursive(src, dest) {
16
5
  fs.mkdirSync(dest, { recursive: true });
17
6
 
18
7
  for (const item of fs.readdirSync(src)) {
19
8
  const srcItem = path.join(src, item);
20
9
  const destItem = path.join(dest, item);
21
10
 
22
- const stats = fs.statSync(srcItem);
23
-
24
- if (stats.isDirectory()) {
25
- copyRecursive(srcItem, destItem, options);
11
+ if (fs.statSync(srcItem).isDirectory()) {
12
+ copyRecursive(srcItem, destItem);
26
13
  } else {
27
- if (fs.existsSync(destItem)) {
28
- if (options.force) {
29
- fs.copyFileSync(srcItem, destItem);
30
- console.log(`✅ Overwritten: ${destItem}`);
31
- } else if (options.safe) {
32
- console.log(`ℹ️ Skipped existing: ${destItem}`);
33
- } else {
34
- console.log(`⚠️ Exists (use --force to overwrite): ${destItem}`);
35
- }
36
- continue;
14
+ if (!fs.existsSync(destItem)) {
15
+ fs.copyFileSync(srcItem, destItem);
37
16
  }
38
- fs.copyFileSync(srcItem, destItem);
39
- console.log(`✅ Copied: ${destItem}`);
40
17
  }
41
18
  }
42
19
  }
@@ -1,19 +1,10 @@
1
1
  import fs from "fs";
2
2
 
3
- const CONFIG_FILE = "sparesdev.config.json";
4
-
5
3
  export function getConfig() {
6
- if (!fs.existsSync(CONFIG_FILE)) {
4
+ if (!fs.existsSync("sparesdev.config.json")) {
7
5
  console.error("❌ Run 'sparesdev init' first");
8
6
  process.exit(1);
9
7
  }
10
8
 
11
- try {
12
- const content = fs.readFileSync(CONFIG_FILE, "utf-8");
13
- return JSON.parse(content);
14
- } catch {
15
- console.error(`❌ Invalid ${CONFIG_FILE}`);
16
- console.error("💡 Delete it and run 'sparesdev init' again");
17
- process.exit(1);
18
- }
9
+ return JSON.parse(fs.readFileSync("sparesdev.config.json", "utf-8"));
19
10
  }
package/core/frontend.js DELETED
@@ -1,57 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- function writeFile(filePath, content) {
5
- fs.mkdirSync(path.dirname(filePath), {
6
- recursive: true
7
- });
8
-
9
- fs.writeFileSync(filePath, content);
10
- }
11
-
12
- export async function frontend() {
13
- const pages = {
14
- "src/pages/LandingPage.tsx": `function LandingPage() {
15
- return <div>Landing Page</div>;
16
- }
17
-
18
- export default LandingPage;
19
- `,
20
- "src/pages/PricingPage.tsx": `function PricingPage() {
21
- return <div>Pricing Page</div>;
22
- }
23
-
24
- export default PricingPage;
25
- `,
26
- "src/pages/LoginPage.tsx": `function LoginPage() {
27
- return <div>Login Page</div>;
28
- }
29
-
30
- export default LoginPage;
31
- `,
32
- "src/pages/DashboardPage.tsx": `function DashboardPage() {
33
- return <div>Dashboard Page</div>;
34
- }
35
-
36
- export default DashboardPage;
37
- `,
38
- "src/pages/TermsPage.tsx": `function TermsPage() {
39
- return <div>Terms Page</div>;
40
- }
41
-
42
- export default TermsPage;
43
- `,
44
- "src/pages/NotFoundPage.tsx": `function NotFoundPage() {
45
- return <div>404 Page</div>;
46
- }
47
-
48
- export default NotFoundPage;
49
- `
50
- };
51
-
52
- for (const [file, content] of Object.entries(pages)) {
53
- writeFile(file, content);
54
- }
55
-
56
- console.log("✅ Frontend starter created");
57
- }
@@ -1,34 +0,0 @@
1
- import React from "react"
2
- import { cva, type VariantProps } from "class-variance-authority"
3
- import { cn } from "@/utils/cn"
4
-
5
- const avatarVariants = cva(
6
- "relative flex shrink-0 overflow-hidden rounded-full items-center justify-center bg-neutral-000",
7
- {
8
- variants: {
9
- size: {
10
- sm: "w-6 h-6 text-xs font-normal",
11
- md: "w-9 h-9 text-sm font-normal",
12
- lg: "w-12 h-12 text-tiny font-normal",
13
- },
14
- },
15
- defaultVariants: {
16
- size: "md",
17
- },
18
- }
19
- )
20
-
21
- interface AvatarProps
22
- extends React.HTMLAttributes<HTMLDivElement>,
23
- VariantProps<typeof avatarVariants> {}
24
-
25
- function Avatar({ className, size, ...props }: AvatarProps) {
26
- return (
27
- <div
28
- className={cn(avatarVariants({ size }), className)}
29
- {...props}
30
- />
31
- )
32
- }
33
-
34
- export default Avatar
@@ -1,35 +0,0 @@
1
- import React from "react"
2
- import { cn } from "@/utils/cn"
3
-
4
- interface AvatarFallbackProps
5
- extends React.HTMLAttributes<HTMLSpanElement> {
6
- initials?: string
7
- }
8
-
9
- function AvatarFallback({
10
- initials,
11
- className,
12
- children,
13
- ...props
14
- }: AvatarFallbackProps) {
15
- const fallbackIcon = (
16
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
17
- <path d="M7 0.5C8.38071 0.5 9.5 1.61929 9.5 3C9.5 4.38071 8.38071 5.5 7 5.5C5.61929 5.5 4.5 4.38071 4.5 3C4.5 1.61929 5.61929 0.5 7 0.5Z" fill="currentColor" stroke="currentColor"/>
18
- <path d="M7 7.5C9.48528 7.5 11.5 9.51472 11.5 12V13.5H2.5V12C2.5 9.51472 4.51472 7.5 7 7.5Z" fill="currentColor" stroke="currentColor"/>
19
- </svg>
20
- )
21
-
22
- return (
23
- <span
24
- className={cn(
25
- "flex h-full w-full items-center justify-center",
26
- className
27
- )}
28
- {...props}
29
- >
30
- {children ?? initials ?? fallbackIcon}
31
- </span>
32
- )
33
- }
34
-
35
- export default AvatarFallback
@@ -1,23 +0,0 @@
1
- import React, { useState } from "react"
2
- import { cn } from "@/utils/cn"
3
-
4
- interface AvatarImageProps
5
- extends React.ImgHTMLAttributes<HTMLImageElement> {
6
- className?: "string";
7
- }
8
-
9
- function AvatarImage({ className, ...props }: AvatarImageProps) {
10
- const [error, setError] = useState(false)
11
-
12
- if (error) return null
13
-
14
- return (
15
- <img
16
- className={cn("aspect-square h-full w-full object-cover", className)}
17
- onError={() => setError(true)}
18
- {...props}
19
- />
20
- )
21
- }
22
-
23
- export default AvatarImage
@@ -1,3 +0,0 @@
1
- export { default as Avatar } from './Avatar';
2
- export { default as AvatarFallback } from './AvatarFallback';
3
- export { default as AvatarImage } from './AvatarImage';
@@ -1,8 +0,0 @@
1
-
2
- function Hero() {
3
- return (
4
- <div>Hero</div>
5
- )
6
- }
7
-
8
- export default Hero
@@ -1 +0,0 @@
1
- export { default as Hero } from './Hero';