stackscan 0.1.15 → 0.1.17
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-PL2BQ6GK.mjs +203 -0
- package/dist/cli.js +41 -0
- package/dist/cli.mjs +1 -1
- package/dist/index.js +41 -0
- package/dist/index.mjs +1 -1
- package/dist/scan.js +41 -0
- package/dist/scan.mjs +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,203 @@
|
|
|
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 scan(options = {}) {
|
|
55
|
+
if (options.readme === void 0) options.readme = true;
|
|
56
|
+
console.log("\u{1F680} Starting Scan...");
|
|
57
|
+
if (options.color) {
|
|
58
|
+
console.log(`\u{1F3A8} Color mode: ${options.color}`);
|
|
59
|
+
}
|
|
60
|
+
if (!fs.existsSync(BASE_DIR)) {
|
|
61
|
+
console.log(`Creating stackscan directory at: ${BASE_DIR}`);
|
|
62
|
+
fs.mkdirSync(BASE_DIR, { recursive: true });
|
|
63
|
+
console.log('Please place your project folders inside "public/stackscan/" and run this command again.');
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
const entries = fs.readdirSync(BASE_DIR, { withFileTypes: true });
|
|
67
|
+
const projectDirs = entries.filter((dirent) => dirent.isDirectory() && dirent.name !== "input" && dirent.name !== "output");
|
|
68
|
+
if (projectDirs.length === 0) {
|
|
69
|
+
console.log('\u26A0\uFE0F No project directories found in "public/stackscan/".');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.log(`Found ${projectDirs.length} projects to process.
|
|
73
|
+
`);
|
|
74
|
+
const allProjects = [];
|
|
75
|
+
const allTechs = [];
|
|
76
|
+
for (const dir of projectDirs) {
|
|
77
|
+
const projectPath = path.join(BASE_DIR, dir.name);
|
|
78
|
+
const packageJsonPath = path.join(projectPath, "package.json");
|
|
79
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
80
|
+
try {
|
|
81
|
+
const content = fs.readFileSync(packageJsonPath, "utf-8");
|
|
82
|
+
const pkg = JSON.parse(content);
|
|
83
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
84
|
+
const detectedTechs = [];
|
|
85
|
+
Object.keys(allDeps).forEach((dep) => {
|
|
86
|
+
if (SKIPPED_TECHS.includes(dep)) return;
|
|
87
|
+
if (techMap[dep]) {
|
|
88
|
+
const tech = techMap[dep];
|
|
89
|
+
let color = null;
|
|
90
|
+
if (options.color === "white") color = "#FFFFFF";
|
|
91
|
+
else if (options.color === "black") color = "#000000";
|
|
92
|
+
else if (options.color && options.color.startsWith("#")) color = options.color;
|
|
93
|
+
else {
|
|
94
|
+
const depSlug = dep.toLowerCase();
|
|
95
|
+
const nameSlug = tech.name.toLowerCase();
|
|
96
|
+
const nameSlugNoSpaces = tech.name.toLowerCase().replace(/\s+/g, "");
|
|
97
|
+
const hex = simple_icons_hex_default[depSlug] || simple_icons_hex_default[nameSlug] || simple_icons_hex_default[nameSlugNoSpaces];
|
|
98
|
+
if (hex) color = `#${hex}`;
|
|
99
|
+
}
|
|
100
|
+
detectedTechs.push({
|
|
101
|
+
name: tech.name,
|
|
102
|
+
slug: dep,
|
|
103
|
+
logo: tech.logo,
|
|
104
|
+
// Raw path for resolution
|
|
105
|
+
type: tech.type,
|
|
106
|
+
color
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
let uniqueTechs = Array.from(new Set(detectedTechs.map((t) => t.slug))).map((slug) => detectedTechs.find((t) => t.slug === slug));
|
|
111
|
+
const seenLogos = /* @__PURE__ */ new Set();
|
|
112
|
+
uniqueTechs = uniqueTechs.filter((t) => {
|
|
113
|
+
if (seenLogos.has(t.logo)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
seenLogos.add(t.logo);
|
|
117
|
+
return true;
|
|
118
|
+
});
|
|
119
|
+
uniqueTechs.sort((a, b) => {
|
|
120
|
+
const pA = getCategoryPriority(a.type);
|
|
121
|
+
const pB = getCategoryPriority(b.type);
|
|
122
|
+
if (pA !== pB) return pA - pB;
|
|
123
|
+
return a.name.localeCompare(b.name);
|
|
124
|
+
});
|
|
125
|
+
const assetsDir = path.join(process.cwd(), "public", "assets", "logos");
|
|
126
|
+
if (options.copyAssets !== false) {
|
|
127
|
+
await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
|
|
128
|
+
}
|
|
129
|
+
const techsWithUrls = uniqueTechs.map((t) => ({
|
|
130
|
+
...t,
|
|
131
|
+
logo: `https://raw.githubusercontent.com/benjamindotdev/stackscan/main/public/assets/logos/${t.logo}`,
|
|
132
|
+
relativePath: `./public/assets/logos/${t.logo}`
|
|
133
|
+
}));
|
|
134
|
+
allTechs.push(...techsWithUrls);
|
|
135
|
+
fs.writeFileSync(
|
|
136
|
+
path.join(projectPath, "stack.json"),
|
|
137
|
+
JSON.stringify(techsWithUrls, null, 2)
|
|
138
|
+
);
|
|
139
|
+
const mdContent = generateMarkdown(techsWithUrls);
|
|
140
|
+
fs.writeFileSync(path.join(projectPath, "stack.md"), mdContent);
|
|
141
|
+
allProjects.push({
|
|
142
|
+
name: dir.name,
|
|
143
|
+
techs: techsWithUrls
|
|
144
|
+
});
|
|
145
|
+
console.log(`\u2705 ${dir.name.padEnd(20)} -> stack.json (${uniqueTechs.length} techs)`);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error(`\u274C Error processing ${dir.name}:`, err.message);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
console.warn(`\u26A0\uFE0F Skipping "${dir.name}": No package.json found.`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (options.readme && allProjects.length > 0) {
|
|
154
|
+
updateRootReadme(allProjects);
|
|
155
|
+
} else if (!options.readme) {
|
|
156
|
+
console.log("Skipping README update (--no-readme passed).");
|
|
157
|
+
}
|
|
158
|
+
console.log("\n\u2728 Sync complete.");
|
|
159
|
+
}
|
|
160
|
+
function updateRootReadme(projects) {
|
|
161
|
+
const readmePath = path.join(process.cwd(), "README.md");
|
|
162
|
+
if (!fs.existsSync(readmePath)) {
|
|
163
|
+
console.log("\u26A0\uFE0F No root README.md found to update.");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
let readmeContent = fs.readFileSync(readmePath, "utf-8");
|
|
167
|
+
const startMarker = "<!-- STACKSYNC_START -->";
|
|
168
|
+
const endMarker = "<!-- STACKSYNC_END -->";
|
|
169
|
+
let newSection = `${startMarker}
|
|
170
|
+
## My Projects
|
|
171
|
+
|
|
172
|
+
`;
|
|
173
|
+
for (const p of projects) {
|
|
174
|
+
newSection += `### ${p.name}
|
|
175
|
+
`;
|
|
176
|
+
newSection += `<p>
|
|
177
|
+
`;
|
|
178
|
+
for (const t of p.techs) {
|
|
179
|
+
const src = t.relativePath || t.logo;
|
|
180
|
+
newSection += ` <img src="${src}" alt="${t.name}" height="25" style="margin-right: 10px;" />
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
183
|
+
newSection += `</p>
|
|
184
|
+
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
newSection += `${endMarker}`;
|
|
188
|
+
if (readmeContent.includes(startMarker) && readmeContent.includes(endMarker)) {
|
|
189
|
+
const regex = new RegExp(`${startMarker}[\\s\\S]*?${endMarker}`);
|
|
190
|
+
readmeContent = readmeContent.replace(regex, newSection);
|
|
191
|
+
console.log(`\u{1F4DD} Updated root README.md with ${projects.length} projects.`);
|
|
192
|
+
} else {
|
|
193
|
+
readmeContent += `
|
|
194
|
+
|
|
195
|
+
${newSection}`;
|
|
196
|
+
console.log(`\u{1F4DD} Appended projects to root README.md.`);
|
|
197
|
+
}
|
|
198
|
+
fs.writeFileSync(readmePath, readmeContent);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export {
|
|
202
|
+
scan
|
|
203
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -5575,6 +5575,41 @@ var BASE_DIR = import_path2.default.join(process.cwd(), "public", "stackscan");
|
|
|
5575
5575
|
var SKIPPED_TECHS = [
|
|
5576
5576
|
"react-dom"
|
|
5577
5577
|
];
|
|
5578
|
+
var CATEGORY_PRIORITY = [
|
|
5579
|
+
"language",
|
|
5580
|
+
"framework",
|
|
5581
|
+
"mobile",
|
|
5582
|
+
"frontend",
|
|
5583
|
+
"backend",
|
|
5584
|
+
"runtime",
|
|
5585
|
+
"database",
|
|
5586
|
+
"orm",
|
|
5587
|
+
"auth",
|
|
5588
|
+
"api",
|
|
5589
|
+
"state",
|
|
5590
|
+
"css",
|
|
5591
|
+
"cloud",
|
|
5592
|
+
"hosting",
|
|
5593
|
+
"devops",
|
|
5594
|
+
"container",
|
|
5595
|
+
"ci",
|
|
5596
|
+
"testing",
|
|
5597
|
+
"build",
|
|
5598
|
+
"lint",
|
|
5599
|
+
"format",
|
|
5600
|
+
"automation",
|
|
5601
|
+
"package",
|
|
5602
|
+
"ai",
|
|
5603
|
+
"network",
|
|
5604
|
+
"utility",
|
|
5605
|
+
"cms",
|
|
5606
|
+
"ssg",
|
|
5607
|
+
"payment"
|
|
5608
|
+
];
|
|
5609
|
+
var getCategoryPriority = (cat) => {
|
|
5610
|
+
const idx = CATEGORY_PRIORITY.indexOf(cat ? cat.toLowerCase() : "");
|
|
5611
|
+
return idx === -1 ? 999 : idx;
|
|
5612
|
+
};
|
|
5578
5613
|
async function scan(options = {}) {
|
|
5579
5614
|
if (options.readme === void 0) options.readme = true;
|
|
5580
5615
|
console.log("\u{1F680} Starting Scan...");
|
|
@@ -5640,6 +5675,12 @@ async function scan(options = {}) {
|
|
|
5640
5675
|
seenLogos.add(t.logo);
|
|
5641
5676
|
return true;
|
|
5642
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
|
+
});
|
|
5643
5684
|
const assetsDir = import_path2.default.join(process.cwd(), "public", "assets", "logos");
|
|
5644
5685
|
if (options.copyAssets !== false) {
|
|
5645
5686
|
await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
|
package/dist/cli.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -5662,6 +5662,41 @@ var BASE_DIR2 = import_path3.default.join(process.cwd(), "public", "stackscan");
|
|
|
5662
5662
|
var SKIPPED_TECHS = [
|
|
5663
5663
|
"react-dom"
|
|
5664
5664
|
];
|
|
5665
|
+
var CATEGORY_PRIORITY = [
|
|
5666
|
+
"language",
|
|
5667
|
+
"framework",
|
|
5668
|
+
"mobile",
|
|
5669
|
+
"frontend",
|
|
5670
|
+
"backend",
|
|
5671
|
+
"runtime",
|
|
5672
|
+
"database",
|
|
5673
|
+
"orm",
|
|
5674
|
+
"auth",
|
|
5675
|
+
"api",
|
|
5676
|
+
"state",
|
|
5677
|
+
"css",
|
|
5678
|
+
"cloud",
|
|
5679
|
+
"hosting",
|
|
5680
|
+
"devops",
|
|
5681
|
+
"container",
|
|
5682
|
+
"ci",
|
|
5683
|
+
"testing",
|
|
5684
|
+
"build",
|
|
5685
|
+
"lint",
|
|
5686
|
+
"format",
|
|
5687
|
+
"automation",
|
|
5688
|
+
"package",
|
|
5689
|
+
"ai",
|
|
5690
|
+
"network",
|
|
5691
|
+
"utility",
|
|
5692
|
+
"cms",
|
|
5693
|
+
"ssg",
|
|
5694
|
+
"payment"
|
|
5695
|
+
];
|
|
5696
|
+
var getCategoryPriority = (cat) => {
|
|
5697
|
+
const idx = CATEGORY_PRIORITY.indexOf(cat ? cat.toLowerCase() : "");
|
|
5698
|
+
return idx === -1 ? 999 : idx;
|
|
5699
|
+
};
|
|
5665
5700
|
async function scan(options = {}) {
|
|
5666
5701
|
if (options.readme === void 0) options.readme = true;
|
|
5667
5702
|
console.log("\u{1F680} Starting Scan...");
|
|
@@ -5727,6 +5762,12 @@ async function scan(options = {}) {
|
|
|
5727
5762
|
seenLogos.add(t.logo);
|
|
5728
5763
|
return true;
|
|
5729
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
|
+
});
|
|
5730
5771
|
const assetsDir = import_path3.default.join(process.cwd(), "public", "assets", "logos");
|
|
5731
5772
|
if (options.copyAssets !== false) {
|
|
5732
5773
|
await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
|
package/dist/index.mjs
CHANGED
package/dist/scan.js
CHANGED
|
@@ -5583,6 +5583,41 @@ var BASE_DIR = import_path2.default.join(process.cwd(), "public", "stackscan");
|
|
|
5583
5583
|
var SKIPPED_TECHS = [
|
|
5584
5584
|
"react-dom"
|
|
5585
5585
|
];
|
|
5586
|
+
var CATEGORY_PRIORITY = [
|
|
5587
|
+
"language",
|
|
5588
|
+
"framework",
|
|
5589
|
+
"mobile",
|
|
5590
|
+
"frontend",
|
|
5591
|
+
"backend",
|
|
5592
|
+
"runtime",
|
|
5593
|
+
"database",
|
|
5594
|
+
"orm",
|
|
5595
|
+
"auth",
|
|
5596
|
+
"api",
|
|
5597
|
+
"state",
|
|
5598
|
+
"css",
|
|
5599
|
+
"cloud",
|
|
5600
|
+
"hosting",
|
|
5601
|
+
"devops",
|
|
5602
|
+
"container",
|
|
5603
|
+
"ci",
|
|
5604
|
+
"testing",
|
|
5605
|
+
"build",
|
|
5606
|
+
"lint",
|
|
5607
|
+
"format",
|
|
5608
|
+
"automation",
|
|
5609
|
+
"package",
|
|
5610
|
+
"ai",
|
|
5611
|
+
"network",
|
|
5612
|
+
"utility",
|
|
5613
|
+
"cms",
|
|
5614
|
+
"ssg",
|
|
5615
|
+
"payment"
|
|
5616
|
+
];
|
|
5617
|
+
var getCategoryPriority = (cat) => {
|
|
5618
|
+
const idx = CATEGORY_PRIORITY.indexOf(cat ? cat.toLowerCase() : "");
|
|
5619
|
+
return idx === -1 ? 999 : idx;
|
|
5620
|
+
};
|
|
5586
5621
|
async function scan(options = {}) {
|
|
5587
5622
|
if (options.readme === void 0) options.readme = true;
|
|
5588
5623
|
console.log("\u{1F680} Starting Scan...");
|
|
@@ -5648,6 +5683,12 @@ async function scan(options = {}) {
|
|
|
5648
5683
|
seenLogos.add(t.logo);
|
|
5649
5684
|
return true;
|
|
5650
5685
|
});
|
|
5686
|
+
uniqueTechs.sort((a, b) => {
|
|
5687
|
+
const pA = getCategoryPriority(a.type);
|
|
5688
|
+
const pB = getCategoryPriority(b.type);
|
|
5689
|
+
if (pA !== pB) return pA - pB;
|
|
5690
|
+
return a.name.localeCompare(b.name);
|
|
5691
|
+
});
|
|
5651
5692
|
const assetsDir = import_path2.default.join(process.cwd(), "public", "assets", "logos");
|
|
5652
5693
|
if (options.copyAssets !== false) {
|
|
5653
5694
|
await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
|
package/dist/scan.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stackscan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "Automatically detect tech stacks from repositories and generate logos, JSON, and markdown for portfolios.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"stackscan": "
|
|
9
|
+
"stackscan": "dist/cli.js"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsup src --format cjs,esm --dts --shims",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
|
29
|
-
"url": "https://github.com/benjamindotdev/stackscan.git"
|
|
29
|
+
"url": "git+https://github.com/benjamindotdev/stackscan.git"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|