buddy-workbench 0.1.66 → 0.1.67

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-workbench",
3
- "version": "0.1.66",
3
+ "version": "0.1.67",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { dirname } from 'node:path';
3
3
  import { paths } from '../config.js';
4
4
 
5
- const defaults = { workspaceDir: '', repos: [], tasks: [] };
5
+ const defaults = { workspaceDir: '', repos: [], tasks: [], dependencyRepos: [] };
6
6
  export function readPackageUpgradeData() {
7
7
  try { return existsSync(paths.packageUpgrade) ? { ...defaults, ...JSON.parse(readFileSync(paths.packageUpgrade, 'utf8')) } : { ...defaults }; }
8
8
  catch { return { ...defaults }; }
@@ -1,8 +1,8 @@
1
1
  import https from 'node:https';
2
2
  import axios from 'axios';
3
3
  import { Router } from 'express';
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
5
- import { join, basename } from 'node:path';
4
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
5
+ import { join, basename, resolve } from 'node:path';
6
6
  import { execFile } from 'node:child_process';
7
7
  import { promisify } from 'node:util';
8
8
  import { readSettings } from '../repositories/settings.js';
@@ -20,6 +20,60 @@ const httpClient = axios.create({
20
20
 
21
21
  function repoName(url) { return basename(String(url || '').replace(/[\\/]$/, '').replace(/\.git$/i, '').replace(/[:/]+$/, '')) || 'repo'; }
22
22
  function repoPath(data, repo) { return join(data.workspaceDir, repo.directory || repoName(repo.url)); }
23
+ const dependencySections = ['dependencies', 'devDependencies'];
24
+ function workspaceRepositoryCandidates(workspaceDir) {
25
+ const root = resolve(workspaceDir);
26
+ if (!existsSync(root) || !statSync(root).isDirectory()) throw new Error(`Workspace directory does not exist: ${root}`);
27
+ const folders = [];
28
+ if (existsSync(join(root, '.git')) || existsSync(join(root, 'package.json'))) folders.push(root);
29
+ readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules').forEach((entry) => {
30
+ const folder = join(root, entry.name);
31
+ if (existsSync(join(folder, '.git')) || existsSync(join(folder, 'package.json'))) folders.push(folder);
32
+ });
33
+ return folders.map((folder) => {
34
+ const directory = basename(folder);
35
+ let name = directory;
36
+ try { name = JSON.parse(readFileSync(join(folder, 'package.json'), 'utf8')).name || directory; } catch {}
37
+ return { directory, name, path: folder };
38
+ });
39
+ }
40
+ function scanWorkspaceDependencies(workspaceDir, selectedDirectories = []) {
41
+ const root = resolve(workspaceDir);
42
+ if (!existsSync(root) || !statSync(root).isDirectory()) throw new Error(`Workspace directory does not exist: ${root}`);
43
+ const selected = new Set(selectedDirectories);
44
+ const folders = workspaceRepositoryCandidates(root)
45
+ .filter((item) => !selected.size || selected.has(item.directory))
46
+ .map((item) => item.path);
47
+ const repositories = [];
48
+ const packages = new Map();
49
+ const errors = [];
50
+ folders.forEach((folder) => {
51
+ const packageFile = join(folder, 'package.json');
52
+ if (!existsSync(packageFile)) return;
53
+ const directory = basename(folder);
54
+ try {
55
+ const packageJson = JSON.parse(readFileSync(packageFile, 'utf8'));
56
+ const repository = { name: packageJson.name || directory, directory, path: folder, packageCount: 0 };
57
+ dependencySections.forEach((section) => Object.entries(packageJson[section] || {}).forEach(([name, version]) => {
58
+ if (typeof version !== 'string') return;
59
+ repository.packageCount += 1;
60
+ if (!packages.has(name)) packages.set(name, new Map());
61
+ const versions = packages.get(name);
62
+ if (!versions.has(version)) versions.set(version, []);
63
+ const existing = versions.get(version).find((item) => item.directory === directory);
64
+ if (existing) existing.sections.push(section);
65
+ else versions.get(version).push({ repository: repository.name, directory, sections: [section] });
66
+ }));
67
+ repositories.push(repository);
68
+ } catch (error) { errors.push({ repository: directory, message: error.message || 'Invalid package.json' }); }
69
+ });
70
+ const packageRows = [...packages.entries()].map(([name, versions]) => ({
71
+ name,
72
+ repositoryCount: new Set([...versions.values()].flat().map((item) => item.directory)).size,
73
+ versions: [...versions.entries()].sort(([a], [b]) => a.localeCompare(b, undefined, { numeric: true })).map(([version, items]) => ({ version, repositories: items }))
74
+ })).sort((a, b) => b.repositoryCount - a.repositoryCount || a.name.localeCompare(b.name));
75
+ return { workspaceDir: root, scannedAt: new Date().toISOString(), repositories, packages: packageRows, errors };
76
+ }
23
77
  function parseBitbucket(url) {
24
78
  const raw = String(url || '').replace(/\.git$/, '');
25
79
  let u;
@@ -134,6 +188,31 @@ async function execute(task) {
134
188
  }
135
189
 
136
190
  router.get('/config', (_req, res) => { const data = readPackageUpgradeData(); res.json({ workspaceDir: data.workspaceDir, repos: data.repos.map((repo) => ({ ...repo, cloned: existsSync(join(repoPath(data, repo), '.git')) })), tasks: data.tasks }); });
191
+ router.get('/dependency-repositories', (_req, res) => {
192
+ const data = readPackageUpgradeData();
193
+ if (!data.workspaceDir) return res.status(400).json({ error: 'Configure a workspace directory in Package Upgrade first.' });
194
+ try {
195
+ const selected = new Set(data.dependencyRepos || []);
196
+ res.json({ workspaceDir: resolve(data.workspaceDir), repositories: workspaceRepositoryCandidates(data.workspaceDir).map((repo) => ({ ...repo, selected: !selected.size || selected.has(repo.directory) })) });
197
+ } catch (error) { res.status(400).json({ error: error.message }); }
198
+ });
199
+ router.put('/dependency-repositories', (req, res) => {
200
+ const data = readPackageUpgradeData();
201
+ if (!data.workspaceDir) return res.status(400).json({ error: 'Configure a workspace directory in Package Upgrade first.' });
202
+ try {
203
+ const available = new Set(workspaceRepositoryCandidates(data.workspaceDir).map((repo) => repo.directory));
204
+ data.dependencyRepos = [...new Set(Array.isArray(req.body?.directories) ? req.body.directories.map(String).filter((directory) => available.has(directory)) : [])];
205
+ savePackageUpgradeData(data);
206
+ res.json({ directories: data.dependencyRepos });
207
+ } catch (error) { res.status(400).json({ error: error.message }); }
208
+ });
209
+ router.get('/dependency-scan', (_req, res) => {
210
+ const data = readPackageUpgradeData();
211
+ if (!data.workspaceDir) return res.status(400).json({ error: 'Configure a workspace directory in Package Upgrade first.' });
212
+ try {
213
+ res.json(scanWorkspaceDependencies(data.workspaceDir, data.dependencyRepos));
214
+ } catch (error) { res.status(400).json({ error: error.message }); }
215
+ });
137
216
  router.put('/config', (req, res) => { const data = readPackageUpgradeData(); data.workspaceDir = String(req.body?.workspaceDir || '').trim(); if (!data.workspaceDir) return res.status(400).json({ error: 'Workspace directory is required.' }); savePackageUpgradeData(data); res.json({ workspaceDir: data.workspaceDir, repos: data.repos }); });
138
217
  router.post('/repos', (req, res) => { const url = String(req.body?.url || '').trim(); if (!url) return res.status(400).json({ error: 'Repository URL is required.' }); const data = readPackageUpgradeData(); const repo = { id: `repo-${Date.now()}`, url, name: String(req.body?.name || repoName(url)), directory: String(req.body?.directory || repoName(url)) }; data.repos.push(repo); savePackageUpgradeData(data); res.status(201).json(repo); });
139
218
  router.post('/repos/packages', (req, res) => {