stackscan 0.1.23 → 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 +16 -8
- package/dist/add.mjs +3 -2
- package/dist/chunk-24PM76MV.mjs +235 -0
- 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-E75XPZ2U.mjs +2237 -0
- package/dist/chunk-GFZMRHRT.mjs +181 -0
- package/dist/chunk-HKCVFKM4.mjs +19 -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-XNTLNSF6.mjs +158 -0
- package/dist/chunk-ZYFKPOWH.mjs +54 -0
- package/dist/cli.js +48 -15
- 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.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +50 -17
- package/dist/index.mjs +18 -12
- package/dist/magic-string.es-WH4FGKAB.mjs +1315 -0
- package/dist/output.d.mts +3 -3
- package/dist/output.d.ts +3 -3
- 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 +48 -15
- 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 +15 -10
- package/dist/sync.mjs +7 -6
- package/dist/techDefinitions.js +13 -8
- package/dist/techDefinitions.mjs +3 -2
- package/dist/techMap.js +13 -8
- 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.d.mts +2 -2
- package/dist/types.d.ts +2 -2
- 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
package/README.md
CHANGED
|
@@ -38,10 +38,8 @@ stackscan
|
|
|
38
38
|
## Usage
|
|
39
39
|
|
|
40
40
|
1. **Prepare Input**:
|
|
41
|
-
* Place
|
|
42
|
-
*
|
|
43
|
-
* If you have multiple, you can name them `package (1).json`, `package (2).json`, etc.
|
|
44
|
-
* StackScan will automatically create folders based on the project name defined in each file.
|
|
41
|
+
* Place a folder for each desired project inside `public/stackscan/`.
|
|
42
|
+
* Each folder should contain a `package.json` file.
|
|
45
43
|
2. **Run Scan**:
|
|
46
44
|
|
|
47
45
|
```bash
|
|
@@ -60,10 +58,10 @@ npx stackscan add ./path/to/package.json
|
|
|
60
58
|
npx stackscan add ../my-project
|
|
61
59
|
```
|
|
62
60
|
|
|
63
|
-
This will copy the `package.json` into a new folder inside `stackscan/` (e.g., `stackscan/my-project/`), handling name collisions automatically.
|
|
61
|
+
This will copy the `package.json` into a new folder inside `public/stackscan/` (e.g., `public/stackscan/my-project/`), handling name collisions automatically.
|
|
64
62
|
|
|
65
63
|
This will:
|
|
66
|
-
* Scan all projects in `stackscan/`.
|
|
64
|
+
* Scan all projects in `public/stackscan/`.
|
|
67
65
|
* Generate `stack.json` and `stack.md` inside each project folder.
|
|
68
66
|
* Copy logo assets to `public/assets/logos/`.
|
|
69
67
|
* Update your root `README.md` with a "My Projects" section.
|
|
@@ -83,9 +81,19 @@ npx stackscan scan --color brand
|
|
|
83
81
|
|
|
84
82
|
---
|
|
85
83
|
|
|
84
|
+
## Dependabot & Security
|
|
85
|
+
|
|
86
|
+
When hosting `package.json` files for analysis, security tools like Dependabot may incorrectly flag them as vulnerable dependencies of your project.
|
|
87
|
+
|
|
88
|
+
To prevent this, StackScan will **automatically rename** any `package.json` found in `public/stackscan/` to `_package.json`.
|
|
89
|
+
- Dependabot ignores `_package.json`.
|
|
90
|
+
- StackScan prioritizes reading `_package.json` on future runs.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
86
94
|
## Output
|
|
87
95
|
|
|
88
|
-
For each project in `stackscan/`, a `stack.json` is generated in the same folder.
|
|
96
|
+
For each project in `public/stackscan/`, a `stack.json` is generated in the same folder.
|
|
89
97
|
|
|
90
98
|
Example `stack.json`:
|
|
91
99
|
|
|
@@ -149,7 +157,7 @@ jobs:
|
|
|
149
157
|
|
|
150
158
|
---
|
|
151
159
|
|
|
152
|
-
## What
|
|
160
|
+
## What StackScan does *not* do
|
|
153
161
|
|
|
154
162
|
* ❌ It does not execute or analyze runtime code
|
|
155
163
|
* ❌ It does not attempt to infer architectural quality
|
package/dist/add.mjs
CHANGED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
techMap
|
|
3
|
+
} from "./chunk-HKCVFKM4.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 = "<!-- STACKSCAN_START -->";
|
|
200
|
+
const endMarker = "<!-- STACKSCAN_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
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
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/sync.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
|
+
async function sync(options = {}) {
|
|
24
|
+
console.log("\u{1F680} Starting Sync...");
|
|
25
|
+
if (options.color) {
|
|
26
|
+
console.log(`\u{1F3A8} Color mode: ${options.color}`);
|
|
27
|
+
}
|
|
28
|
+
if (!fs.existsSync(BASE_DIR)) {
|
|
29
|
+
console.log(`Creating stackscan directory at: ${BASE_DIR}`);
|
|
30
|
+
fs.mkdirSync(BASE_DIR, { recursive: true });
|
|
31
|
+
console.log('Please place your project folders inside "public/stackscan/" and run this command again.');
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
const entries = fs.readdirSync(BASE_DIR, { withFileTypes: true });
|
|
35
|
+
const projectDirs = entries.filter((dirent) => dirent.isDirectory() && dirent.name !== "input" && dirent.name !== "output");
|
|
36
|
+
if (projectDirs.length === 0) {
|
|
37
|
+
console.log('\u26A0\uFE0F No project directories found in "public/stackscan/".');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
console.log(`Found ${projectDirs.length} projects to process.
|
|
41
|
+
`);
|
|
42
|
+
const allProjects = [];
|
|
43
|
+
const allTechs = [];
|
|
44
|
+
for (const dir of projectDirs) {
|
|
45
|
+
const projectPath = path.join(BASE_DIR, dir.name);
|
|
46
|
+
const packageJsonPath = path.join(projectPath, "package.json");
|
|
47
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
48
|
+
try {
|
|
49
|
+
const content = fs.readFileSync(packageJsonPath, "utf-8");
|
|
50
|
+
const pkg = JSON.parse(content);
|
|
51
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
52
|
+
const detectedTechs = [];
|
|
53
|
+
Object.keys(allDeps).forEach((dep) => {
|
|
54
|
+
if (SKIPPED_TECHS.includes(dep)) return;
|
|
55
|
+
if (techMap[dep]) {
|
|
56
|
+
const tech = techMap[dep];
|
|
57
|
+
let color = null;
|
|
58
|
+
if (options.color === "white") color = "#FFFFFF";
|
|
59
|
+
else if (options.color === "black") color = "#000000";
|
|
60
|
+
else if (options.color && options.color.startsWith("#")) color = options.color;
|
|
61
|
+
else {
|
|
62
|
+
const depSlug = dep.toLowerCase();
|
|
63
|
+
const nameSlug = tech.name.toLowerCase();
|
|
64
|
+
const nameSlugNoSpaces = tech.name.toLowerCase().replace(/\s+/g, "");
|
|
65
|
+
const hex = simple_icons_hex_default[depSlug] || simple_icons_hex_default[nameSlug] || simple_icons_hex_default[nameSlugNoSpaces];
|
|
66
|
+
if (hex) color = `#${hex}`;
|
|
67
|
+
}
|
|
68
|
+
detectedTechs.push({
|
|
69
|
+
name: tech.name,
|
|
70
|
+
slug: dep,
|
|
71
|
+
logo: tech.logo,
|
|
72
|
+
// Raw path for resolution
|
|
73
|
+
color
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
let uniqueTechs = Array.from(new Set(detectedTechs.map((t) => t.slug))).map((slug) => detectedTechs.find((t) => t.slug === slug));
|
|
78
|
+
const seenLogos = /* @__PURE__ */ new Set();
|
|
79
|
+
uniqueTechs = uniqueTechs.filter((t) => {
|
|
80
|
+
if (seenLogos.has(t.logo)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
seenLogos.add(t.logo);
|
|
84
|
+
return true;
|
|
85
|
+
});
|
|
86
|
+
const assetsDir = path.join(process.cwd(), "public", "assets", "logos");
|
|
87
|
+
if (options.copyAssets !== false) {
|
|
88
|
+
await copyAssets(uniqueTechs, assetsDir, { colorMode: options.color });
|
|
89
|
+
}
|
|
90
|
+
const techsWithUrls = uniqueTechs.map((t) => ({
|
|
91
|
+
...t,
|
|
92
|
+
logo: `https://raw.githubusercontent.com/benjamindotdev/stackscan/main/public/assets/logos/${t.logo}`,
|
|
93
|
+
relativePath: `./public/assets/logos/${t.logo}`
|
|
94
|
+
}));
|
|
95
|
+
allTechs.push(...techsWithUrls);
|
|
96
|
+
fs.writeFileSync(
|
|
97
|
+
path.join(projectPath, "stack.json"),
|
|
98
|
+
JSON.stringify(techsWithUrls, null, 2)
|
|
99
|
+
);
|
|
100
|
+
const mdContent = generateMarkdown(techsWithUrls);
|
|
101
|
+
fs.writeFileSync(path.join(projectPath, "stack.md"), mdContent);
|
|
102
|
+
allProjects.push({
|
|
103
|
+
name: dir.name,
|
|
104
|
+
techs: techsWithUrls
|
|
105
|
+
});
|
|
106
|
+
console.log(`\u2705 ${dir.name.padEnd(20)} -> stack.json (${uniqueTechs.length} techs)`);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.error(`\u274C Error processing ${dir.name}:`, err.message);
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
console.warn(`\u26A0\uFE0F Skipping "${dir.name}": No package.json found.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (allProjects.length > 0) {
|
|
115
|
+
updateRootReadme(allProjects);
|
|
116
|
+
}
|
|
117
|
+
console.log("\n\u2728 Sync complete.");
|
|
118
|
+
}
|
|
119
|
+
function updateRootReadme(projects) {
|
|
120
|
+
const readmePath = path.join(process.cwd(), "README.md");
|
|
121
|
+
if (!fs.existsSync(readmePath)) {
|
|
122
|
+
console.log("\u26A0\uFE0F No root README.md found to update.");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
let readmeContent = fs.readFileSync(readmePath, "utf-8");
|
|
126
|
+
const startMarker = "<!-- STACKSCAN_START -->";
|
|
127
|
+
const endMarker = "<!-- STACKSCAN_END -->";
|
|
128
|
+
let newSection = `${startMarker}
|
|
129
|
+
## My Projects
|
|
130
|
+
|
|
131
|
+
`;
|
|
132
|
+
for (const p of projects) {
|
|
133
|
+
newSection += `### ${p.name}
|
|
134
|
+
`;
|
|
135
|
+
newSection += `<p>
|
|
136
|
+
`;
|
|
137
|
+
for (const t of p.techs) {
|
|
138
|
+
const src = t.relativePath || t.logo;
|
|
139
|
+
newSection += ` <img src="${src}" alt="${t.name}" height="25" style="margin-right: 10px;" />
|
|
140
|
+
`;
|
|
141
|
+
}
|
|
142
|
+
newSection += `</p>
|
|
143
|
+
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
newSection += `${endMarker}`;
|
|
147
|
+
if (readmeContent.includes(startMarker) && readmeContent.includes(endMarker)) {
|
|
148
|
+
const regex = new RegExp(`${startMarker}[\\s\\S]*?${endMarker}`);
|
|
149
|
+
readmeContent = readmeContent.replace(regex, newSection);
|
|
150
|
+
console.log(`\u{1F4DD} Updated root README.md with ${projects.length} projects.`);
|
|
151
|
+
} else {
|
|
152
|
+
readmeContent += `
|
|
153
|
+
|
|
154
|
+
${newSection}`;
|
|
155
|
+
console.log(`\u{1F4DD} Appended projects to root README.md.`);
|
|
156
|
+
}
|
|
157
|
+
fs.writeFileSync(readmePath, readmeContent);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export {
|
|
161
|
+
sync
|
|
162
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__esm
|
|
3
|
+
} from "./chunk-CFGUYUPP.mjs";
|
|
4
|
+
|
|
5
|
+
// node_modules/tsup/assets/esm_shims.js
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
var getFilename, getDirname, __dirname;
|
|
9
|
+
var init_esm_shims = __esm({
|
|
10
|
+
"node_modules/tsup/assets/esm_shims.js"() {
|
|
11
|
+
"use strict";
|
|
12
|
+
getFilename = () => fileURLToPath(import.meta.url);
|
|
13
|
+
getDirname = () => path.dirname(getFilename());
|
|
14
|
+
__dirname = /* @__PURE__ */ getDirname();
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
__dirname,
|
|
20
|
+
init_esm_shims
|
|
21
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
12
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
13
|
+
};
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
27
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
28
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
29
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
30
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
31
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
32
|
+
mod
|
|
33
|
+
));
|
|
34
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
__esm,
|
|
38
|
+
__commonJS,
|
|
39
|
+
__export,
|
|
40
|
+
__toESM,
|
|
41
|
+
__publicField
|
|
42
|
+
};
|