stackscan 0.1.8 → 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.
package/dist/add.js CHANGED
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(add_exports);
36
36
  var import_fs_extra = __toESM(require("fs-extra"));
37
37
  var import_path = __toESM(require("path"));
38
38
  var import_chalk = __toESM(require("chalk"));
39
- var BASE_DIR = import_path.default.join(process.cwd(), "stackscan");
39
+ var BASE_DIR = import_path.default.join(process.cwd(), "public", "stackscan");
40
40
  async function add(sourcePath) {
41
41
  let absoluteSourcePath = import_path.default.resolve(sourcePath);
42
42
  if (!import_fs_extra.default.existsSync(absoluteSourcePath)) {
@@ -71,7 +71,7 @@ async function add(sourcePath) {
71
71
  import_fs_extra.default.mkdirSync(targetDir, { recursive: true });
72
72
  }
73
73
  import_fs_extra.default.copyFileSync(absoluteSourcePath, targetFile);
74
- console.log(import_chalk.default.green(`\u2705 Added project "${pkg.name}" to stackscan/${folderName}`));
74
+ console.log(import_chalk.default.green(`\u2705 Added project "${pkg.name}" to public/stackscan/${folderName}`));
75
75
  } catch (error) {
76
76
  console.error(import_chalk.default.red(`\u274C Error processing ${sourcePath}:`), error);
77
77
  process.exit(1);
package/dist/add.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  add
3
- } from "./chunk-IFEEO473.mjs";
3
+ } from "./chunk-ACCTMJVS.mjs";
4
4
  import "./chunk-EOKQCSHI.mjs";
5
5
  export {
6
6
  add
@@ -0,0 +1,49 @@
1
+ // src/add.ts
2
+ import fs from "fs-extra";
3
+ import path from "path";
4
+ import chalk from "chalk";
5
+ var BASE_DIR = path.join(process.cwd(), "public", "stackscan");
6
+ async function add(sourcePath) {
7
+ let absoluteSourcePath = path.resolve(sourcePath);
8
+ if (!fs.existsSync(absoluteSourcePath)) {
9
+ console.error(chalk.red(`\u274C Path not found: ${sourcePath}`));
10
+ process.exit(1);
11
+ }
12
+ if (fs.statSync(absoluteSourcePath).isDirectory()) {
13
+ absoluteSourcePath = path.join(absoluteSourcePath, "package.json");
14
+ if (!fs.existsSync(absoluteSourcePath)) {
15
+ console.error(chalk.red(`\u274C No package.json found in directory: ${sourcePath}`));
16
+ process.exit(1);
17
+ }
18
+ }
19
+ try {
20
+ const content = fs.readFileSync(absoluteSourcePath, "utf-8");
21
+ const pkg = JSON.parse(content);
22
+ if (!pkg.name) {
23
+ console.error(chalk.red(`\u274C No "name" field found in ${sourcePath}`));
24
+ process.exit(1);
25
+ }
26
+ const baseFolderName = pkg.name.replace(/^@/, "").replace(/\//g, "-");
27
+ let folderName = baseFolderName;
28
+ let targetDir = path.join(BASE_DIR, folderName);
29
+ let counter = 1;
30
+ while (fs.existsSync(targetDir)) {
31
+ folderName = `${baseFolderName}-${counter}`;
32
+ targetDir = path.join(BASE_DIR, folderName);
33
+ counter++;
34
+ }
35
+ const targetFile = path.join(targetDir, "package.json");
36
+ if (!fs.existsSync(targetDir)) {
37
+ fs.mkdirSync(targetDir, { recursive: true });
38
+ }
39
+ fs.copyFileSync(absoluteSourcePath, targetFile);
40
+ console.log(chalk.green(`\u2705 Added project "${pkg.name}" to public/stackscan/${folderName}`));
41
+ } catch (error) {
42
+ console.error(chalk.red(`\u274C Error processing ${sourcePath}:`), error);
43
+ process.exit(1);
44
+ }
45
+ }
46
+
47
+ export {
48
+ add
49
+ };
@@ -0,0 +1,158 @@
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
+ color
70
+ });
71
+ }
72
+ });
73
+ let uniqueTechs = Array.from(new Set(detectedTechs.map((t) => t.slug))).map((slug) => detectedTechs.find((t) => t.slug === slug));
74
+ const seenLogos = /* @__PURE__ */ new Set();
75
+ uniqueTechs = uniqueTechs.filter((t) => {
76
+ if (seenLogos.has(t.logo)) {
77
+ return false;
78
+ }
79
+ seenLogos.add(t.logo);
80
+ return true;
81
+ });
82
+ const assetsDir = path.join(process.cwd(), "public", "assets", "logos");
83
+ if (options.copyAssets !== false) {
84
+ await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
85
+ }
86
+ const techsWithUrls = uniqueTechs.map((t) => ({
87
+ ...t,
88
+ logo: `https://raw.githubusercontent.com/benjamindotdev/stackscan/main/public/assets/logos/${t.logo}`,
89
+ relativePath: `./public/assets/logos/${t.logo}`
90
+ }));
91
+ allTechs.push(...techsWithUrls);
92
+ fs.writeFileSync(
93
+ path.join(projectPath, "stack.json"),
94
+ JSON.stringify(techsWithUrls, null, 2)
95
+ );
96
+ const mdContent = generateMarkdown(techsWithUrls);
97
+ fs.writeFileSync(path.join(projectPath, "stack.md"), mdContent);
98
+ allProjects.push({
99
+ name: dir.name,
100
+ techs: techsWithUrls
101
+ });
102
+ console.log(`\u2705 ${dir.name.padEnd(20)} -> stack.json (${uniqueTechs.length} techs)`);
103
+ } catch (err) {
104
+ console.error(`\u274C Error processing ${dir.name}:`, err.message);
105
+ }
106
+ } else {
107
+ console.warn(`\u26A0\uFE0F Skipping "${dir.name}": No package.json found.`);
108
+ }
109
+ }
110
+ if (allProjects.length > 0) {
111
+ updateRootReadme(allProjects);
112
+ }
113
+ console.log("\n\u2728 Sync complete.");
114
+ }
115
+ function updateRootReadme(projects) {
116
+ const readmePath = path.join(process.cwd(), "README.md");
117
+ if (!fs.existsSync(readmePath)) {
118
+ console.log("\u26A0\uFE0F No root README.md found to update.");
119
+ return;
120
+ }
121
+ let readmeContent = fs.readFileSync(readmePath, "utf-8");
122
+ const startMarker = "<!-- STACKSYNC_START -->";
123
+ const endMarker = "<!-- STACKSYNC_END -->";
124
+ let newSection = `${startMarker}
125
+ ## My Projects
126
+
127
+ `;
128
+ for (const p of projects) {
129
+ newSection += `### ${p.name}
130
+ `;
131
+ newSection += `<p>
132
+ `;
133
+ for (const t of p.techs) {
134
+ const src = t.relativePath || t.logo;
135
+ newSection += ` <img src="${src}" alt="${t.name}" height="25" style="margin-right: 10px;" />
136
+ `;
137
+ }
138
+ newSection += `</p>
139
+
140
+ `;
141
+ }
142
+ newSection += `${endMarker}`;
143
+ if (readmeContent.includes(startMarker) && readmeContent.includes(endMarker)) {
144
+ const regex = new RegExp(`${startMarker}[\\s\\S]*?${endMarker}`);
145
+ readmeContent = readmeContent.replace(regex, newSection);
146
+ console.log(`\u{1F4DD} Updated root README.md with ${projects.length} projects.`);
147
+ } else {
148
+ readmeContent += `
149
+
150
+ ${newSection}`;
151
+ console.log(`\u{1F4DD} Appended projects to root README.md.`);
152
+ }
153
+ fs.writeFileSync(readmePath, readmeContent);
154
+ }
155
+
156
+ export {
157
+ scan
158
+ };
@@ -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,158 @@
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/sync.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 sync(options = {}) {
20
+ console.log("\u{1F680} Starting Sync...");
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
+ color
70
+ });
71
+ }
72
+ });
73
+ let uniqueTechs = Array.from(new Set(detectedTechs.map((t) => t.slug))).map((slug) => detectedTechs.find((t) => t.slug === slug));
74
+ const seenLogos = /* @__PURE__ */ new Set();
75
+ uniqueTechs = uniqueTechs.filter((t) => {
76
+ if (seenLogos.has(t.logo)) {
77
+ return false;
78
+ }
79
+ seenLogos.add(t.logo);
80
+ return true;
81
+ });
82
+ const assetsDir = path.join(process.cwd(), "public", "assets", "logos");
83
+ if (options.copyAssets !== false) {
84
+ await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
85
+ }
86
+ const techsWithUrls = uniqueTechs.map((t) => ({
87
+ ...t,
88
+ logo: `https://raw.githubusercontent.com/benjamindotdev/stackscan/main/public/assets/logos/${t.logo}`,
89
+ relativePath: `./public/assets/logos/${t.logo}`
90
+ }));
91
+ allTechs.push(...techsWithUrls);
92
+ fs.writeFileSync(
93
+ path.join(projectPath, "stack.json"),
94
+ JSON.stringify(techsWithUrls, null, 2)
95
+ );
96
+ const mdContent = generateMarkdown(techsWithUrls);
97
+ fs.writeFileSync(path.join(projectPath, "stack.md"), mdContent);
98
+ allProjects.push({
99
+ name: dir.name,
100
+ techs: techsWithUrls
101
+ });
102
+ console.log(`\u2705 ${dir.name.padEnd(20)} -> stack.json (${uniqueTechs.length} techs)`);
103
+ } catch (err) {
104
+ console.error(`\u274C Error processing ${dir.name}:`, err.message);
105
+ }
106
+ } else {
107
+ console.warn(`\u26A0\uFE0F Skipping "${dir.name}": No package.json found.`);
108
+ }
109
+ }
110
+ if (allProjects.length > 0) {
111
+ updateRootReadme(allProjects);
112
+ }
113
+ console.log("\n\u2728 Sync complete.");
114
+ }
115
+ function updateRootReadme(projects) {
116
+ const readmePath = path.join(process.cwd(), "README.md");
117
+ if (!fs.existsSync(readmePath)) {
118
+ console.log("\u26A0\uFE0F No root README.md found to update.");
119
+ return;
120
+ }
121
+ let readmeContent = fs.readFileSync(readmePath, "utf-8");
122
+ const startMarker = "<!-- STACKSYNC_START -->";
123
+ const endMarker = "<!-- STACKSYNC_END -->";
124
+ let newSection = `${startMarker}
125
+ ## My Projects
126
+
127
+ `;
128
+ for (const p of projects) {
129
+ newSection += `### ${p.name}
130
+ `;
131
+ newSection += `<p>
132
+ `;
133
+ for (const t of p.techs) {
134
+ const src = t.relativePath || t.logo;
135
+ newSection += ` <img src="${src}" alt="${t.name}" height="25" style="margin-right: 10px;" />
136
+ `;
137
+ }
138
+ newSection += `</p>
139
+
140
+ `;
141
+ }
142
+ newSection += `${endMarker}`;
143
+ if (readmeContent.includes(startMarker) && readmeContent.includes(endMarker)) {
144
+ const regex = new RegExp(`${startMarker}[\\s\\S]*?${endMarker}`);
145
+ readmeContent = readmeContent.replace(regex, newSection);
146
+ console.log(`\u{1F4DD} Updated root README.md with ${projects.length} projects.`);
147
+ } else {
148
+ readmeContent += `
149
+
150
+ ${newSection}`;
151
+ console.log(`\u{1F4DD} Appended projects to root README.md.`);
152
+ }
153
+ fs.writeFileSync(readmePath, readmeContent);
154
+ }
155
+
156
+ export {
157
+ sync
158
+ };
@@ -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
@@ -5571,7 +5571,7 @@ function generateMarkdown(techs, assetsPath, availableLogos) {
5571
5571
  }
5572
5572
 
5573
5573
  // src/scan.ts
5574
- var BASE_DIR = import_path2.default.join(process.cwd(), "stackscan");
5574
+ var BASE_DIR = import_path2.default.join(process.cwd(), "public", "stackscan");
5575
5575
  var SKIPPED_TECHS = [
5576
5576
  "react-dom"
5577
5577
  ];
@@ -5583,13 +5583,13 @@ async function scan(options = {}) {
5583
5583
  if (!import_fs.default.existsSync(BASE_DIR)) {
5584
5584
  console.log(`Creating stackscan directory at: ${BASE_DIR}`);
5585
5585
  import_fs.default.mkdirSync(BASE_DIR, { recursive: true });
5586
- console.log('Please place your project folders inside "stackscan/" and run this command again.');
5586
+ console.log('Please place your project folders inside "public/stackscan/" and run this command again.');
5587
5587
  process.exit(0);
5588
5588
  }
5589
5589
  const entries = import_fs.default.readdirSync(BASE_DIR, { withFileTypes: true });
5590
5590
  const projectDirs = entries.filter((dirent) => dirent.isDirectory() && dirent.name !== "input" && dirent.name !== "output");
5591
5591
  if (projectDirs.length === 0) {
5592
- console.log('\u26A0\uFE0F No project directories found in "stackscan/".');
5592
+ console.log('\u26A0\uFE0F No project directories found in "public/stackscan/".');
5593
5593
  return;
5594
5594
  }
5595
5595
  console.log(`Found ${projectDirs.length} projects to process.
@@ -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
  }
@@ -5716,7 +5717,7 @@ ${newSection}`;
5716
5717
  var import_fs_extra2 = __toESM(require("fs-extra"));
5717
5718
  var import_path3 = __toESM(require("path"));
5718
5719
  var import_chalk2 = __toESM(require("chalk"));
5719
- var BASE_DIR2 = import_path3.default.join(process.cwd(), "stackscan");
5720
+ var BASE_DIR2 = import_path3.default.join(process.cwd(), "public", "stackscan");
5720
5721
  async function add(sourcePath) {
5721
5722
  let absoluteSourcePath = import_path3.default.resolve(sourcePath);
5722
5723
  if (!import_fs_extra2.default.existsSync(absoluteSourcePath)) {
@@ -5751,7 +5752,7 @@ async function add(sourcePath) {
5751
5752
  import_fs_extra2.default.mkdirSync(targetDir, { recursive: true });
5752
5753
  }
5753
5754
  import_fs_extra2.default.copyFileSync(absoluteSourcePath, targetFile);
5754
- console.log(import_chalk2.default.green(`\u2705 Added project "${pkg.name}" to stackscan/${folderName}`));
5755
+ console.log(import_chalk2.default.green(`\u2705 Added project "${pkg.name}" to public/stackscan/${folderName}`));
5755
5756
  } catch (error) {
5756
5757
  console.error(import_chalk2.default.red(`\u274C Error processing ${sourcePath}:`), error);
5757
5758
  process.exit(1);
@@ -5761,10 +5762,10 @@ async function add(sourcePath) {
5761
5762
  // src/cli.ts
5762
5763
  var program = new import_commander.Command();
5763
5764
  program.name("stackscan").description("Auto-detect tech stacks and generate tech.json or markdown.").version("0.1.0");
5764
- program.command("scan", { isDefault: true }).description("Scan stacks from multiple projects in stackscan/").option("--color <mode>", "Color mode (brand, white, black, or hex)", "brand").action(async (options) => {
5765
+ 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").action(async (options) => {
5765
5766
  await scan(options);
5766
5767
  });
5767
- program.command("add <path>").description("Add a project (folder or package.json) to the stackscan workspace").action(async (path4) => {
5768
+ program.command("add <path>").description("Add a project (folder or package.json) to the public/stackscan workspace").action(async (path4) => {
5768
5769
  await add(path4);
5769
5770
  });
5770
5771
  program.parse();
package/dist/cli.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  add
4
- } from "./chunk-IFEEO473.mjs";
4
+ } from "./chunk-ACCTMJVS.mjs";
5
5
  import {
6
6
  scan
7
- } from "./chunk-4WANII4B.mjs";
7
+ } from "./chunk-ZPLKDMPP.mjs";
8
8
  import "./chunk-LSUI3VI4.mjs";
9
9
  import "./chunk-LB3L25FS.mjs";
10
10
  import "./chunk-UJM3S22V.mjs";
@@ -16,10 +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 stackscan/").option("--color <mode>", "Color mode (brand, white, black, or hex)", "brand").action(async (options) => {
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").action(async (options) => {
20
20
  await scan(options);
21
21
  });
22
- program.command("add <path>").description("Add a project (folder or package.json) to the stackscan workspace").action(async (path) => {
22
+ program.command("add <path>").description("Add a project (folder or package.json) to the public/stackscan workspace").action(async (path) => {
23
23
  await add(path);
24
24
  });
25
25
  program.parse();
package/dist/index.js CHANGED
@@ -50,7 +50,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
50
50
  var import_fs_extra = __toESM(require("fs-extra"));
51
51
  var import_path = __toESM(require("path"));
52
52
  var import_chalk = __toESM(require("chalk"));
53
- var BASE_DIR = import_path.default.join(process.cwd(), "stackscan");
53
+ var BASE_DIR = import_path.default.join(process.cwd(), "public", "stackscan");
54
54
  async function add(sourcePath) {
55
55
  let absoluteSourcePath = import_path.default.resolve(sourcePath);
56
56
  if (!import_fs_extra.default.existsSync(absoluteSourcePath)) {
@@ -85,7 +85,7 @@ async function add(sourcePath) {
85
85
  import_fs_extra.default.mkdirSync(targetDir, { recursive: true });
86
86
  }
87
87
  import_fs_extra.default.copyFileSync(absoluteSourcePath, targetFile);
88
- console.log(import_chalk.default.green(`\u2705 Added project "${pkg.name}" to stackscan/${folderName}`));
88
+ console.log(import_chalk.default.green(`\u2705 Added project "${pkg.name}" to public/stackscan/${folderName}`));
89
89
  } catch (error) {
90
90
  console.error(import_chalk.default.red(`\u274C Error processing ${sourcePath}:`), error);
91
91
  process.exit(1);
@@ -5658,7 +5658,7 @@ for (const def of techDefinitions) {
5658
5658
  }
5659
5659
 
5660
5660
  // src/scan.ts
5661
- var BASE_DIR2 = import_path3.default.join(process.cwd(), "stackscan");
5661
+ var BASE_DIR2 = import_path3.default.join(process.cwd(), "public", "stackscan");
5662
5662
  var SKIPPED_TECHS = [
5663
5663
  "react-dom"
5664
5664
  ];
@@ -5670,13 +5670,13 @@ async function scan(options = {}) {
5670
5670
  if (!import_fs.default.existsSync(BASE_DIR2)) {
5671
5671
  console.log(`Creating stackscan directory at: ${BASE_DIR2}`);
5672
5672
  import_fs.default.mkdirSync(BASE_DIR2, { recursive: true });
5673
- console.log('Please place your project folders inside "stackscan/" and run this command again.');
5673
+ console.log('Please place your project folders inside "public/stackscan/" and run this command again.');
5674
5674
  process.exit(0);
5675
5675
  }
5676
5676
  const entries = import_fs.default.readdirSync(BASE_DIR2, { withFileTypes: true });
5677
5677
  const projectDirs = entries.filter((dirent) => dirent.isDirectory() && dirent.name !== "input" && dirent.name !== "output");
5678
5678
  if (projectDirs.length === 0) {
5679
- console.log('\u26A0\uFE0F No project directories found in "stackscan/".');
5679
+ console.log('\u26A0\uFE0F No project directories found in "public/stackscan/".');
5680
5680
  return;
5681
5681
  }
5682
5682
  console.log(`Found ${projectDirs.length} projects to process.
@@ -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
  }
@@ -5802,7 +5803,7 @@ ${newSection}`;
5802
5803
  // src/sync.ts
5803
5804
  var import_fs2 = __toESM(require("fs"));
5804
5805
  var import_path4 = __toESM(require("path"));
5805
- var BASE_DIR3 = import_path4.default.join(process.cwd(), "stackscan");
5806
+ var BASE_DIR3 = import_path4.default.join(process.cwd(), "public", "stackscan");
5806
5807
  var SKIPPED_TECHS2 = [
5807
5808
  "react-dom"
5808
5809
  ];
@@ -5814,13 +5815,13 @@ async function sync(options = {}) {
5814
5815
  if (!import_fs2.default.existsSync(BASE_DIR3)) {
5815
5816
  console.log(`Creating stackscan directory at: ${BASE_DIR3}`);
5816
5817
  import_fs2.default.mkdirSync(BASE_DIR3, { recursive: true });
5817
- console.log('Please place your project folders inside "stackscan/" and run this command again.');
5818
+ console.log('Please place your project folders inside "public/stackscan/" and run this command again.');
5818
5819
  process.exit(0);
5819
5820
  }
5820
5821
  const entries = import_fs2.default.readdirSync(BASE_DIR3, { withFileTypes: true });
5821
5822
  const projectDirs = entries.filter((dirent) => dirent.isDirectory() && dirent.name !== "input" && dirent.name !== "output");
5822
5823
  if (projectDirs.length === 0) {
5823
- console.log('\u26A0\uFE0F No project directories found in "stackscan/".');
5824
+ console.log('\u26A0\uFE0F No project directories found in "public/stackscan/".');
5824
5825
  return;
5825
5826
  }
5826
5827
  console.log(`Found ${projectDirs.length} projects to process.
package/dist/index.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  import "./chunk-WAXGOBY2.mjs";
2
2
  import {
3
3
  add
4
- } from "./chunk-IFEEO473.mjs";
4
+ } from "./chunk-ACCTMJVS.mjs";
5
5
  import {
6
6
  scan
7
- } from "./chunk-4WANII4B.mjs";
7
+ } from "./chunk-ZPLKDMPP.mjs";
8
8
  import {
9
9
  sync
10
- } from "./chunk-GI46Z5RR.mjs";
10
+ } from "./chunk-XS2LU2MN.mjs";
11
11
  import {
12
12
  techMap
13
13
  } from "./chunk-LSUI3VI4.mjs";
package/dist/scan.js CHANGED
@@ -5579,7 +5579,7 @@ function generateMarkdown(techs, assetsPath, availableLogos) {
5579
5579
  }
5580
5580
 
5581
5581
  // src/scan.ts
5582
- var BASE_DIR = import_path2.default.join(process.cwd(), "stackscan");
5582
+ var BASE_DIR = import_path2.default.join(process.cwd(), "public", "stackscan");
5583
5583
  var SKIPPED_TECHS = [
5584
5584
  "react-dom"
5585
5585
  ];
@@ -5591,13 +5591,13 @@ async function scan(options = {}) {
5591
5591
  if (!import_fs.default.existsSync(BASE_DIR)) {
5592
5592
  console.log(`Creating stackscan directory at: ${BASE_DIR}`);
5593
5593
  import_fs.default.mkdirSync(BASE_DIR, { recursive: true });
5594
- console.log('Please place your project folders inside "stackscan/" and run this command again.');
5594
+ console.log('Please place your project folders inside "public/stackscan/" and run this command again.');
5595
5595
  process.exit(0);
5596
5596
  }
5597
5597
  const entries = import_fs.default.readdirSync(BASE_DIR, { withFileTypes: true });
5598
5598
  const projectDirs = entries.filter((dirent) => dirent.isDirectory() && dirent.name !== "input" && dirent.name !== "output");
5599
5599
  if (projectDirs.length === 0) {
5600
- console.log('\u26A0\uFE0F No project directories found in "stackscan/".');
5600
+ console.log('\u26A0\uFE0F No project directories found in "public/stackscan/".');
5601
5601
  return;
5602
5602
  }
5603
5603
  console.log(`Found ${projectDirs.length} projects to process.
@@ -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-4WANII4B.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/dist/sync.js CHANGED
@@ -5579,7 +5579,7 @@ function generateMarkdown(techs, assetsPath, availableLogos) {
5579
5579
  }
5580
5580
 
5581
5581
  // src/sync.ts
5582
- var BASE_DIR = import_path2.default.join(process.cwd(), "stackscan");
5582
+ var BASE_DIR = import_path2.default.join(process.cwd(), "public", "stackscan");
5583
5583
  var SKIPPED_TECHS = [
5584
5584
  "react-dom"
5585
5585
  ];
@@ -5591,13 +5591,13 @@ async function sync(options = {}) {
5591
5591
  if (!import_fs.default.existsSync(BASE_DIR)) {
5592
5592
  console.log(`Creating stackscan directory at: ${BASE_DIR}`);
5593
5593
  import_fs.default.mkdirSync(BASE_DIR, { recursive: true });
5594
- console.log('Please place your project folders inside "stackscan/" and run this command again.');
5594
+ console.log('Please place your project folders inside "public/stackscan/" and run this command again.');
5595
5595
  process.exit(0);
5596
5596
  }
5597
5597
  const entries = import_fs.default.readdirSync(BASE_DIR, { withFileTypes: true });
5598
5598
  const projectDirs = entries.filter((dirent) => dirent.isDirectory() && dirent.name !== "input" && dirent.name !== "output");
5599
5599
  if (projectDirs.length === 0) {
5600
- console.log('\u26A0\uFE0F No project directories found in "stackscan/".');
5600
+ console.log('\u26A0\uFE0F No project directories found in "public/stackscan/".');
5601
5601
  return;
5602
5602
  }
5603
5603
  console.log(`Found ${projectDirs.length} projects to process.
package/dist/sync.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sync
3
- } from "./chunk-GI46Z5RR.mjs";
3
+ } from "./chunk-XS2LU2MN.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.8",
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",