stackscan 0.1.24 → 0.1.29
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/README.md +10 -0
- package/dist/add.mjs +3 -2
- package/dist/chunk-2ZANNQYS.mjs +162 -0
- package/dist/chunk-5AYPCRZA.mjs +21 -0
- package/dist/chunk-CFGUYUPP.mjs +42 -0
- package/dist/chunk-DF3YGYKJ.mjs +2241 -0
- package/dist/chunk-GFZMRHRT.mjs +181 -0
- package/dist/chunk-HT4RZGLE.mjs +267 -0
- package/dist/chunk-IFB4PCXR.mjs +28 -0
- package/dist/chunk-MFJXW5RR.mjs +6 -0
- package/dist/chunk-SECL5E42.mjs +23 -0
- package/dist/chunk-STCTH5AY.mjs +15619 -0
- package/dist/chunk-ZYFKPOWH.mjs +54 -0
- package/dist/cli.js +34 -7
- package/dist/cli.mjs +11 -7
- package/dist/defaults.mjs +3 -2
- package/dist/defaults.test.d.mts +2 -0
- package/dist/defaults.test.d.ts +2 -0
- package/dist/defaults.test.js +17007 -0
- package/dist/defaults.test.mjs +30 -0
- package/dist/index.js +34 -7
- package/dist/index.mjs +18 -12
- package/dist/magic-string.es-WH4FGKAB.mjs +1315 -0
- package/dist/output.mjs +4 -3
- package/dist/output.test.d.mts +2 -0
- package/dist/output.test.d.ts +2 -0
- package/dist/output.test.js +20687 -0
- package/dist/output.test.mjs +160 -0
- package/dist/scan.js +34 -7
- package/dist/scan.mjs +7 -6
- package/dist/scan.test.d.mts +2 -0
- package/dist/scan.test.d.ts +2 -0
- package/dist/scan.test.js +23184 -0
- package/dist/scan.test.mjs +183 -0
- package/dist/simple-icons-hex.mjs +1 -0
- package/dist/sync.js +1 -2
- package/dist/sync.mjs +7 -6
- package/dist/techDefinitions.js +1 -2
- package/dist/techDefinitions.mjs +3 -2
- package/dist/techMap.js +1 -2
- package/dist/techMap.mjs +4 -3
- package/dist/techMap.test.d.mts +2 -0
- package/dist/techMap.test.d.ts +2 -0
- package/dist/techMap.test.js +19240 -0
- package/dist/techMap.test.mjs +38 -0
- package/dist/types.mjs +3 -2
- package/package.json +9 -4
- package/public/assets/logos/defaults/package.svg +18 -18
- package/public/assets/logos/defaults/terminal.svg +16 -16
- package/public/assets/logos/defaults/wrench.svg +15 -15
- package/public/assets/logos/testing/vitest.svg +1 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import {
|
|
2
|
+
simple_icons_hex_default
|
|
3
|
+
} from "./chunk-EH2SEQZP.mjs";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_CATEGORY_ICONS
|
|
6
|
+
} from "./chunk-IFB4PCXR.mjs";
|
|
7
|
+
import {
|
|
8
|
+
__dirname,
|
|
9
|
+
init_esm_shims
|
|
10
|
+
} from "./chunk-5AYPCRZA.mjs";
|
|
11
|
+
|
|
12
|
+
// src/output.ts
|
|
13
|
+
init_esm_shims();
|
|
14
|
+
import fs from "fs-extra";
|
|
15
|
+
import path from "path";
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import { createRequire } from "module";
|
|
18
|
+
var require2 = createRequire(import.meta.url);
|
|
19
|
+
async function writeOutput(outPath, techs, config, format = "json", assetsOutPath) {
|
|
20
|
+
const outDirectory = path.dirname(outPath);
|
|
21
|
+
await fs.ensureDir(outDirectory);
|
|
22
|
+
let availableLogos = /* @__PURE__ */ new Set();
|
|
23
|
+
if (assetsOutPath) {
|
|
24
|
+
availableLogos = await copyAssets(techs, assetsOutPath, config);
|
|
25
|
+
}
|
|
26
|
+
if (format === "json") {
|
|
27
|
+
await fs.writeJSON(
|
|
28
|
+
outPath,
|
|
29
|
+
{
|
|
30
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31
|
+
tech: techs
|
|
32
|
+
},
|
|
33
|
+
{ spaces: 2 }
|
|
34
|
+
);
|
|
35
|
+
console.log(chalk.blue(`\u2192 Saved ${techs.length} tech entries to ${outPath}`));
|
|
36
|
+
} else if (format === "markdown") {
|
|
37
|
+
const relativeAssetsPath = assetsOutPath ? path.relative(outDirectory, assetsOutPath).replace(/\\/g, "/") : void 0;
|
|
38
|
+
const mdContent = generateMarkdown(techs, relativeAssetsPath, availableLogos);
|
|
39
|
+
const mdPath = outPath.endsWith(".json") ? outPath.replace(/\.json$/, ".md") : outPath;
|
|
40
|
+
await fs.writeFile(mdPath, mdContent);
|
|
41
|
+
console.log(chalk.blue(`\u2192 Saved markdown to ${mdPath}`));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function copyAssets(techs, dest, config) {
|
|
45
|
+
const srcDir = path.resolve(__dirname, "../public/assets/logos");
|
|
46
|
+
let lucideDir;
|
|
47
|
+
try {
|
|
48
|
+
lucideDir = path.join(path.dirname(require2.resolve("lucide-static/package.json")), "icons");
|
|
49
|
+
} catch (e) {
|
|
50
|
+
lucideDir = path.resolve(__dirname, "../node_modules/lucide-static/icons");
|
|
51
|
+
}
|
|
52
|
+
const copied = /* @__PURE__ */ new Set();
|
|
53
|
+
let count = 0;
|
|
54
|
+
for (const t of techs) {
|
|
55
|
+
let srcFile = t.logo ? path.join(srcDir, t.logo) : null;
|
|
56
|
+
let destFile = t.logo ? path.join(dest, t.logo) : null;
|
|
57
|
+
let isDefault = false;
|
|
58
|
+
if (!srcFile || !await fs.pathExists(srcFile)) {
|
|
59
|
+
const defaultIconName = DEFAULT_CATEGORY_ICONS[t.type];
|
|
60
|
+
if (defaultIconName) {
|
|
61
|
+
const lucideFile = path.join(lucideDir, `${defaultIconName}.svg`);
|
|
62
|
+
if (await fs.pathExists(lucideFile)) {
|
|
63
|
+
srcFile = lucideFile;
|
|
64
|
+
const newLogoPath = `defaults/${defaultIconName}.svg`;
|
|
65
|
+
destFile = path.join(dest, newLogoPath);
|
|
66
|
+
t.logo = newLogoPath;
|
|
67
|
+
isDefault = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (srcFile && destFile && await fs.pathExists(srcFile)) {
|
|
72
|
+
await fs.ensureDir(path.dirname(destFile));
|
|
73
|
+
let svgContent = await fs.readFile(srcFile, "utf8");
|
|
74
|
+
let color;
|
|
75
|
+
if (config.iconColors && config.iconColors[t.name]) {
|
|
76
|
+
color = config.iconColors[t.name];
|
|
77
|
+
} else if (config.colorMode === "white") {
|
|
78
|
+
color = "#FFFFFF";
|
|
79
|
+
} else if (config.colorMode === "black") {
|
|
80
|
+
color = "#000000";
|
|
81
|
+
} else if (config.colorMode === "custom" && config.customColor) {
|
|
82
|
+
color = config.customColor;
|
|
83
|
+
} else if (config.colorMode === "default" || !config.colorMode || config.colorMode === "brand") {
|
|
84
|
+
if (!isDefault) {
|
|
85
|
+
const slug = path.basename(t.logo, ".svg");
|
|
86
|
+
const brandHex = simple_icons_hex_default[slug] || simple_icons_hex_default[slug.toLowerCase()];
|
|
87
|
+
if (brandHex) {
|
|
88
|
+
color = `#${brandHex}`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (color) {
|
|
93
|
+
if (isDefault) {
|
|
94
|
+
if (svgContent.includes("stroke=")) {
|
|
95
|
+
svgContent = svgContent.replace(/stroke="[^"]*"/g, `stroke="${color}"`);
|
|
96
|
+
} else {
|
|
97
|
+
svgContent = svgContent.replace("<svg", `<svg stroke="${color}"`);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
if (svgContent.includes("fill=")) {
|
|
101
|
+
svgContent = svgContent.replace(/fill="[^"]*"/g, `fill="${color}"`);
|
|
102
|
+
} else {
|
|
103
|
+
svgContent = svgContent.replace("<svg", `<svg fill="${color}"`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
await fs.writeFile(destFile, svgContent);
|
|
108
|
+
copied.add(t.logo);
|
|
109
|
+
if (!isDefault) count++;
|
|
110
|
+
} else {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (count > 0) {
|
|
114
|
+
console.log(chalk.blue(`\u2192 Copied ${count} logos to ${dest}`));
|
|
115
|
+
}
|
|
116
|
+
return copied;
|
|
117
|
+
}
|
|
118
|
+
function generateMarkdown(techs, assetsPath, availableLogos) {
|
|
119
|
+
const grouped = {};
|
|
120
|
+
for (const t of techs) {
|
|
121
|
+
const type = t.type || "misc";
|
|
122
|
+
if (!grouped[type]) grouped[type] = [];
|
|
123
|
+
grouped[type].push(t);
|
|
124
|
+
}
|
|
125
|
+
let md = "# Tech Stack\n\n";
|
|
126
|
+
const order = [
|
|
127
|
+
"language",
|
|
128
|
+
"frontend",
|
|
129
|
+
"backend",
|
|
130
|
+
"framework",
|
|
131
|
+
"library",
|
|
132
|
+
"database",
|
|
133
|
+
"orm",
|
|
134
|
+
"auth",
|
|
135
|
+
"api",
|
|
136
|
+
"cloud",
|
|
137
|
+
"hosting",
|
|
138
|
+
"ci",
|
|
139
|
+
"devops",
|
|
140
|
+
"container",
|
|
141
|
+
"testing",
|
|
142
|
+
"build",
|
|
143
|
+
"tooling",
|
|
144
|
+
"misc"
|
|
145
|
+
];
|
|
146
|
+
const sortedKeys = Object.keys(grouped).sort((a, b) => {
|
|
147
|
+
const idxA = order.indexOf(a);
|
|
148
|
+
const idxB = order.indexOf(b);
|
|
149
|
+
if (idxA === -1 && idxB === -1) return a.localeCompare(b);
|
|
150
|
+
if (idxA === -1) return 1;
|
|
151
|
+
if (idxB === -1) return -1;
|
|
152
|
+
return idxA - idxB;
|
|
153
|
+
});
|
|
154
|
+
for (const type of sortedKeys) {
|
|
155
|
+
const title = type.charAt(0).toUpperCase() + type.slice(1);
|
|
156
|
+
md += `## ${title}
|
|
157
|
+
|
|
158
|
+
`;
|
|
159
|
+
for (const t of grouped[type]) {
|
|
160
|
+
if (t.logo && (t.logo.startsWith("http") || t.logo.startsWith("//"))) {
|
|
161
|
+
md += `- <img src="${t.logo}" alt="${t.name}" width="24" height="24" /> **${t.name}**
|
|
162
|
+
`;
|
|
163
|
+
} else if (assetsPath && t.logo && availableLogos?.has(t.logo)) {
|
|
164
|
+
const logoUrl = `${assetsPath}/${t.logo}`;
|
|
165
|
+
md += `- <img src="${logoUrl}" alt="${t.name}" width="24" height="24" /> **${t.name}**
|
|
166
|
+
`;
|
|
167
|
+
} else {
|
|
168
|
+
md += `- **${t.name}**
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
md += "\n";
|
|
173
|
+
}
|
|
174
|
+
return md;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export {
|
|
178
|
+
writeOutput,
|
|
179
|
+
copyAssets,
|
|
180
|
+
generateMarkdown
|
|
181
|
+
};
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import {
|
|
2
|
+
techMap
|
|
3
|
+
} from "./chunk-SECL5E42.mjs";
|
|
4
|
+
import {
|
|
5
|
+
copyAssets,
|
|
6
|
+
generateMarkdown
|
|
7
|
+
} from "./chunk-GFZMRHRT.mjs";
|
|
8
|
+
import {
|
|
9
|
+
simple_icons_hex_default
|
|
10
|
+
} from "./chunk-EH2SEQZP.mjs";
|
|
11
|
+
import {
|
|
12
|
+
init_esm_shims
|
|
13
|
+
} from "./chunk-5AYPCRZA.mjs";
|
|
14
|
+
|
|
15
|
+
// src/scan.ts
|
|
16
|
+
init_esm_shims();
|
|
17
|
+
import fs from "fs";
|
|
18
|
+
import path from "path";
|
|
19
|
+
var BASE_DIR = path.join(process.cwd(), "public", "stackscan");
|
|
20
|
+
var SKIPPED_TECHS = [
|
|
21
|
+
"react-dom"
|
|
22
|
+
];
|
|
23
|
+
function getPackageJson(projectPath) {
|
|
24
|
+
const pkgPath = path.join(projectPath, "package.json");
|
|
25
|
+
const pkgPathUnderscore = path.join(projectPath, "_package.json");
|
|
26
|
+
const isInsideStackScanDir = projectPath.includes(path.join("public", "stackscan"));
|
|
27
|
+
if (fs.existsSync(pkgPath) && isInsideStackScanDir) {
|
|
28
|
+
try {
|
|
29
|
+
console.log(`Renaming ${pkgPath} to ${pkgPathUnderscore}`);
|
|
30
|
+
fs.renameSync(pkgPath, pkgPathUnderscore);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.warn(`Failed to rename package.json to _package.json: ${e.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (fs.existsSync(pkgPathUnderscore)) {
|
|
36
|
+
try {
|
|
37
|
+
const content = fs.readFileSync(pkgPathUnderscore, "utf-8");
|
|
38
|
+
return JSON.parse(content);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
throw new Error(`Failed to read _package.json: ${e.message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (fs.existsSync(pkgPath)) {
|
|
44
|
+
try {
|
|
45
|
+
const content = fs.readFileSync(pkgPath, "utf-8");
|
|
46
|
+
return JSON.parse(content);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
throw new Error(`Failed to read package.json: ${e.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
var CATEGORY_PRIORITY = [
|
|
54
|
+
"language",
|
|
55
|
+
"framework",
|
|
56
|
+
"mobile",
|
|
57
|
+
"frontend",
|
|
58
|
+
"backend",
|
|
59
|
+
"runtime",
|
|
60
|
+
"database",
|
|
61
|
+
"orm",
|
|
62
|
+
"auth",
|
|
63
|
+
"api",
|
|
64
|
+
"state",
|
|
65
|
+
"css",
|
|
66
|
+
"cloud",
|
|
67
|
+
"hosting",
|
|
68
|
+
"devops",
|
|
69
|
+
"container",
|
|
70
|
+
"ci",
|
|
71
|
+
"testing",
|
|
72
|
+
"build",
|
|
73
|
+
"lint",
|
|
74
|
+
"format",
|
|
75
|
+
"automation",
|
|
76
|
+
"package",
|
|
77
|
+
"ai",
|
|
78
|
+
"network",
|
|
79
|
+
"utility",
|
|
80
|
+
"cms",
|
|
81
|
+
"ssg",
|
|
82
|
+
"payment"
|
|
83
|
+
];
|
|
84
|
+
var getCategoryPriority = (cat) => {
|
|
85
|
+
const idx = CATEGORY_PRIORITY.indexOf(cat ? cat.toLowerCase() : "");
|
|
86
|
+
return idx === -1 ? 999 : idx;
|
|
87
|
+
};
|
|
88
|
+
async function analyzeProject(projectPath, options) {
|
|
89
|
+
const pkg = getPackageJson(projectPath);
|
|
90
|
+
if (!pkg) {
|
|
91
|
+
throw new Error(`No package.json or _package.json found at ${projectPath}`);
|
|
92
|
+
}
|
|
93
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
94
|
+
const detectedTechs = [];
|
|
95
|
+
Object.keys(allDeps).forEach((dep) => {
|
|
96
|
+
if (SKIPPED_TECHS.includes(dep)) return;
|
|
97
|
+
if (techMap[dep]) {
|
|
98
|
+
const tech = techMap[dep];
|
|
99
|
+
let color = null;
|
|
100
|
+
if (options.color === "white") color = "#FFFFFF";
|
|
101
|
+
else if (options.color === "black") color = "#000000";
|
|
102
|
+
else if (options.color && options.color.startsWith("#")) color = options.color;
|
|
103
|
+
else {
|
|
104
|
+
const depSlug = dep.toLowerCase();
|
|
105
|
+
const nameSlug = tech.name.toLowerCase();
|
|
106
|
+
const nameSlugNoSpaces = tech.name.toLowerCase().replace(/\s+/g, "");
|
|
107
|
+
const hex = simple_icons_hex_default[depSlug] || simple_icons_hex_default[nameSlug] || simple_icons_hex_default[nameSlugNoSpaces];
|
|
108
|
+
if (hex) color = `#${hex}`;
|
|
109
|
+
}
|
|
110
|
+
detectedTechs.push({
|
|
111
|
+
name: tech.name,
|
|
112
|
+
slug: dep,
|
|
113
|
+
logo: tech.logo,
|
|
114
|
+
type: tech.type,
|
|
115
|
+
color
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
const uniqueSlugs = Array.from(new Set(detectedTechs.map((t) => t.slug)));
|
|
120
|
+
let uniqueTechs = uniqueSlugs.map((slug) => detectedTechs.find((t) => t.slug === slug));
|
|
121
|
+
const seenLogos = /* @__PURE__ */ new Set();
|
|
122
|
+
uniqueTechs = uniqueTechs.filter((t) => {
|
|
123
|
+
if (seenLogos.has(t.logo)) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
seenLogos.add(t.logo);
|
|
127
|
+
return true;
|
|
128
|
+
});
|
|
129
|
+
uniqueTechs.sort((a, b) => {
|
|
130
|
+
const pA = getCategoryPriority(a.type);
|
|
131
|
+
const pB = getCategoryPriority(b.type);
|
|
132
|
+
if (pA !== pB) return pA - pB;
|
|
133
|
+
return a.name.localeCompare(b.name);
|
|
134
|
+
});
|
|
135
|
+
const assetsDir = path.join(process.cwd(), "public", "assets", "logos");
|
|
136
|
+
if (options.copyAssets !== false) {
|
|
137
|
+
await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
|
|
138
|
+
}
|
|
139
|
+
return uniqueTechs.map((t) => ({
|
|
140
|
+
...t,
|
|
141
|
+
logo: `https://raw.githubusercontent.com/benjamindotdev/stackscan/main/public/assets/logos/${t.logo}`,
|
|
142
|
+
relativePath: `./public/assets/logos/${t.logo}`
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
async function scan(targetPath, optionsOrUndefined) {
|
|
146
|
+
let options = {};
|
|
147
|
+
let pathArg = void 0;
|
|
148
|
+
if (typeof targetPath === "string") {
|
|
149
|
+
pathArg = targetPath;
|
|
150
|
+
options = optionsOrUndefined || {};
|
|
151
|
+
} else if (typeof targetPath === "object") {
|
|
152
|
+
options = targetPath;
|
|
153
|
+
}
|
|
154
|
+
if (options.readme === void 0) options.readme = true;
|
|
155
|
+
console.log("\u{1F680} Starting Scan...");
|
|
156
|
+
if (options.color) {
|
|
157
|
+
console.log(`\u{1F3A8} Color mode: ${options.color}`);
|
|
158
|
+
}
|
|
159
|
+
if (pathArg) {
|
|
160
|
+
const absPath = path.resolve(pathArg);
|
|
161
|
+
console.log(`Scanning single project at: ${absPath}`);
|
|
162
|
+
try {
|
|
163
|
+
const techsWithUrls = await analyzeProject(absPath, options);
|
|
164
|
+
if (options.out) {
|
|
165
|
+
const outPath = path.resolve(options.out);
|
|
166
|
+
fs.writeFileSync(outPath, JSON.stringify(techsWithUrls, null, 2));
|
|
167
|
+
console.log(`\u2705 Generated stack output to: ${options.out}`);
|
|
168
|
+
} else {
|
|
169
|
+
const outPath = path.join(absPath, "stack.json");
|
|
170
|
+
fs.writeFileSync(outPath, JSON.stringify(techsWithUrls, null, 2));
|
|
171
|
+
console.log(`\u2705 Generated stack.json at: ${outPath}`);
|
|
172
|
+
}
|
|
173
|
+
} catch (err) {
|
|
174
|
+
console.error(`\u274C Error scanning project:`, err.message);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (!fs.existsSync(BASE_DIR)) {
|
|
180
|
+
console.log(`Creating stackscan directory at: ${BASE_DIR}`);
|
|
181
|
+
fs.mkdirSync(BASE_DIR, { recursive: true });
|
|
182
|
+
console.log('Please place your project folders inside "public/stackscan/" and run this command again.');
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
185
|
+
const entries = fs.readdirSync(BASE_DIR, { withFileTypes: true });
|
|
186
|
+
const projectDirs = entries.filter((dirent) => dirent.isDirectory() && dirent.name !== "input" && dirent.name !== "output");
|
|
187
|
+
if (projectDirs.length === 0) {
|
|
188
|
+
console.log('\u26A0\uFE0F No project directories found in "public/stackscan/".');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
console.log(`Found ${projectDirs.length} projects to process.
|
|
192
|
+
`);
|
|
193
|
+
const allProjects = [];
|
|
194
|
+
for (const dir of projectDirs) {
|
|
195
|
+
const projectPath = path.join(BASE_DIR, dir.name);
|
|
196
|
+
if (fs.existsSync(path.join(projectPath, "package.json"))) {
|
|
197
|
+
try {
|
|
198
|
+
const techsWithUrls = await analyzeProject(projectPath, options);
|
|
199
|
+
fs.writeFileSync(
|
|
200
|
+
path.join(projectPath, "stack.json"),
|
|
201
|
+
JSON.stringify(techsWithUrls, null, 2)
|
|
202
|
+
);
|
|
203
|
+
const mdContent = generateMarkdown(techsWithUrls);
|
|
204
|
+
fs.writeFileSync(path.join(projectPath, "stack.md"), mdContent);
|
|
205
|
+
allProjects.push({
|
|
206
|
+
name: dir.name,
|
|
207
|
+
techs: techsWithUrls
|
|
208
|
+
});
|
|
209
|
+
console.log(`\u2705 ${dir.name.padEnd(20)} -> stack.json (${techsWithUrls.length} techs)`);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
console.error(`\u274C Error processing ${dir.name}:`, err.message);
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
console.warn(`\u26A0\uFE0F Skipping "${dir.name}": No package.json found.`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (options.readme && allProjects.length > 0) {
|
|
218
|
+
updateRootReadme(allProjects);
|
|
219
|
+
} else if (!options.readme) {
|
|
220
|
+
console.log("Skipping README update (--no-readme passed).");
|
|
221
|
+
}
|
|
222
|
+
console.log("\n\u2728 Sync complete.");
|
|
223
|
+
}
|
|
224
|
+
function updateRootReadme(projects) {
|
|
225
|
+
const readmePath = path.join(process.cwd(), "README.md");
|
|
226
|
+
if (!fs.existsSync(readmePath)) {
|
|
227
|
+
console.log("\u26A0\uFE0F No root README.md found to update.");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
let readmeContent = fs.readFileSync(readmePath, "utf-8");
|
|
231
|
+
const startMarker = "<!-- STACKSCAN_START -->";
|
|
232
|
+
const endMarker = "<!-- STACKSCAN_END -->";
|
|
233
|
+
let newSection = `${startMarker}
|
|
234
|
+
## My Projects
|
|
235
|
+
|
|
236
|
+
`;
|
|
237
|
+
for (const p of projects) {
|
|
238
|
+
newSection += `### ${p.name}
|
|
239
|
+
`;
|
|
240
|
+
newSection += `<p>
|
|
241
|
+
`;
|
|
242
|
+
for (const t of p.techs) {
|
|
243
|
+
const src = t.relativePath || t.logo;
|
|
244
|
+
newSection += ` <img src="${src}" alt="${t.name}" height="25" style="margin-right: 10px;" />
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
newSection += `</p>
|
|
248
|
+
|
|
249
|
+
`;
|
|
250
|
+
}
|
|
251
|
+
newSection += `${endMarker}`;
|
|
252
|
+
if (readmeContent.includes(startMarker) && readmeContent.includes(endMarker)) {
|
|
253
|
+
const regex = new RegExp(`${startMarker}[\\s\\S]*?${endMarker}`);
|
|
254
|
+
readmeContent = readmeContent.replace(regex, newSection);
|
|
255
|
+
console.log(`\u{1F4DD} Updated root README.md with ${projects.length} projects.`);
|
|
256
|
+
} else {
|
|
257
|
+
readmeContent += `
|
|
258
|
+
|
|
259
|
+
${newSection}`;
|
|
260
|
+
console.log(`\u{1F4DD} Appended projects to root README.md.`);
|
|
261
|
+
}
|
|
262
|
+
fs.writeFileSync(readmePath, readmeContent);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export {
|
|
266
|
+
scan
|
|
267
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
init_esm_shims
|
|
3
|
+
} from "./chunk-5AYPCRZA.mjs";
|
|
4
|
+
|
|
5
|
+
// src/defaults.ts
|
|
6
|
+
init_esm_shims();
|
|
7
|
+
var DEFAULT_CATEGORY_ICONS = {
|
|
8
|
+
ai: "brain",
|
|
9
|
+
auth: "lock",
|
|
10
|
+
backend: "server",
|
|
11
|
+
build: "package",
|
|
12
|
+
cloud: "cloud",
|
|
13
|
+
css: "palette",
|
|
14
|
+
lint: "check-square",
|
|
15
|
+
network: "globe",
|
|
16
|
+
state: "layers",
|
|
17
|
+
utility: "wrench",
|
|
18
|
+
// Optional refinements
|
|
19
|
+
validation: "shield-check",
|
|
20
|
+
date: "clock",
|
|
21
|
+
cli: "terminal",
|
|
22
|
+
git: "git-branch",
|
|
23
|
+
http: "arrow-left-right"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
DEFAULT_CATEGORY_ICONS
|
|
28
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
techDefinitions
|
|
3
|
+
} from "./chunk-DF3YGYKJ.mjs";
|
|
4
|
+
import {
|
|
5
|
+
init_esm_shims
|
|
6
|
+
} from "./chunk-5AYPCRZA.mjs";
|
|
7
|
+
|
|
8
|
+
// src/techMap.ts
|
|
9
|
+
init_esm_shims();
|
|
10
|
+
var techMap = {};
|
|
11
|
+
for (const def of techDefinitions) {
|
|
12
|
+
for (const alias of def.aliases) {
|
|
13
|
+
techMap[alias] = {
|
|
14
|
+
name: def.name,
|
|
15
|
+
logo: def.logo,
|
|
16
|
+
type: def.category
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
techMap
|
|
23
|
+
};
|