@mono-labs/cli 0.0.203 → 0.0.205
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 +65 -189
- package/dist/project/build-mono-readme.js +272 -0
- package/dist/project/build-readme.js +2 -0
- package/dist/project/generate-docs.js +53 -0
- package/dist/project/generate-readme.js +311 -0
- package/dist/types/project/build-mono-readme.d.ts +1 -0
- package/dist/types/project/build-readme.d.ts +2 -0
- package/dist/types/project/generate-docs.d.ts +11 -0
- package/dist/types/project/generate-readme.d.ts +1 -0
- package/dist/types/project/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/project/build-mono-readme.ts +399 -0
- package/src/project/build-readme.ts +2 -0
- package/src/project/generate-docs.ts +70 -0
- package/src/project/generate-readme.ts +376 -0
- package/src/project/index.ts +2 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
// scripts/generate-repo-help.mjs
|
|
2
|
+
// Generates a developer-friendly workspace command reference.
|
|
3
|
+
//
|
|
4
|
+
// Output: docs/workspaces.md
|
|
5
|
+
//
|
|
6
|
+
// Run (from repo root):
|
|
7
|
+
// node ./scripts/generate-repo-help.mjs
|
|
8
|
+
//
|
|
9
|
+
// Philosophy:
|
|
10
|
+
// - Optimize for onboarding and day-to-day use
|
|
11
|
+
// - Keep raw yarn workspace commands for reference
|
|
12
|
+
// - Emphasize `yarn mono` as the primary interface
|
|
13
|
+
import { promises as fs } from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { generateDocsIndex } from './generate-docs.js';
|
|
16
|
+
// ----------------- config -----------------
|
|
17
|
+
// Always use the working directory as the root for all file actions
|
|
18
|
+
const REPO_ROOT = path.resolve(process.cwd());
|
|
19
|
+
const ROOT_PKG_JSON = path.join(REPO_ROOT, 'package.json');
|
|
20
|
+
const OUTPUT_PATH = path.join(REPO_ROOT, 'docs', 'workspaces.md');
|
|
21
|
+
// ----------------- helpers -----------------
|
|
22
|
+
async function exists(p) {
|
|
23
|
+
// Always resolve path relative to working directory
|
|
24
|
+
const absPath = path.resolve(process.cwd(), p);
|
|
25
|
+
try {
|
|
26
|
+
await fs.access(absPath);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function isObject(v) {
|
|
34
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
35
|
+
}
|
|
36
|
+
function toPosix(p) {
|
|
37
|
+
return p.split(path.sep).join('/');
|
|
38
|
+
}
|
|
39
|
+
function mdEscapeInline(s) {
|
|
40
|
+
return String(s ?? '').replaceAll('`', '\`');
|
|
41
|
+
}
|
|
42
|
+
function slugifyForGithubAnchor(title) {
|
|
43
|
+
return String(title ?? '')
|
|
44
|
+
.trim()
|
|
45
|
+
.toLowerCase()
|
|
46
|
+
.replace(/[^\w\s-]/g, '')
|
|
47
|
+
.replace(/\s+/g, '-')
|
|
48
|
+
.replace(/-+/g, '-');
|
|
49
|
+
}
|
|
50
|
+
async function readJson(filePath) {
|
|
51
|
+
// Always resolve filePath relative to working directory
|
|
52
|
+
const absPath = path.resolve(process.cwd(), filePath);
|
|
53
|
+
const raw = await fs.readFile(absPath, 'utf8');
|
|
54
|
+
return JSON.parse(raw);
|
|
55
|
+
}
|
|
56
|
+
function normalizeWorkspacePatterns(workspacesField) {
|
|
57
|
+
if (Array.isArray(workspacesField))
|
|
58
|
+
return workspacesField;
|
|
59
|
+
if (isObject(workspacesField) &&
|
|
60
|
+
Array.isArray(workspacesField.packages))
|
|
61
|
+
return workspacesField.packages;
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
// ----------------- glob expansion -----------------
|
|
65
|
+
function matchSegment(patternSeg, name) {
|
|
66
|
+
if (patternSeg === '*')
|
|
67
|
+
return true;
|
|
68
|
+
if (!patternSeg.includes('*'))
|
|
69
|
+
return patternSeg === name;
|
|
70
|
+
const escaped = patternSeg.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
71
|
+
const regex = new RegExp('^' + escaped.replaceAll('*', '.*') + '$');
|
|
72
|
+
return regex.test(name);
|
|
73
|
+
}
|
|
74
|
+
async function expandWorkspacePattern(root, pattern) {
|
|
75
|
+
const segs = toPosix(pattern).split('/').filter(Boolean);
|
|
76
|
+
async function expandFrom(dir, segIndex) {
|
|
77
|
+
// Always resolve dir relative to working directory
|
|
78
|
+
const absDir = path.resolve(process.cwd(), dir);
|
|
79
|
+
if (segIndex >= segs.length)
|
|
80
|
+
return [absDir];
|
|
81
|
+
const seg = segs[segIndex];
|
|
82
|
+
if (seg === '**') {
|
|
83
|
+
const results = [];
|
|
84
|
+
results.push(...(await expandFrom(absDir, segIndex + 1)));
|
|
85
|
+
const entries = await fs
|
|
86
|
+
.readdir(absDir, { withFileTypes: true })
|
|
87
|
+
.catch(() => []);
|
|
88
|
+
for (const e of entries) {
|
|
89
|
+
if (e.isDirectory()) {
|
|
90
|
+
results.push(...(await expandFrom(path.join(absDir, e.name), segIndex)));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
95
|
+
const entries = await fs
|
|
96
|
+
.readdir(absDir, { withFileTypes: true })
|
|
97
|
+
.catch(() => []);
|
|
98
|
+
const results = [];
|
|
99
|
+
for (const e of entries) {
|
|
100
|
+
if (e.isDirectory() && matchSegment(seg, e.name)) {
|
|
101
|
+
results.push(...(await expandFrom(path.join(absDir, e.name), segIndex + 1)));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return results;
|
|
105
|
+
}
|
|
106
|
+
return [...new Set(await expandFrom(root, 0))];
|
|
107
|
+
}
|
|
108
|
+
async function findWorkspaceRoots(repoRoot, workspacePatterns) {
|
|
109
|
+
const roots = [];
|
|
110
|
+
for (const pat of workspacePatterns) {
|
|
111
|
+
const expandedDirs = await expandWorkspacePattern(repoRoot, pat);
|
|
112
|
+
roots.push(...expandedDirs);
|
|
113
|
+
}
|
|
114
|
+
return [...new Set(roots)];
|
|
115
|
+
}
|
|
116
|
+
// ----------------- package discovery -----------------
|
|
117
|
+
const SKIP_DIRS = new Set([
|
|
118
|
+
'node_modules',
|
|
119
|
+
'.git',
|
|
120
|
+
'.next',
|
|
121
|
+
'dist',
|
|
122
|
+
'build',
|
|
123
|
+
'out',
|
|
124
|
+
'coverage',
|
|
125
|
+
'.turbo',
|
|
126
|
+
]);
|
|
127
|
+
async function findPackageJsonFilesRecursive(startDir) {
|
|
128
|
+
const found = [];
|
|
129
|
+
async function walk(dir) {
|
|
130
|
+
// Always resolve dir relative to working directory
|
|
131
|
+
const absDir = path.resolve(process.cwd(), dir);
|
|
132
|
+
const entries = await fs
|
|
133
|
+
.readdir(absDir, { withFileTypes: true })
|
|
134
|
+
.catch(() => []);
|
|
135
|
+
for (const e of entries) {
|
|
136
|
+
const full = path.join(absDir, e.name);
|
|
137
|
+
if (e.isDirectory()) {
|
|
138
|
+
if (!SKIP_DIRS.has(e.name))
|
|
139
|
+
await walk(full);
|
|
140
|
+
}
|
|
141
|
+
else if (e.isFile() && e.name === 'package.json') {
|
|
142
|
+
found.push(full);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
await walk(startDir);
|
|
147
|
+
return found;
|
|
148
|
+
}
|
|
149
|
+
async function collectPackagesFromWorkspaceRoots(workspaceRoots) {
|
|
150
|
+
const pkgJsonFiles = [];
|
|
151
|
+
for (const root of workspaceRoots) {
|
|
152
|
+
if (await exists(root)) {
|
|
153
|
+
pkgJsonFiles.push(...(await findPackageJsonFilesRecursive(root)));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const packages = [];
|
|
157
|
+
for (const file of [...new Set(pkgJsonFiles)]) {
|
|
158
|
+
if (path.resolve(file) === path.resolve(ROOT_PKG_JSON))
|
|
159
|
+
continue;
|
|
160
|
+
try {
|
|
161
|
+
const pj = await readJson(file);
|
|
162
|
+
const dir = path.dirname(file);
|
|
163
|
+
packages.push({
|
|
164
|
+
name: pj.name || toPosix(path.relative(REPO_ROOT, dir)),
|
|
165
|
+
dir,
|
|
166
|
+
scripts: isObject(pj.scripts) ? pj.scripts : {},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch { }
|
|
170
|
+
}
|
|
171
|
+
const seen = new Set();
|
|
172
|
+
return packages
|
|
173
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
174
|
+
.filter((p) => (seen.has(p.name) ? false : seen.add(p.name)));
|
|
175
|
+
}
|
|
176
|
+
// ----------------- classification -----------------
|
|
177
|
+
function classifyPackage(pkg) {
|
|
178
|
+
const p = toPosix(pkg.dir);
|
|
179
|
+
if (p.startsWith('apps/'))
|
|
180
|
+
return 'Apps';
|
|
181
|
+
if (p.startsWith('packages/'))
|
|
182
|
+
return 'Libraries';
|
|
183
|
+
return 'Other';
|
|
184
|
+
}
|
|
185
|
+
// ----------------- markdown generation -----------------
|
|
186
|
+
function formatQuickStart(pkgMgr) {
|
|
187
|
+
return [
|
|
188
|
+
'# 🗂️ Workspace Overview',
|
|
189
|
+
'',
|
|
190
|
+
'This document explains how to run and discover commands in this monorepo.',
|
|
191
|
+
'',
|
|
192
|
+
'---',
|
|
193
|
+
'',
|
|
194
|
+
'## 🚀 Quick Start',
|
|
195
|
+
'',
|
|
196
|
+
'Most developers only need the following:',
|
|
197
|
+
'',
|
|
198
|
+
'```bash',
|
|
199
|
+
`${pkgMgr} dev`,
|
|
200
|
+
`${pkgMgr} serve`,
|
|
201
|
+
`${pkgMgr} mobile`,
|
|
202
|
+
`${pkgMgr} help`,
|
|
203
|
+
'```',
|
|
204
|
+
'',
|
|
205
|
+
'Use `yarn mono` whenever possible. It handles environment setup,',
|
|
206
|
+
'workspace routing, and service coordination automatically.',
|
|
207
|
+
'',
|
|
208
|
+
'---',
|
|
209
|
+
'',
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
function formatReferenceIntro() {
|
|
213
|
+
return [
|
|
214
|
+
'## 📖 Reference',
|
|
215
|
+
'',
|
|
216
|
+
'This section lists all workspace packages and their available scripts.',
|
|
217
|
+
'',
|
|
218
|
+
'Use this when:',
|
|
219
|
+
'- Debugging',
|
|
220
|
+
'- Working on internal libraries',
|
|
221
|
+
'- Running CI or low-level tooling',
|
|
222
|
+
'',
|
|
223
|
+
];
|
|
224
|
+
}
|
|
225
|
+
function formatIndex(packages) {
|
|
226
|
+
const groups = {};
|
|
227
|
+
for (const p of packages) {
|
|
228
|
+
const g = classifyPackage(p);
|
|
229
|
+
groups[g] ||= [];
|
|
230
|
+
groups[g].push(p);
|
|
231
|
+
}
|
|
232
|
+
const lines = ['## Workspace Index', ''];
|
|
233
|
+
for (const group of Object.keys(groups)) {
|
|
234
|
+
lines.push(`### ${group}`);
|
|
235
|
+
lines.push('');
|
|
236
|
+
for (const p of groups[group]) {
|
|
237
|
+
lines.push(`- [\`${mdEscapeInline(p.name)}\`](#${slugifyForGithubAnchor(p.name)})`);
|
|
238
|
+
}
|
|
239
|
+
lines.push('');
|
|
240
|
+
}
|
|
241
|
+
return lines;
|
|
242
|
+
}
|
|
243
|
+
function formatPackages(packages) {
|
|
244
|
+
const lines = [];
|
|
245
|
+
for (const p of packages) {
|
|
246
|
+
lines.push(`### ${p.name}`);
|
|
247
|
+
lines.push('');
|
|
248
|
+
lines.push(`_Location: \`${toPosix(path.relative(REPO_ROOT, p.dir))}\`_`);
|
|
249
|
+
lines.push('');
|
|
250
|
+
const scripts = Object.keys(p.scripts).sort();
|
|
251
|
+
if (!scripts.length) {
|
|
252
|
+
lines.push('_No scripts defined._');
|
|
253
|
+
lines.push('');
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
lines.push('| Script | Recommended |');
|
|
257
|
+
lines.push('|------|-------------|');
|
|
258
|
+
for (const s of scripts) {
|
|
259
|
+
lines.push(`| \`${s}\` | \`yarn mono ${p.name} ${s}\` |`);
|
|
260
|
+
}
|
|
261
|
+
lines.push('');
|
|
262
|
+
lines.push('<details>');
|
|
263
|
+
lines.push('<summary>Raw yarn workspace commands</summary>');
|
|
264
|
+
lines.push('');
|
|
265
|
+
for (const s of scripts) {
|
|
266
|
+
lines.push(`- \`yarn workspace ${p.name} ${s}\``);
|
|
267
|
+
}
|
|
268
|
+
lines.push('</details>');
|
|
269
|
+
lines.push('');
|
|
270
|
+
}
|
|
271
|
+
return lines;
|
|
272
|
+
}
|
|
273
|
+
async function ensureParentDir(filePath) {
|
|
274
|
+
// Always resolve parent dir relative to working directory
|
|
275
|
+
const dir = path.resolve(process.cwd(), path.dirname(filePath));
|
|
276
|
+
await fs.mkdir(dir, { recursive: true });
|
|
277
|
+
}
|
|
278
|
+
// ----------------- main -----------------
|
|
279
|
+
async function main() {
|
|
280
|
+
// Always resolve all paths relative to working directory
|
|
281
|
+
if (!(await exists(ROOT_PKG_JSON))) {
|
|
282
|
+
throw new Error('Root package.json not found');
|
|
283
|
+
}
|
|
284
|
+
const rootPkg = await readJson(ROOT_PKG_JSON);
|
|
285
|
+
const workspacePatterns = normalizeWorkspacePatterns(rootPkg.workspaces);
|
|
286
|
+
const pkgMgr = `${(rootPkg.packageManager || 'yarn').split('@')[0]} mono`;
|
|
287
|
+
const workspaceRoots = await findWorkspaceRoots(REPO_ROOT, workspacePatterns);
|
|
288
|
+
const fallbackRoots = ['packages', 'apps'].map((p) => path.join(REPO_ROOT, p));
|
|
289
|
+
const roots = workspaceRoots.length ? workspaceRoots : fallbackRoots;
|
|
290
|
+
const packages = await collectPackagesFromWorkspaceRoots(roots);
|
|
291
|
+
const lines = [];
|
|
292
|
+
lines.push(...formatQuickStart(pkgMgr));
|
|
293
|
+
lines.push(...formatReferenceIntro());
|
|
294
|
+
lines.push(...formatIndex(packages));
|
|
295
|
+
lines.push('## Packages');
|
|
296
|
+
lines.push('');
|
|
297
|
+
lines.push(...formatPackages(packages));
|
|
298
|
+
const val = await generateDocsIndex({
|
|
299
|
+
docsDir: path.join(REPO_ROOT, 'docs'),
|
|
300
|
+
excludeFile: 'workspaces.md',
|
|
301
|
+
});
|
|
302
|
+
val.split('\n').forEach((line) => lines.push(line));
|
|
303
|
+
await ensureParentDir(OUTPUT_PATH);
|
|
304
|
+
await fs.writeFile(OUTPUT_PATH, lines.join('\n'), 'utf8');
|
|
305
|
+
console.log(`✅ Generated ${OUTPUT_PATH}`);
|
|
306
|
+
console.log(`📦 Packages found: ${packages.length}`);
|
|
307
|
+
}
|
|
308
|
+
main().catch((err) => {
|
|
309
|
+
console.error(err);
|
|
310
|
+
process.exitCode = 1;
|
|
311
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface GenerateDocsIndexOptions {
|
|
2
|
+
docsDir: string;
|
|
3
|
+
excludeFile?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Generate a docs index from markdown files.
|
|
7
|
+
*
|
|
8
|
+
* @param options - Options for docs index generation
|
|
9
|
+
* @returns Markdown-formatted index
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateDocsIndex({ docsDir, excludeFile, }: GenerateDocsIndexOptions): Promise<string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|