stackscan 0.1.16 → 0.1.18
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/dist/chunk-OX7SYHLD.mjs +235 -0
- package/dist/cli.js +95 -61
- package/dist/cli.mjs +5 -3
- package/dist/index.js +91 -59
- package/dist/index.mjs +4 -4
- package/dist/scan.d.mts +2 -1
- package/dist/scan.d.ts +2 -1
- package/dist/scan.js +91 -59
- package/dist/scan.mjs +1 -1
- package/dist/scan.old.d.mts +8 -0
- package/dist/scan.old.d.ts +8 -0
- package/dist/scan.old.js +5770 -0
- package/dist/scan.old.mjs +205 -0
- package/package.json +3 -3
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
techMap
|
|
3
|
+
} from "./chunk-LSUI3VI4.mjs";
|
|
4
|
+
import {
|
|
5
|
+
copyAssets,
|
|
6
|
+
generateMarkdown
|
|
7
|
+
} from "./chunk-UJM3S22V.mjs";
|
|
8
|
+
import {
|
|
9
|
+
simple_icons_hex_default
|
|
10
|
+
} from "./chunk-EH2SEQZP.mjs";
|
|
11
|
+
|
|
12
|
+
// src/scan.ts
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import path from "path";
|
|
15
|
+
var BASE_DIR = path.join(process.cwd(), "public", "stackscan");
|
|
16
|
+
var SKIPPED_TECHS = [
|
|
17
|
+
"react-dom"
|
|
18
|
+
];
|
|
19
|
+
var CATEGORY_PRIORITY = [
|
|
20
|
+
"language",
|
|
21
|
+
"framework",
|
|
22
|
+
"mobile",
|
|
23
|
+
"frontend",
|
|
24
|
+
"backend",
|
|
25
|
+
"runtime",
|
|
26
|
+
"database",
|
|
27
|
+
"orm",
|
|
28
|
+
"auth",
|
|
29
|
+
"api",
|
|
30
|
+
"state",
|
|
31
|
+
"css",
|
|
32
|
+
"cloud",
|
|
33
|
+
"hosting",
|
|
34
|
+
"devops",
|
|
35
|
+
"container",
|
|
36
|
+
"ci",
|
|
37
|
+
"testing",
|
|
38
|
+
"build",
|
|
39
|
+
"lint",
|
|
40
|
+
"format",
|
|
41
|
+
"automation",
|
|
42
|
+
"package",
|
|
43
|
+
"ai",
|
|
44
|
+
"network",
|
|
45
|
+
"utility",
|
|
46
|
+
"cms",
|
|
47
|
+
"ssg",
|
|
48
|
+
"payment"
|
|
49
|
+
];
|
|
50
|
+
var getCategoryPriority = (cat) => {
|
|
51
|
+
const idx = CATEGORY_PRIORITY.indexOf(cat ? cat.toLowerCase() : "");
|
|
52
|
+
return idx === -1 ? 999 : idx;
|
|
53
|
+
};
|
|
54
|
+
async function analyzeProject(projectPath, options) {
|
|
55
|
+
const packageJsonPath = path.join(projectPath, "package.json");
|
|
56
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
57
|
+
throw new Error(`No package.json found at ${packageJsonPath}`);
|
|
58
|
+
}
|
|
59
|
+
const content = fs.readFileSync(packageJsonPath, "utf-8");
|
|
60
|
+
const pkg = JSON.parse(content);
|
|
61
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
62
|
+
const detectedTechs = [];
|
|
63
|
+
Object.keys(allDeps).forEach((dep) => {
|
|
64
|
+
if (SKIPPED_TECHS.includes(dep)) return;
|
|
65
|
+
if (techMap[dep]) {
|
|
66
|
+
const tech = techMap[dep];
|
|
67
|
+
let color = null;
|
|
68
|
+
if (options.color === "white") color = "#FFFFFF";
|
|
69
|
+
else if (options.color === "black") color = "#000000";
|
|
70
|
+
else if (options.color && options.color.startsWith("#")) color = options.color;
|
|
71
|
+
else {
|
|
72
|
+
const depSlug = dep.toLowerCase();
|
|
73
|
+
const nameSlug = tech.name.toLowerCase();
|
|
74
|
+
const nameSlugNoSpaces = tech.name.toLowerCase().replace(/\s+/g, "");
|
|
75
|
+
const hex = simple_icons_hex_default[depSlug] || simple_icons_hex_default[nameSlug] || simple_icons_hex_default[nameSlugNoSpaces];
|
|
76
|
+
if (hex) color = `#${hex}`;
|
|
77
|
+
}
|
|
78
|
+
detectedTechs.push({
|
|
79
|
+
name: tech.name,
|
|
80
|
+
slug: dep,
|
|
81
|
+
logo: tech.logo,
|
|
82
|
+
type: tech.type,
|
|
83
|
+
color
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const uniqueSlugs = Array.from(new Set(detectedTechs.map((t) => t.slug)));
|
|
88
|
+
let uniqueTechs = uniqueSlugs.map((slug) => detectedTechs.find((t) => t.slug === slug));
|
|
89
|
+
const seenLogos = /* @__PURE__ */ new Set();
|
|
90
|
+
uniqueTechs = uniqueTechs.filter((t) => {
|
|
91
|
+
if (seenLogos.has(t.logo)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
seenLogos.add(t.logo);
|
|
95
|
+
return true;
|
|
96
|
+
});
|
|
97
|
+
uniqueTechs.sort((a, b) => {
|
|
98
|
+
const pA = getCategoryPriority(a.type);
|
|
99
|
+
const pB = getCategoryPriority(b.type);
|
|
100
|
+
if (pA !== pB) return pA - pB;
|
|
101
|
+
return a.name.localeCompare(b.name);
|
|
102
|
+
});
|
|
103
|
+
const assetsDir = path.join(process.cwd(), "public", "assets", "logos");
|
|
104
|
+
if (options.copyAssets !== false) {
|
|
105
|
+
await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
|
|
106
|
+
}
|
|
107
|
+
return uniqueTechs.map((t) => ({
|
|
108
|
+
...t,
|
|
109
|
+
logo: `https://raw.githubusercontent.com/benjamindotdev/stackscan/main/public/assets/logos/${t.logo}`,
|
|
110
|
+
relativePath: `./public/assets/logos/${t.logo}`
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
async function scan(targetPath, optionsOrUndefined) {
|
|
114
|
+
let options = {};
|
|
115
|
+
let pathArg = void 0;
|
|
116
|
+
if (typeof targetPath === "string") {
|
|
117
|
+
pathArg = targetPath;
|
|
118
|
+
options = optionsOrUndefined || {};
|
|
119
|
+
} else if (typeof targetPath === "object") {
|
|
120
|
+
options = targetPath;
|
|
121
|
+
}
|
|
122
|
+
if (options.readme === void 0) options.readme = true;
|
|
123
|
+
console.log("\u{1F680} Starting Scan...");
|
|
124
|
+
if (options.color) {
|
|
125
|
+
console.log(`\u{1F3A8} Color mode: ${options.color}`);
|
|
126
|
+
}
|
|
127
|
+
if (pathArg) {
|
|
128
|
+
const absPath = path.resolve(pathArg);
|
|
129
|
+
console.log(`Scanning single project at: ${absPath}`);
|
|
130
|
+
try {
|
|
131
|
+
const techsWithUrls = await analyzeProject(absPath, options);
|
|
132
|
+
if (options.out) {
|
|
133
|
+
const outPath = path.resolve(options.out);
|
|
134
|
+
fs.writeFileSync(outPath, JSON.stringify(techsWithUrls, null, 2));
|
|
135
|
+
console.log(`\u2705 Generated stack output to: ${options.out}`);
|
|
136
|
+
} else {
|
|
137
|
+
const outPath = path.join(absPath, "stack.json");
|
|
138
|
+
fs.writeFileSync(outPath, JSON.stringify(techsWithUrls, null, 2));
|
|
139
|
+
console.log(`\u2705 Generated stack.json at: ${outPath}`);
|
|
140
|
+
}
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.error(`\u274C Error scanning project:`, err.message);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (!fs.existsSync(BASE_DIR)) {
|
|
148
|
+
console.log(`Creating stackscan directory at: ${BASE_DIR}`);
|
|
149
|
+
fs.mkdirSync(BASE_DIR, { recursive: true });
|
|
150
|
+
console.log('Please place your project folders inside "public/stackscan/" and run this command again.');
|
|
151
|
+
process.exit(0);
|
|
152
|
+
}
|
|
153
|
+
const entries = fs.readdirSync(BASE_DIR, { withFileTypes: true });
|
|
154
|
+
const projectDirs = entries.filter((dirent) => dirent.isDirectory() && dirent.name !== "input" && dirent.name !== "output");
|
|
155
|
+
if (projectDirs.length === 0) {
|
|
156
|
+
console.log('\u26A0\uFE0F No project directories found in "public/stackscan/".');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
console.log(`Found ${projectDirs.length} projects to process.
|
|
160
|
+
`);
|
|
161
|
+
const allProjects = [];
|
|
162
|
+
for (const dir of projectDirs) {
|
|
163
|
+
const projectPath = path.join(BASE_DIR, dir.name);
|
|
164
|
+
if (fs.existsSync(path.join(projectPath, "package.json"))) {
|
|
165
|
+
try {
|
|
166
|
+
const techsWithUrls = await analyzeProject(projectPath, options);
|
|
167
|
+
fs.writeFileSync(
|
|
168
|
+
path.join(projectPath, "stack.json"),
|
|
169
|
+
JSON.stringify(techsWithUrls, null, 2)
|
|
170
|
+
);
|
|
171
|
+
const mdContent = generateMarkdown(techsWithUrls);
|
|
172
|
+
fs.writeFileSync(path.join(projectPath, "stack.md"), mdContent);
|
|
173
|
+
allProjects.push({
|
|
174
|
+
name: dir.name,
|
|
175
|
+
techs: techsWithUrls
|
|
176
|
+
});
|
|
177
|
+
console.log(`\u2705 ${dir.name.padEnd(20)} -> stack.json (${techsWithUrls.length} techs)`);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
console.error(`\u274C Error processing ${dir.name}:`, err.message);
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
console.warn(`\u26A0\uFE0F Skipping "${dir.name}": No package.json found.`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (options.readme && allProjects.length > 0) {
|
|
186
|
+
updateRootReadme(allProjects);
|
|
187
|
+
} else if (!options.readme) {
|
|
188
|
+
console.log("Skipping README update (--no-readme passed).");
|
|
189
|
+
}
|
|
190
|
+
console.log("\n\u2728 Sync complete.");
|
|
191
|
+
}
|
|
192
|
+
function updateRootReadme(projects) {
|
|
193
|
+
const readmePath = path.join(process.cwd(), "README.md");
|
|
194
|
+
if (!fs.existsSync(readmePath)) {
|
|
195
|
+
console.log("\u26A0\uFE0F No root README.md found to update.");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
let readmeContent = fs.readFileSync(readmePath, "utf-8");
|
|
199
|
+
const startMarker = "<!-- STACKSYNC_START -->";
|
|
200
|
+
const endMarker = "<!-- STACKSYNC_END -->";
|
|
201
|
+
let newSection = `${startMarker}
|
|
202
|
+
## My Projects
|
|
203
|
+
|
|
204
|
+
`;
|
|
205
|
+
for (const p of projects) {
|
|
206
|
+
newSection += `### ${p.name}
|
|
207
|
+
`;
|
|
208
|
+
newSection += `<p>
|
|
209
|
+
`;
|
|
210
|
+
for (const t of p.techs) {
|
|
211
|
+
const src = t.relativePath || t.logo;
|
|
212
|
+
newSection += ` <img src="${src}" alt="${t.name}" height="25" style="margin-right: 10px;" />
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
newSection += `</p>
|
|
216
|
+
|
|
217
|
+
`;
|
|
218
|
+
}
|
|
219
|
+
newSection += `${endMarker}`;
|
|
220
|
+
if (readmeContent.includes(startMarker) && readmeContent.includes(endMarker)) {
|
|
221
|
+
const regex = new RegExp(`${startMarker}[\\s\\S]*?${endMarker}`);
|
|
222
|
+
readmeContent = readmeContent.replace(regex, newSection);
|
|
223
|
+
console.log(`\u{1F4DD} Updated root README.md with ${projects.length} projects.`);
|
|
224
|
+
} else {
|
|
225
|
+
readmeContent += `
|
|
226
|
+
|
|
227
|
+
${newSection}`;
|
|
228
|
+
console.log(`\u{1F4DD} Appended projects to root README.md.`);
|
|
229
|
+
}
|
|
230
|
+
fs.writeFileSync(readmePath, readmeContent);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export {
|
|
234
|
+
scan
|
|
235
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -5610,12 +5610,99 @@ var getCategoryPriority = (cat) => {
|
|
|
5610
5610
|
const idx = CATEGORY_PRIORITY.indexOf(cat ? cat.toLowerCase() : "");
|
|
5611
5611
|
return idx === -1 ? 999 : idx;
|
|
5612
5612
|
};
|
|
5613
|
-
async function
|
|
5613
|
+
async function analyzeProject(projectPath, options) {
|
|
5614
|
+
const packageJsonPath = import_path2.default.join(projectPath, "package.json");
|
|
5615
|
+
if (!import_fs.default.existsSync(packageJsonPath)) {
|
|
5616
|
+
throw new Error(`No package.json found at ${packageJsonPath}`);
|
|
5617
|
+
}
|
|
5618
|
+
const content = import_fs.default.readFileSync(packageJsonPath, "utf-8");
|
|
5619
|
+
const pkg = JSON.parse(content);
|
|
5620
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5621
|
+
const detectedTechs = [];
|
|
5622
|
+
Object.keys(allDeps).forEach((dep) => {
|
|
5623
|
+
if (SKIPPED_TECHS.includes(dep)) return;
|
|
5624
|
+
if (techMap[dep]) {
|
|
5625
|
+
const tech = techMap[dep];
|
|
5626
|
+
let color = null;
|
|
5627
|
+
if (options.color === "white") color = "#FFFFFF";
|
|
5628
|
+
else if (options.color === "black") color = "#000000";
|
|
5629
|
+
else if (options.color && options.color.startsWith("#")) color = options.color;
|
|
5630
|
+
else {
|
|
5631
|
+
const depSlug = dep.toLowerCase();
|
|
5632
|
+
const nameSlug = tech.name.toLowerCase();
|
|
5633
|
+
const nameSlugNoSpaces = tech.name.toLowerCase().replace(/\s+/g, "");
|
|
5634
|
+
const hex = simple_icons_hex_default[depSlug] || simple_icons_hex_default[nameSlug] || simple_icons_hex_default[nameSlugNoSpaces];
|
|
5635
|
+
if (hex) color = `#${hex}`;
|
|
5636
|
+
}
|
|
5637
|
+
detectedTechs.push({
|
|
5638
|
+
name: tech.name,
|
|
5639
|
+
slug: dep,
|
|
5640
|
+
logo: tech.logo,
|
|
5641
|
+
type: tech.type,
|
|
5642
|
+
color
|
|
5643
|
+
});
|
|
5644
|
+
}
|
|
5645
|
+
});
|
|
5646
|
+
const uniqueSlugs = Array.from(new Set(detectedTechs.map((t) => t.slug)));
|
|
5647
|
+
let uniqueTechs = uniqueSlugs.map((slug) => detectedTechs.find((t) => t.slug === slug));
|
|
5648
|
+
const seenLogos = /* @__PURE__ */ new Set();
|
|
5649
|
+
uniqueTechs = uniqueTechs.filter((t) => {
|
|
5650
|
+
if (seenLogos.has(t.logo)) {
|
|
5651
|
+
return false;
|
|
5652
|
+
}
|
|
5653
|
+
seenLogos.add(t.logo);
|
|
5654
|
+
return true;
|
|
5655
|
+
});
|
|
5656
|
+
uniqueTechs.sort((a, b) => {
|
|
5657
|
+
const pA = getCategoryPriority(a.type);
|
|
5658
|
+
const pB = getCategoryPriority(b.type);
|
|
5659
|
+
if (pA !== pB) return pA - pB;
|
|
5660
|
+
return a.name.localeCompare(b.name);
|
|
5661
|
+
});
|
|
5662
|
+
const assetsDir = import_path2.default.join(process.cwd(), "public", "assets", "logos");
|
|
5663
|
+
if (options.copyAssets !== false) {
|
|
5664
|
+
await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
|
|
5665
|
+
}
|
|
5666
|
+
return uniqueTechs.map((t) => ({
|
|
5667
|
+
...t,
|
|
5668
|
+
logo: `https://raw.githubusercontent.com/benjamindotdev/stackscan/main/public/assets/logos/${t.logo}`,
|
|
5669
|
+
relativePath: `./public/assets/logos/${t.logo}`
|
|
5670
|
+
}));
|
|
5671
|
+
}
|
|
5672
|
+
async function scan(targetPath, optionsOrUndefined) {
|
|
5673
|
+
let options = {};
|
|
5674
|
+
let pathArg = void 0;
|
|
5675
|
+
if (typeof targetPath === "string") {
|
|
5676
|
+
pathArg = targetPath;
|
|
5677
|
+
options = optionsOrUndefined || {};
|
|
5678
|
+
} else if (typeof targetPath === "object") {
|
|
5679
|
+
options = targetPath;
|
|
5680
|
+
}
|
|
5614
5681
|
if (options.readme === void 0) options.readme = true;
|
|
5615
5682
|
console.log("\u{1F680} Starting Scan...");
|
|
5616
5683
|
if (options.color) {
|
|
5617
5684
|
console.log(`\u{1F3A8} Color mode: ${options.color}`);
|
|
5618
5685
|
}
|
|
5686
|
+
if (pathArg) {
|
|
5687
|
+
const absPath = import_path2.default.resolve(pathArg);
|
|
5688
|
+
console.log(`Scanning single project at: ${absPath}`);
|
|
5689
|
+
try {
|
|
5690
|
+
const techsWithUrls = await analyzeProject(absPath, options);
|
|
5691
|
+
if (options.out) {
|
|
5692
|
+
const outPath = import_path2.default.resolve(options.out);
|
|
5693
|
+
import_fs.default.writeFileSync(outPath, JSON.stringify(techsWithUrls, null, 2));
|
|
5694
|
+
console.log(`\u2705 Generated stack output to: ${options.out}`);
|
|
5695
|
+
} else {
|
|
5696
|
+
const outPath = import_path2.default.join(absPath, "stack.json");
|
|
5697
|
+
import_fs.default.writeFileSync(outPath, JSON.stringify(techsWithUrls, null, 2));
|
|
5698
|
+
console.log(`\u2705 Generated stack.json at: ${outPath}`);
|
|
5699
|
+
}
|
|
5700
|
+
} catch (err) {
|
|
5701
|
+
console.error(`\u274C Error scanning project:`, err.message);
|
|
5702
|
+
process.exit(1);
|
|
5703
|
+
}
|
|
5704
|
+
return;
|
|
5705
|
+
}
|
|
5619
5706
|
if (!import_fs.default.existsSync(BASE_DIR)) {
|
|
5620
5707
|
console.log(`Creating stackscan directory at: ${BASE_DIR}`);
|
|
5621
5708
|
import_fs.default.mkdirSync(BASE_DIR, { recursive: true });
|
|
@@ -5631,66 +5718,11 @@ async function scan(options = {}) {
|
|
|
5631
5718
|
console.log(`Found ${projectDirs.length} projects to process.
|
|
5632
5719
|
`);
|
|
5633
5720
|
const allProjects = [];
|
|
5634
|
-
const allTechs = [];
|
|
5635
5721
|
for (const dir of projectDirs) {
|
|
5636
5722
|
const projectPath = import_path2.default.join(BASE_DIR, dir.name);
|
|
5637
|
-
|
|
5638
|
-
if (import_fs.default.existsSync(packageJsonPath)) {
|
|
5723
|
+
if (import_fs.default.existsSync(import_path2.default.join(projectPath, "package.json"))) {
|
|
5639
5724
|
try {
|
|
5640
|
-
const
|
|
5641
|
-
const pkg = JSON.parse(content);
|
|
5642
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5643
|
-
const detectedTechs = [];
|
|
5644
|
-
Object.keys(allDeps).forEach((dep) => {
|
|
5645
|
-
if (SKIPPED_TECHS.includes(dep)) return;
|
|
5646
|
-
if (techMap[dep]) {
|
|
5647
|
-
const tech = techMap[dep];
|
|
5648
|
-
let color = null;
|
|
5649
|
-
if (options.color === "white") color = "#FFFFFF";
|
|
5650
|
-
else if (options.color === "black") color = "#000000";
|
|
5651
|
-
else if (options.color && options.color.startsWith("#")) color = options.color;
|
|
5652
|
-
else {
|
|
5653
|
-
const depSlug = dep.toLowerCase();
|
|
5654
|
-
const nameSlug = tech.name.toLowerCase();
|
|
5655
|
-
const nameSlugNoSpaces = tech.name.toLowerCase().replace(/\s+/g, "");
|
|
5656
|
-
const hex = simple_icons_hex_default[depSlug] || simple_icons_hex_default[nameSlug] || simple_icons_hex_default[nameSlugNoSpaces];
|
|
5657
|
-
if (hex) color = `#${hex}`;
|
|
5658
|
-
}
|
|
5659
|
-
detectedTechs.push({
|
|
5660
|
-
name: tech.name,
|
|
5661
|
-
slug: dep,
|
|
5662
|
-
logo: tech.logo,
|
|
5663
|
-
// Raw path for resolution
|
|
5664
|
-
type: tech.type,
|
|
5665
|
-
color
|
|
5666
|
-
});
|
|
5667
|
-
}
|
|
5668
|
-
});
|
|
5669
|
-
let uniqueTechs = Array.from(new Set(detectedTechs.map((t) => t.slug))).map((slug) => detectedTechs.find((t) => t.slug === slug));
|
|
5670
|
-
const seenLogos = /* @__PURE__ */ new Set();
|
|
5671
|
-
uniqueTechs = uniqueTechs.filter((t) => {
|
|
5672
|
-
if (seenLogos.has(t.logo)) {
|
|
5673
|
-
return false;
|
|
5674
|
-
}
|
|
5675
|
-
seenLogos.add(t.logo);
|
|
5676
|
-
return true;
|
|
5677
|
-
});
|
|
5678
|
-
uniqueTechs.sort((a, b) => {
|
|
5679
|
-
const pA = getCategoryPriority(a.type);
|
|
5680
|
-
const pB = getCategoryPriority(b.type);
|
|
5681
|
-
if (pA !== pB) return pA - pB;
|
|
5682
|
-
return a.name.localeCompare(b.name);
|
|
5683
|
-
});
|
|
5684
|
-
const assetsDir = import_path2.default.join(process.cwd(), "public", "assets", "logos");
|
|
5685
|
-
if (options.copyAssets !== false) {
|
|
5686
|
-
await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
|
|
5687
|
-
}
|
|
5688
|
-
const techsWithUrls = uniqueTechs.map((t) => ({
|
|
5689
|
-
...t,
|
|
5690
|
-
logo: `https://raw.githubusercontent.com/benjamindotdev/stackscan/main/public/assets/logos/${t.logo}`,
|
|
5691
|
-
relativePath: `./public/assets/logos/${t.logo}`
|
|
5692
|
-
}));
|
|
5693
|
-
allTechs.push(...techsWithUrls);
|
|
5725
|
+
const techsWithUrls = await analyzeProject(projectPath, options);
|
|
5694
5726
|
import_fs.default.writeFileSync(
|
|
5695
5727
|
import_path2.default.join(projectPath, "stack.json"),
|
|
5696
5728
|
JSON.stringify(techsWithUrls, null, 2)
|
|
@@ -5701,7 +5733,7 @@ async function scan(options = {}) {
|
|
|
5701
5733
|
name: dir.name,
|
|
5702
5734
|
techs: techsWithUrls
|
|
5703
5735
|
});
|
|
5704
|
-
console.log(`\u2705 ${dir.name.padEnd(20)} -> stack.json (${
|
|
5736
|
+
console.log(`\u2705 ${dir.name.padEnd(20)} -> stack.json (${techsWithUrls.length} techs)`);
|
|
5705
5737
|
} catch (err) {
|
|
5706
5738
|
console.error(`\u274C Error processing ${dir.name}:`, err.message);
|
|
5707
5739
|
}
|
|
@@ -5806,8 +5838,10 @@ async function add(sourcePath) {
|
|
|
5806
5838
|
// src/cli.ts
|
|
5807
5839
|
var program = new import_commander.Command();
|
|
5808
5840
|
program.name("stackscan").description("Auto-detect tech stacks and generate tech.json or markdown.").version("0.1.0");
|
|
5809
|
-
program.command("scan", { isDefault: true }).description("Scan stacks from multiple projects in public/stackscan/").option("--color <mode>", "Color mode (brand, white, black, or hex)", "brand").option("--no-readme", "Do not update the root README.md").action(async (options) => {
|
|
5810
|
-
|
|
5841
|
+
program.command("scan [path]", { isDefault: true }).description("Scan stacks from multiple projects in public/stackscan/ or a specific project path").option("--color <mode>", "Color mode (brand, white, black, or hex)", "brand").option("--no-readme", "Do not update the root README.md").option("--out <file>", "Output JSON file (only for single project scan)").action(async (path4, options) => {
|
|
5842
|
+
let targetPath = typeof path4 === "string" ? path4 : void 0;
|
|
5843
|
+
let opts = typeof path4 === "object" ? path4 : options;
|
|
5844
|
+
await scan(targetPath, opts);
|
|
5811
5845
|
});
|
|
5812
5846
|
program.command("add <path>").description("Add a project (folder or package.json) to the public/stackscan workspace").action(async (path4) => {
|
|
5813
5847
|
await add(path4);
|
package/dist/cli.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-ACCTMJVS.mjs";
|
|
5
5
|
import {
|
|
6
6
|
scan
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-OX7SYHLD.mjs";
|
|
8
8
|
import "./chunk-LSUI3VI4.mjs";
|
|
9
9
|
import "./chunk-LB3L25FS.mjs";
|
|
10
10
|
import "./chunk-UJM3S22V.mjs";
|
|
@@ -16,8 +16,10 @@ import "./chunk-EH2SEQZP.mjs";
|
|
|
16
16
|
import { Command } from "commander";
|
|
17
17
|
var program = new Command();
|
|
18
18
|
program.name("stackscan").description("Auto-detect tech stacks and generate tech.json or markdown.").version("0.1.0");
|
|
19
|
-
program.command("scan", { isDefault: true }).description("Scan stacks from multiple projects in public/stackscan/").option("--color <mode>", "Color mode (brand, white, black, or hex)", "brand").option("--no-readme", "Do not update the root README.md").action(async (options) => {
|
|
20
|
-
|
|
19
|
+
program.command("scan [path]", { isDefault: true }).description("Scan stacks from multiple projects in public/stackscan/ or a specific project path").option("--color <mode>", "Color mode (brand, white, black, or hex)", "brand").option("--no-readme", "Do not update the root README.md").option("--out <file>", "Output JSON file (only for single project scan)").action(async (path, options) => {
|
|
20
|
+
let targetPath = typeof path === "string" ? path : void 0;
|
|
21
|
+
let opts = typeof path === "object" ? path : options;
|
|
22
|
+
await scan(targetPath, opts);
|
|
21
23
|
});
|
|
22
24
|
program.command("add <path>").description("Add a project (folder or package.json) to the public/stackscan workspace").action(async (path) => {
|
|
23
25
|
await add(path);
|
package/dist/index.js
CHANGED
|
@@ -5697,12 +5697,99 @@ var getCategoryPriority = (cat) => {
|
|
|
5697
5697
|
const idx = CATEGORY_PRIORITY.indexOf(cat ? cat.toLowerCase() : "");
|
|
5698
5698
|
return idx === -1 ? 999 : idx;
|
|
5699
5699
|
};
|
|
5700
|
-
async function
|
|
5700
|
+
async function analyzeProject(projectPath, options) {
|
|
5701
|
+
const packageJsonPath = import_path3.default.join(projectPath, "package.json");
|
|
5702
|
+
if (!import_fs.default.existsSync(packageJsonPath)) {
|
|
5703
|
+
throw new Error(`No package.json found at ${packageJsonPath}`);
|
|
5704
|
+
}
|
|
5705
|
+
const content = import_fs.default.readFileSync(packageJsonPath, "utf-8");
|
|
5706
|
+
const pkg = JSON.parse(content);
|
|
5707
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5708
|
+
const detectedTechs = [];
|
|
5709
|
+
Object.keys(allDeps).forEach((dep) => {
|
|
5710
|
+
if (SKIPPED_TECHS.includes(dep)) return;
|
|
5711
|
+
if (techMap[dep]) {
|
|
5712
|
+
const tech = techMap[dep];
|
|
5713
|
+
let color = null;
|
|
5714
|
+
if (options.color === "white") color = "#FFFFFF";
|
|
5715
|
+
else if (options.color === "black") color = "#000000";
|
|
5716
|
+
else if (options.color && options.color.startsWith("#")) color = options.color;
|
|
5717
|
+
else {
|
|
5718
|
+
const depSlug = dep.toLowerCase();
|
|
5719
|
+
const nameSlug = tech.name.toLowerCase();
|
|
5720
|
+
const nameSlugNoSpaces = tech.name.toLowerCase().replace(/\s+/g, "");
|
|
5721
|
+
const hex = simple_icons_hex_default[depSlug] || simple_icons_hex_default[nameSlug] || simple_icons_hex_default[nameSlugNoSpaces];
|
|
5722
|
+
if (hex) color = `#${hex}`;
|
|
5723
|
+
}
|
|
5724
|
+
detectedTechs.push({
|
|
5725
|
+
name: tech.name,
|
|
5726
|
+
slug: dep,
|
|
5727
|
+
logo: tech.logo,
|
|
5728
|
+
type: tech.type,
|
|
5729
|
+
color
|
|
5730
|
+
});
|
|
5731
|
+
}
|
|
5732
|
+
});
|
|
5733
|
+
const uniqueSlugs = Array.from(new Set(detectedTechs.map((t) => t.slug)));
|
|
5734
|
+
let uniqueTechs = uniqueSlugs.map((slug) => detectedTechs.find((t) => t.slug === slug));
|
|
5735
|
+
const seenLogos = /* @__PURE__ */ new Set();
|
|
5736
|
+
uniqueTechs = uniqueTechs.filter((t) => {
|
|
5737
|
+
if (seenLogos.has(t.logo)) {
|
|
5738
|
+
return false;
|
|
5739
|
+
}
|
|
5740
|
+
seenLogos.add(t.logo);
|
|
5741
|
+
return true;
|
|
5742
|
+
});
|
|
5743
|
+
uniqueTechs.sort((a, b) => {
|
|
5744
|
+
const pA = getCategoryPriority(a.type);
|
|
5745
|
+
const pB = getCategoryPriority(b.type);
|
|
5746
|
+
if (pA !== pB) return pA - pB;
|
|
5747
|
+
return a.name.localeCompare(b.name);
|
|
5748
|
+
});
|
|
5749
|
+
const assetsDir = import_path3.default.join(process.cwd(), "public", "assets", "logos");
|
|
5750
|
+
if (options.copyAssets !== false) {
|
|
5751
|
+
await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
|
|
5752
|
+
}
|
|
5753
|
+
return uniqueTechs.map((t) => ({
|
|
5754
|
+
...t,
|
|
5755
|
+
logo: `https://raw.githubusercontent.com/benjamindotdev/stackscan/main/public/assets/logos/${t.logo}`,
|
|
5756
|
+
relativePath: `./public/assets/logos/${t.logo}`
|
|
5757
|
+
}));
|
|
5758
|
+
}
|
|
5759
|
+
async function scan(targetPath, optionsOrUndefined) {
|
|
5760
|
+
let options = {};
|
|
5761
|
+
let pathArg = void 0;
|
|
5762
|
+
if (typeof targetPath === "string") {
|
|
5763
|
+
pathArg = targetPath;
|
|
5764
|
+
options = optionsOrUndefined || {};
|
|
5765
|
+
} else if (typeof targetPath === "object") {
|
|
5766
|
+
options = targetPath;
|
|
5767
|
+
}
|
|
5701
5768
|
if (options.readme === void 0) options.readme = true;
|
|
5702
5769
|
console.log("\u{1F680} Starting Scan...");
|
|
5703
5770
|
if (options.color) {
|
|
5704
5771
|
console.log(`\u{1F3A8} Color mode: ${options.color}`);
|
|
5705
5772
|
}
|
|
5773
|
+
if (pathArg) {
|
|
5774
|
+
const absPath = import_path3.default.resolve(pathArg);
|
|
5775
|
+
console.log(`Scanning single project at: ${absPath}`);
|
|
5776
|
+
try {
|
|
5777
|
+
const techsWithUrls = await analyzeProject(absPath, options);
|
|
5778
|
+
if (options.out) {
|
|
5779
|
+
const outPath = import_path3.default.resolve(options.out);
|
|
5780
|
+
import_fs.default.writeFileSync(outPath, JSON.stringify(techsWithUrls, null, 2));
|
|
5781
|
+
console.log(`\u2705 Generated stack output to: ${options.out}`);
|
|
5782
|
+
} else {
|
|
5783
|
+
const outPath = import_path3.default.join(absPath, "stack.json");
|
|
5784
|
+
import_fs.default.writeFileSync(outPath, JSON.stringify(techsWithUrls, null, 2));
|
|
5785
|
+
console.log(`\u2705 Generated stack.json at: ${outPath}`);
|
|
5786
|
+
}
|
|
5787
|
+
} catch (err) {
|
|
5788
|
+
console.error(`\u274C Error scanning project:`, err.message);
|
|
5789
|
+
process.exit(1);
|
|
5790
|
+
}
|
|
5791
|
+
return;
|
|
5792
|
+
}
|
|
5706
5793
|
if (!import_fs.default.existsSync(BASE_DIR2)) {
|
|
5707
5794
|
console.log(`Creating stackscan directory at: ${BASE_DIR2}`);
|
|
5708
5795
|
import_fs.default.mkdirSync(BASE_DIR2, { recursive: true });
|
|
@@ -5718,66 +5805,11 @@ async function scan(options = {}) {
|
|
|
5718
5805
|
console.log(`Found ${projectDirs.length} projects to process.
|
|
5719
5806
|
`);
|
|
5720
5807
|
const allProjects = [];
|
|
5721
|
-
const allTechs = [];
|
|
5722
5808
|
for (const dir of projectDirs) {
|
|
5723
5809
|
const projectPath = import_path3.default.join(BASE_DIR2, dir.name);
|
|
5724
|
-
|
|
5725
|
-
if (import_fs.default.existsSync(packageJsonPath)) {
|
|
5810
|
+
if (import_fs.default.existsSync(import_path3.default.join(projectPath, "package.json"))) {
|
|
5726
5811
|
try {
|
|
5727
|
-
const
|
|
5728
|
-
const pkg = JSON.parse(content);
|
|
5729
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5730
|
-
const detectedTechs = [];
|
|
5731
|
-
Object.keys(allDeps).forEach((dep) => {
|
|
5732
|
-
if (SKIPPED_TECHS.includes(dep)) return;
|
|
5733
|
-
if (techMap[dep]) {
|
|
5734
|
-
const tech = techMap[dep];
|
|
5735
|
-
let color = null;
|
|
5736
|
-
if (options.color === "white") color = "#FFFFFF";
|
|
5737
|
-
else if (options.color === "black") color = "#000000";
|
|
5738
|
-
else if (options.color && options.color.startsWith("#")) color = options.color;
|
|
5739
|
-
else {
|
|
5740
|
-
const depSlug = dep.toLowerCase();
|
|
5741
|
-
const nameSlug = tech.name.toLowerCase();
|
|
5742
|
-
const nameSlugNoSpaces = tech.name.toLowerCase().replace(/\s+/g, "");
|
|
5743
|
-
const hex = simple_icons_hex_default[depSlug] || simple_icons_hex_default[nameSlug] || simple_icons_hex_default[nameSlugNoSpaces];
|
|
5744
|
-
if (hex) color = `#${hex}`;
|
|
5745
|
-
}
|
|
5746
|
-
detectedTechs.push({
|
|
5747
|
-
name: tech.name,
|
|
5748
|
-
slug: dep,
|
|
5749
|
-
logo: tech.logo,
|
|
5750
|
-
// Raw path for resolution
|
|
5751
|
-
type: tech.type,
|
|
5752
|
-
color
|
|
5753
|
-
});
|
|
5754
|
-
}
|
|
5755
|
-
});
|
|
5756
|
-
let uniqueTechs = Array.from(new Set(detectedTechs.map((t) => t.slug))).map((slug) => detectedTechs.find((t) => t.slug === slug));
|
|
5757
|
-
const seenLogos = /* @__PURE__ */ new Set();
|
|
5758
|
-
uniqueTechs = uniqueTechs.filter((t) => {
|
|
5759
|
-
if (seenLogos.has(t.logo)) {
|
|
5760
|
-
return false;
|
|
5761
|
-
}
|
|
5762
|
-
seenLogos.add(t.logo);
|
|
5763
|
-
return true;
|
|
5764
|
-
});
|
|
5765
|
-
uniqueTechs.sort((a, b) => {
|
|
5766
|
-
const pA = getCategoryPriority(a.type);
|
|
5767
|
-
const pB = getCategoryPriority(b.type);
|
|
5768
|
-
if (pA !== pB) return pA - pB;
|
|
5769
|
-
return a.name.localeCompare(b.name);
|
|
5770
|
-
});
|
|
5771
|
-
const assetsDir = import_path3.default.join(process.cwd(), "public", "assets", "logos");
|
|
5772
|
-
if (options.copyAssets !== false) {
|
|
5773
|
-
await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
|
|
5774
|
-
}
|
|
5775
|
-
const techsWithUrls = uniqueTechs.map((t) => ({
|
|
5776
|
-
...t,
|
|
5777
|
-
logo: `https://raw.githubusercontent.com/benjamindotdev/stackscan/main/public/assets/logos/${t.logo}`,
|
|
5778
|
-
relativePath: `./public/assets/logos/${t.logo}`
|
|
5779
|
-
}));
|
|
5780
|
-
allTechs.push(...techsWithUrls);
|
|
5812
|
+
const techsWithUrls = await analyzeProject(projectPath, options);
|
|
5781
5813
|
import_fs.default.writeFileSync(
|
|
5782
5814
|
import_path3.default.join(projectPath, "stack.json"),
|
|
5783
5815
|
JSON.stringify(techsWithUrls, null, 2)
|
|
@@ -5788,7 +5820,7 @@ async function scan(options = {}) {
|
|
|
5788
5820
|
name: dir.name,
|
|
5789
5821
|
techs: techsWithUrls
|
|
5790
5822
|
});
|
|
5791
|
-
console.log(`\u2705 ${dir.name.padEnd(20)} -> stack.json (${
|
|
5823
|
+
console.log(`\u2705 ${dir.name.padEnd(20)} -> stack.json (${techsWithUrls.length} techs)`);
|
|
5792
5824
|
} catch (err) {
|
|
5793
5825
|
console.error(`\u274C Error processing ${dir.name}:`, err.message);
|
|
5794
5826
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sync
|
|
3
|
+
} from "./chunk-XS2LU2MN.mjs";
|
|
1
4
|
import "./chunk-WAXGOBY2.mjs";
|
|
2
5
|
import {
|
|
3
6
|
add
|
|
4
7
|
} from "./chunk-ACCTMJVS.mjs";
|
|
5
8
|
import {
|
|
6
9
|
scan
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import {
|
|
9
|
-
sync
|
|
10
|
-
} from "./chunk-XS2LU2MN.mjs";
|
|
10
|
+
} from "./chunk-OX7SYHLD.mjs";
|
|
11
11
|
import {
|
|
12
12
|
techMap
|
|
13
13
|
} from "./chunk-LSUI3VI4.mjs";
|
package/dist/scan.d.mts
CHANGED
|
@@ -2,7 +2,8 @@ interface SyncOptions {
|
|
|
2
2
|
color?: string;
|
|
3
3
|
copyAssets?: boolean;
|
|
4
4
|
readme?: boolean;
|
|
5
|
+
out?: string;
|
|
5
6
|
}
|
|
6
|
-
declare function scan(
|
|
7
|
+
declare function scan(targetPath?: string | object, optionsOrUndefined?: SyncOptions): Promise<void>;
|
|
7
8
|
|
|
8
9
|
export { scan };
|
package/dist/scan.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ interface SyncOptions {
|
|
|
2
2
|
color?: string;
|
|
3
3
|
copyAssets?: boolean;
|
|
4
4
|
readme?: boolean;
|
|
5
|
+
out?: string;
|
|
5
6
|
}
|
|
6
|
-
declare function scan(
|
|
7
|
+
declare function scan(targetPath?: string | object, optionsOrUndefined?: SyncOptions): Promise<void>;
|
|
7
8
|
|
|
8
9
|
export { scan };
|