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 +43 -80
- package/core/add.js +109 -16
- package/core/init.js +288 -82
- package/core/list.js +27 -38
- package/package.json +1 -1
- package/utils/copy.js +5 -28
- package/utils/getConfig.js +2 -11
- package/core/frontend.js +0 -57
- package/templates/typescript/frontend/src/components/Avatar/Avatar.tsx +0 -34
- package/templates/typescript/frontend/src/components/Avatar/AvatarFallback.tsx +0 -35
- package/templates/typescript/frontend/src/components/Avatar/AvatarImage.tsx +0 -23
- package/templates/typescript/frontend/src/components/Avatar/index.ts +0 -3
- package/templates/typescript/pages/Hero/Hero.tsx +0 -8
- package/templates/typescript/pages/Hero/index.ts +0 -1
- /package/templates/typescript/frontend/{src/pages → Hero}/Hero.tsx +0 -0
- /package/templates/typescript/frontend/{src/pages → Hero}/index.ts +0 -0
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
|
-
//
|
|
9
|
+
// alias support
|
|
11
10
|
const layerMap = {
|
|
12
|
-
c: "
|
|
13
|
-
l: "
|
|
14
|
-
b: "blocks",
|
|
15
|
-
p: "pages"
|
|
11
|
+
c: "component",
|
|
12
|
+
l: "layout"
|
|
16
13
|
};
|
|
17
14
|
|
|
18
15
|
// ------------------------
|
|
19
|
-
// INIT / DOCTOR
|
|
16
|
+
// INIT / DOCTOR
|
|
20
17
|
// ------------------------
|
|
21
18
|
if (args[0] === "init") {
|
|
22
|
-
|
|
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
|
-
//
|
|
42
|
-
// sparesdev
|
|
43
|
-
// sparesdev add frontend
|
|
29
|
+
// 🔥 GLOBAL LIST (NO LAYER)
|
|
30
|
+
// sparesdev list
|
|
44
31
|
// ------------------------
|
|
45
|
-
if (args[0] === "
|
|
46
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"
|
|
75
|
-
|
|
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
|
-
|
|
85
|
-
list(layer);
|
|
86
|
-
break;
|
|
51
|
+
const validLayers = ["global", "storefront"];
|
|
87
52
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
53
|
+
if (!validLayers.includes(layer)) {
|
|
54
|
+
console.log(`
|
|
55
|
+
❌ Invalid layer
|
|
91
56
|
|
|
92
|
-
|
|
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
|
-
//
|
|
65
|
+
// ROUTING
|
|
97
66
|
// ------------------------
|
|
98
|
-
|
|
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
|
|
103
|
-
sparesdev
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
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 = "
|
|
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
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
? ["components", "layouts", "blocks", "pages"]
|
|
33
|
-
: [layer];
|
|
47
|
+
if (fs.existsSync(utilsPath)) {
|
|
48
|
+
const cnPath = path.join(targetUtils, "cn.ts");
|
|
34
49
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
138
|
+
|
|
139
|
+
copyRecursive(found.src, found.dest);
|
|
70
140
|
installed.push(found.match);
|
|
71
|
-
|
|
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
|
|
4
|
-
import { copyRecursive } from "../utils/copy.js";
|
|
5
|
-
import { createRequire } from "module";
|
|
2
|
+
import { execSync } from "child_process";
|
|
6
3
|
|
|
7
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
//
|
|
39
|
-
|
|
57
|
+
// Tailwind plugin
|
|
58
|
+
if (!content.includes("tailwindcss()")) {
|
|
59
|
+
content = content.replace(
|
|
60
|
+
/plugins:\s*\[([^\]]*)\]/,
|
|
61
|
+
"plugins: [$1, tailwindcss()]"
|
|
62
|
+
);
|
|
63
|
+
}
|
|
40
64
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
const lang = "typescript";
|
|
80
|
+
console.log("✅ vite.config.ts configured");
|
|
81
|
+
}
|
|
54
82
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
console.log("
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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 = "
|
|
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
|
-
// 📁
|
|
43
|
+
// 📁 Paths setup
|
|
45
44
|
// -------------------------------
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
74
|
-
|
|
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(`📦 ${
|
|
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
|
|
95
|
+
console.log("❌ No components found\n");
|
|
107
96
|
}
|
|
108
97
|
}
|
package/package.json
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/utils/getConfig.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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 +0,0 @@
|
|
|
1
|
-
export { default as Hero } from './Hero';
|
|
File without changes
|
|
File without changes
|