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.
@@ -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 scan(options = {}) {
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
- const packageJsonPath = import_path2.default.join(projectPath, "package.json");
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 content = import_fs.default.readFileSync(packageJsonPath, "utf-8");
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 (${uniqueTechs.length} techs)`);
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
- await scan(options);
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-PL2BQ6GK.mjs";
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
- await scan(options);
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 scan(options = {}) {
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
- const packageJsonPath = import_path3.default.join(projectPath, "package.json");
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 content = import_fs.default.readFileSync(packageJsonPath, "utf-8");
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 (${uniqueTechs.length} techs)`);
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-PL2BQ6GK.mjs";
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(options?: SyncOptions): Promise<void>;
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(options?: SyncOptions): Promise<void>;
7
+ declare function scan(targetPath?: string | object, optionsOrUndefined?: SyncOptions): Promise<void>;
7
8
 
8
9
  export { scan };