stackscan 0.1.9 → 0.1.11

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,159 @@
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
+ async function scan(options = {}) {
20
+ console.log("\u{1F680} Starting Scan...");
21
+ if (options.color) {
22
+ console.log(`\u{1F3A8} Color mode: ${options.color}`);
23
+ }
24
+ if (!fs.existsSync(BASE_DIR)) {
25
+ console.log(`Creating stackscan directory at: ${BASE_DIR}`);
26
+ fs.mkdirSync(BASE_DIR, { recursive: true });
27
+ console.log('Please place your project folders inside "public/stackscan/" and run this command again.');
28
+ process.exit(0);
29
+ }
30
+ const entries = fs.readdirSync(BASE_DIR, { withFileTypes: true });
31
+ const projectDirs = entries.filter((dirent) => dirent.isDirectory() && dirent.name !== "input" && dirent.name !== "output");
32
+ if (projectDirs.length === 0) {
33
+ console.log('\u26A0\uFE0F No project directories found in "public/stackscan/".');
34
+ return;
35
+ }
36
+ console.log(`Found ${projectDirs.length} projects to process.
37
+ `);
38
+ const allProjects = [];
39
+ const allTechs = [];
40
+ for (const dir of projectDirs) {
41
+ const projectPath = path.join(BASE_DIR, dir.name);
42
+ const packageJsonPath = path.join(projectPath, "package.json");
43
+ if (fs.existsSync(packageJsonPath)) {
44
+ try {
45
+ const content = fs.readFileSync(packageJsonPath, "utf-8");
46
+ const pkg = JSON.parse(content);
47
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
48
+ const detectedTechs = [];
49
+ Object.keys(allDeps).forEach((dep) => {
50
+ if (SKIPPED_TECHS.includes(dep)) return;
51
+ if (techMap[dep]) {
52
+ const tech = techMap[dep];
53
+ let color = null;
54
+ if (options.color === "white") color = "#FFFFFF";
55
+ else if (options.color === "black") color = "#000000";
56
+ else if (options.color && options.color.startsWith("#")) color = options.color;
57
+ else {
58
+ const depSlug = dep.toLowerCase();
59
+ const nameSlug = tech.name.toLowerCase();
60
+ const nameSlugNoSpaces = tech.name.toLowerCase().replace(/\s+/g, "");
61
+ const hex = simple_icons_hex_default[depSlug] || simple_icons_hex_default[nameSlug] || simple_icons_hex_default[nameSlugNoSpaces];
62
+ if (hex) color = `#${hex}`;
63
+ }
64
+ detectedTechs.push({
65
+ name: tech.name,
66
+ slug: dep,
67
+ logo: tech.logo,
68
+ // Raw path for resolution
69
+ type: tech.category,
70
+ color
71
+ });
72
+ }
73
+ });
74
+ let uniqueTechs = Array.from(new Set(detectedTechs.map((t) => t.slug))).map((slug) => detectedTechs.find((t) => t.slug === slug));
75
+ const seenLogos = /* @__PURE__ */ new Set();
76
+ uniqueTechs = uniqueTechs.filter((t) => {
77
+ if (seenLogos.has(t.logo)) {
78
+ return false;
79
+ }
80
+ seenLogos.add(t.logo);
81
+ return true;
82
+ });
83
+ const assetsDir = path.join(process.cwd(), "public", "assets", "logos");
84
+ if (options.copyAssets !== false) {
85
+ await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
86
+ }
87
+ const techsWithUrls = uniqueTechs.map((t) => ({
88
+ ...t,
89
+ logo: `https://raw.githubusercontent.com/benjamindotdev/stackscan/main/public/assets/logos/${t.logo}`,
90
+ relativePath: `./public/assets/logos/${t.logo}`
91
+ }));
92
+ allTechs.push(...techsWithUrls);
93
+ fs.writeFileSync(
94
+ path.join(projectPath, "stack.json"),
95
+ JSON.stringify(techsWithUrls, null, 2)
96
+ );
97
+ const mdContent = generateMarkdown(techsWithUrls);
98
+ fs.writeFileSync(path.join(projectPath, "stack.md"), mdContent);
99
+ allProjects.push({
100
+ name: dir.name,
101
+ techs: techsWithUrls
102
+ });
103
+ console.log(`\u2705 ${dir.name.padEnd(20)} -> stack.json (${uniqueTechs.length} techs)`);
104
+ } catch (err) {
105
+ console.error(`\u274C Error processing ${dir.name}:`, err.message);
106
+ }
107
+ } else {
108
+ console.warn(`\u26A0\uFE0F Skipping "${dir.name}": No package.json found.`);
109
+ }
110
+ }
111
+ if (allProjects.length > 0) {
112
+ updateRootReadme(allProjects);
113
+ }
114
+ console.log("\n\u2728 Sync complete.");
115
+ }
116
+ function updateRootReadme(projects) {
117
+ const readmePath = path.join(process.cwd(), "README.md");
118
+ if (!fs.existsSync(readmePath)) {
119
+ console.log("\u26A0\uFE0F No root README.md found to update.");
120
+ return;
121
+ }
122
+ let readmeContent = fs.readFileSync(readmePath, "utf-8");
123
+ const startMarker = "<!-- STACKSYNC_START -->";
124
+ const endMarker = "<!-- STACKSYNC_END -->";
125
+ let newSection = `${startMarker}
126
+ ## My Projects
127
+
128
+ `;
129
+ for (const p of projects) {
130
+ newSection += `### ${p.name}
131
+ `;
132
+ newSection += `<p>
133
+ `;
134
+ for (const t of p.techs) {
135
+ const src = t.relativePath || t.logo;
136
+ newSection += ` <img src="${src}" alt="${t.name}" height="25" style="margin-right: 10px;" />
137
+ `;
138
+ }
139
+ newSection += `</p>
140
+
141
+ `;
142
+ }
143
+ newSection += `${endMarker}`;
144
+ if (readmeContent.includes(startMarker) && readmeContent.includes(endMarker)) {
145
+ const regex = new RegExp(`${startMarker}[\\s\\S]*?${endMarker}`);
146
+ readmeContent = readmeContent.replace(regex, newSection);
147
+ console.log(`\u{1F4DD} Updated root README.md with ${projects.length} projects.`);
148
+ } else {
149
+ readmeContent += `
150
+
151
+ ${newSection}`;
152
+ console.log(`\u{1F4DD} Appended projects to root README.md.`);
153
+ }
154
+ fs.writeFileSync(readmePath, readmeContent);
155
+ }
156
+
157
+ export {
158
+ scan
159
+ };
@@ -0,0 +1,159 @@
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
+ async function scan(options = {}) {
20
+ console.log("\u{1F680} Starting Scan...");
21
+ if (options.color) {
22
+ console.log(`\u{1F3A8} Color mode: ${options.color}`);
23
+ }
24
+ if (!fs.existsSync(BASE_DIR)) {
25
+ console.log(`Creating stackscan directory at: ${BASE_DIR}`);
26
+ fs.mkdirSync(BASE_DIR, { recursive: true });
27
+ console.log('Please place your project folders inside "public/stackscan/" and run this command again.');
28
+ process.exit(0);
29
+ }
30
+ const entries = fs.readdirSync(BASE_DIR, { withFileTypes: true });
31
+ const projectDirs = entries.filter((dirent) => dirent.isDirectory() && dirent.name !== "input" && dirent.name !== "output");
32
+ if (projectDirs.length === 0) {
33
+ console.log('\u26A0\uFE0F No project directories found in "public/stackscan/".');
34
+ return;
35
+ }
36
+ console.log(`Found ${projectDirs.length} projects to process.
37
+ `);
38
+ const allProjects = [];
39
+ const allTechs = [];
40
+ for (const dir of projectDirs) {
41
+ const projectPath = path.join(BASE_DIR, dir.name);
42
+ const packageJsonPath = path.join(projectPath, "package.json");
43
+ if (fs.existsSync(packageJsonPath)) {
44
+ try {
45
+ const content = fs.readFileSync(packageJsonPath, "utf-8");
46
+ const pkg = JSON.parse(content);
47
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
48
+ const detectedTechs = [];
49
+ Object.keys(allDeps).forEach((dep) => {
50
+ if (SKIPPED_TECHS.includes(dep)) return;
51
+ if (techMap[dep]) {
52
+ const tech = techMap[dep];
53
+ let color = null;
54
+ if (options.color === "white") color = "#FFFFFF";
55
+ else if (options.color === "black") color = "#000000";
56
+ else if (options.color && options.color.startsWith("#")) color = options.color;
57
+ else {
58
+ const depSlug = dep.toLowerCase();
59
+ const nameSlug = tech.name.toLowerCase();
60
+ const nameSlugNoSpaces = tech.name.toLowerCase().replace(/\s+/g, "");
61
+ const hex = simple_icons_hex_default[depSlug] || simple_icons_hex_default[nameSlug] || simple_icons_hex_default[nameSlugNoSpaces];
62
+ if (hex) color = `#${hex}`;
63
+ }
64
+ detectedTechs.push({
65
+ name: tech.name,
66
+ slug: dep,
67
+ logo: tech.logo,
68
+ // Raw path for resolution
69
+ type: tech.type,
70
+ color
71
+ });
72
+ }
73
+ });
74
+ let uniqueTechs = Array.from(new Set(detectedTechs.map((t) => t.slug))).map((slug) => detectedTechs.find((t) => t.slug === slug));
75
+ const seenLogos = /* @__PURE__ */ new Set();
76
+ uniqueTechs = uniqueTechs.filter((t) => {
77
+ if (seenLogos.has(t.logo)) {
78
+ return false;
79
+ }
80
+ seenLogos.add(t.logo);
81
+ return true;
82
+ });
83
+ const assetsDir = path.join(process.cwd(), "public", "assets", "logos");
84
+ if (options.copyAssets !== false) {
85
+ await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
86
+ }
87
+ const techsWithUrls = uniqueTechs.map((t) => ({
88
+ ...t,
89
+ logo: `https://raw.githubusercontent.com/benjamindotdev/stackscan/main/public/assets/logos/${t.logo}`,
90
+ relativePath: `./public/assets/logos/${t.logo}`
91
+ }));
92
+ allTechs.push(...techsWithUrls);
93
+ fs.writeFileSync(
94
+ path.join(projectPath, "stack.json"),
95
+ JSON.stringify(techsWithUrls, null, 2)
96
+ );
97
+ const mdContent = generateMarkdown(techsWithUrls);
98
+ fs.writeFileSync(path.join(projectPath, "stack.md"), mdContent);
99
+ allProjects.push({
100
+ name: dir.name,
101
+ techs: techsWithUrls
102
+ });
103
+ console.log(`\u2705 ${dir.name.padEnd(20)} -> stack.json (${uniqueTechs.length} techs)`);
104
+ } catch (err) {
105
+ console.error(`\u274C Error processing ${dir.name}:`, err.message);
106
+ }
107
+ } else {
108
+ console.warn(`\u26A0\uFE0F Skipping "${dir.name}": No package.json found.`);
109
+ }
110
+ }
111
+ if (allProjects.length > 0) {
112
+ updateRootReadme(allProjects);
113
+ }
114
+ console.log("\n\u2728 Sync complete.");
115
+ }
116
+ function updateRootReadme(projects) {
117
+ const readmePath = path.join(process.cwd(), "README.md");
118
+ if (!fs.existsSync(readmePath)) {
119
+ console.log("\u26A0\uFE0F No root README.md found to update.");
120
+ return;
121
+ }
122
+ let readmeContent = fs.readFileSync(readmePath, "utf-8");
123
+ const startMarker = "<!-- STACKSYNC_START -->";
124
+ const endMarker = "<!-- STACKSYNC_END -->";
125
+ let newSection = `${startMarker}
126
+ ## My Projects
127
+
128
+ `;
129
+ for (const p of projects) {
130
+ newSection += `### ${p.name}
131
+ `;
132
+ newSection += `<p>
133
+ `;
134
+ for (const t of p.techs) {
135
+ const src = t.relativePath || t.logo;
136
+ newSection += ` <img src="${src}" alt="${t.name}" height="25" style="margin-right: 10px;" />
137
+ `;
138
+ }
139
+ newSection += `</p>
140
+
141
+ `;
142
+ }
143
+ newSection += `${endMarker}`;
144
+ if (readmeContent.includes(startMarker) && readmeContent.includes(endMarker)) {
145
+ const regex = new RegExp(`${startMarker}[\\s\\S]*?${endMarker}`);
146
+ readmeContent = readmeContent.replace(regex, newSection);
147
+ console.log(`\u{1F4DD} Updated root README.md with ${projects.length} projects.`);
148
+ } else {
149
+ readmeContent += `
150
+
151
+ ${newSection}`;
152
+ console.log(`\u{1F4DD} Appended projects to root README.md.`);
153
+ }
154
+ fs.writeFileSync(readmePath, readmeContent);
155
+ }
156
+
157
+ export {
158
+ scan
159
+ };
package/dist/cli.js CHANGED
@@ -5625,6 +5625,7 @@ async function scan(options = {}) {
5625
5625
  slug: dep,
5626
5626
  logo: tech.logo,
5627
5627
  // Raw path for resolution
5628
+ type: tech.type,
5628
5629
  color
5629
5630
  });
5630
5631
  }
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-DYBWKLM7.mjs";
7
+ } from "./chunk-ZPLKDMPP.mjs";
8
8
  import "./chunk-LSUI3VI4.mjs";
9
9
  import "./chunk-LB3L25FS.mjs";
10
10
  import "./chunk-UJM3S22V.mjs";
package/dist/index.js CHANGED
@@ -5712,6 +5712,7 @@ async function scan(options = {}) {
5712
5712
  slug: dep,
5713
5713
  logo: tech.logo,
5714
5714
  // Raw path for resolution
5715
+ type: tech.type,
5715
5716
  color
5716
5717
  });
5717
5718
  }
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-DYBWKLM7.mjs";
7
+ } from "./chunk-ZPLKDMPP.mjs";
8
8
  import {
9
9
  sync
10
10
  } from "./chunk-XS2LU2MN.mjs";
package/dist/scan.js CHANGED
@@ -5633,6 +5633,7 @@ async function scan(options = {}) {
5633
5633
  slug: dep,
5634
5634
  logo: tech.logo,
5635
5635
  // Raw path for resolution
5636
+ type: tech.type,
5636
5637
  color
5637
5638
  });
5638
5639
  }
package/dist/scan.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  scan
3
- } from "./chunk-DYBWKLM7.mjs";
3
+ } from "./chunk-ZPLKDMPP.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.9",
3
+ "version": "0.1.11",
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",