stackscan 0.1.15 → 0.1.16

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.
@@ -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
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-ACCTMJVS.mjs";
5
5
  import {
6
6
  scan
7
- } from "./chunk-A4WZRPHL.mjs";
7
+ } from "./chunk-PL2BQ6GK.mjs";
8
8
  import "./chunk-LSUI3VI4.mjs";
9
9
  import "./chunk-LB3L25FS.mjs";
10
10
  import "./chunk-UJM3S22V.mjs";
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
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-ACCTMJVS.mjs";
5
5
  import {
6
6
  scan
7
- } from "./chunk-A4WZRPHL.mjs";
7
+ } from "./chunk-PL2BQ6GK.mjs";
8
8
  import {
9
9
  sync
10
10
  } from "./chunk-XS2LU2MN.mjs";
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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  scan
3
- } from "./chunk-A4WZRPHL.mjs";
3
+ } from "./chunk-PL2BQ6GK.mjs";
4
4
  import "./chunk-LSUI3VI4.mjs";
5
5
  import "./chunk-LB3L25FS.mjs";
6
6
  import "./chunk-UJM3S22V.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stackscan",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
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",