@mono-labs/cli 0.0.207 → 0.0.209
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.
|
@@ -1,182 +1,159 @@
|
|
|
1
1
|
// scripts/generate-readme.mjs
|
|
2
2
|
// Node >= 18 recommended
|
|
3
|
-
|
|
4
|
-
import {
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
|
+
import { Dirent } from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { generateDocsIndex } from './generate-docs.js';
|
|
7
7
|
|
|
8
|
-
/* -------------------------------------------------------------------------- */
|
|
9
|
-
/* Types */
|
|
10
|
-
/* -------------------------------------------------------------------------- */
|
|
11
|
-
|
|
12
|
-
type MonoConfig = {
|
|
13
|
-
path: string;
|
|
14
|
-
config: {
|
|
15
|
-
envMap?: string[];
|
|
16
|
-
prodFlag?: string;
|
|
17
|
-
workspace?: {
|
|
18
|
-
packageMaps?: Record<string, string>;
|
|
19
|
-
preactions?: string[];
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
type MonoCommand = {
|
|
25
|
-
name: string;
|
|
26
|
-
file: string;
|
|
27
|
-
json: {
|
|
28
|
-
description?: string;
|
|
29
|
-
argument?: {
|
|
30
|
-
type?: string;
|
|
31
|
-
description?: string;
|
|
32
|
-
default?: unknown;
|
|
33
|
-
required?: boolean;
|
|
34
|
-
};
|
|
35
|
-
options?: Record<string, unknown>;
|
|
36
|
-
environments?: Record<string, Record<string, string>>;
|
|
37
|
-
preactions?: string[];
|
|
38
|
-
actions?: string[];
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
type PackageInfo = {
|
|
43
|
-
name: string;
|
|
44
|
-
dir: string;
|
|
45
|
-
scripts: Record<string, string>;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
type OptionSchema = {
|
|
49
|
-
key: string;
|
|
50
|
-
kind: 'boolean' | 'value';
|
|
51
|
-
type: string;
|
|
52
|
-
description: string;
|
|
53
|
-
shortcut: string;
|
|
54
|
-
default: unknown;
|
|
55
|
-
allowed: string[] | null;
|
|
56
|
-
allowAll: boolean;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/* -------------------------------------------------------------------------- */
|
|
60
|
-
/* Constants */
|
|
61
|
-
/* -------------------------------------------------------------------------- */
|
|
62
|
-
|
|
63
8
|
const REPO_ROOT = path.resolve(process.cwd());
|
|
64
9
|
const MONO_DIR = path.join(REPO_ROOT, '.mono');
|
|
65
10
|
const ROOT_PKG_JSON = path.join(REPO_ROOT, 'package.json');
|
|
66
11
|
const OUTPUT_PATH = path.join(REPO_ROOT, 'docs');
|
|
67
12
|
const OUTPUT_README = path.join(OUTPUT_PATH, 'command-line.md');
|
|
68
13
|
|
|
69
|
-
/* -------------------------------------------------------------------------- */
|
|
70
|
-
/* Utils */
|
|
71
|
-
/* -------------------------------------------------------------------------- */
|
|
72
|
-
|
|
73
14
|
async function ensureParentDir(filePath: string): Promise<void> {
|
|
74
|
-
|
|
15
|
+
const dir = path.dirname(filePath);
|
|
16
|
+
console.log(`[ensureParentDir] Ensuring directory:`, dir);
|
|
17
|
+
await fs.mkdir(dir, { recursive: true });
|
|
75
18
|
}
|
|
76
19
|
|
|
20
|
+
// ---------- utils ----------
|
|
77
21
|
async function exists(p: string): Promise<boolean> {
|
|
78
22
|
try {
|
|
79
23
|
await fs.access(p);
|
|
24
|
+
// Log existence check
|
|
25
|
+
console.log(`[exists] Path exists:`, p);
|
|
80
26
|
return true;
|
|
81
27
|
} catch {
|
|
28
|
+
console.log(`[exists] Path does NOT exist:`, p);
|
|
82
29
|
return false;
|
|
83
30
|
}
|
|
84
31
|
}
|
|
85
|
-
|
|
86
32
|
function isObject(v: unknown): v is Record<string, unknown> {
|
|
87
|
-
return typeof v === 'object' &&
|
|
33
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
88
34
|
}
|
|
89
|
-
|
|
90
35
|
function toPosix(p: string): string {
|
|
91
36
|
return p.split(path.sep).join('/');
|
|
92
37
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
38
|
+
async function readJson<T = any>(filePath: string): Promise<T> {
|
|
39
|
+
console.log(`[readJson] Reading JSON file:`, filePath);
|
|
40
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(raw);
|
|
43
|
+
console.log(`[readJson] Successfully parsed:`, filePath);
|
|
44
|
+
return parsed;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error(`[readJson] Failed to parse JSON:`, filePath, err);
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
96
49
|
}
|
|
97
|
-
|
|
98
50
|
async function listDir(dir: string): Promise<Dirent[]> {
|
|
99
|
-
|
|
51
|
+
console.log(`[listDir] Listing directory:`, dir);
|
|
52
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
53
|
+
console.log(`[listDir] Found ${entries.length} entries in:`, dir);
|
|
54
|
+
return entries;
|
|
100
55
|
}
|
|
101
|
-
|
|
102
56
|
function normalizeWorkspacePatterns(workspacesField: unknown): string[] {
|
|
103
|
-
if (Array.isArray(workspacesField)) return workspacesField;
|
|
57
|
+
if (Array.isArray(workspacesField)) return workspacesField as string[];
|
|
104
58
|
if (
|
|
105
59
|
isObject(workspacesField) &&
|
|
106
|
-
Array.isArray((workspacesField as
|
|
107
|
-
)
|
|
108
|
-
return (workspacesField as
|
|
109
|
-
}
|
|
60
|
+
Array.isArray((workspacesField as any).packages)
|
|
61
|
+
)
|
|
62
|
+
return (workspacesField as any).packages;
|
|
110
63
|
return [];
|
|
111
64
|
}
|
|
112
|
-
|
|
113
65
|
function mdEscapeInline(s: string): string {
|
|
114
|
-
return s.replaceAll('`', '
|
|
66
|
+
return String(s ?? '').replaceAll('`', '\`');
|
|
115
67
|
}
|
|
116
|
-
|
|
117
68
|
function indentLines(s: string, spaces = 2): string {
|
|
118
69
|
const pad = ' '.repeat(spaces);
|
|
119
|
-
return s
|
|
70
|
+
return String(s ?? '')
|
|
120
71
|
.split('\n')
|
|
121
72
|
.map((l) => pad + l)
|
|
122
73
|
.join('\n');
|
|
123
74
|
}
|
|
124
75
|
|
|
125
|
-
|
|
126
|
-
/* Workspace globbing */
|
|
127
|
-
/* -------------------------------------------------------------------------- */
|
|
128
|
-
|
|
76
|
+
// ---------- workspace glob matching (supports *, **, and plain segments) ----------
|
|
129
77
|
function matchSegment(patternSeg: string, name: string): boolean {
|
|
130
78
|
if (patternSeg === '*') return true;
|
|
131
79
|
if (!patternSeg.includes('*')) return patternSeg === name;
|
|
132
80
|
const escaped = patternSeg.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
133
|
-
|
|
81
|
+
const regex = new RegExp('^' + escaped.replaceAll('*', '.*') + '$');
|
|
82
|
+
return regex.test(name);
|
|
134
83
|
}
|
|
135
84
|
|
|
136
85
|
async function expandWorkspacePattern(
|
|
137
86
|
root: string,
|
|
138
87
|
pattern: string
|
|
139
88
|
): Promise<string[]> {
|
|
89
|
+
console.log(
|
|
90
|
+
`[expandWorkspacePattern] Expanding pattern:`,
|
|
91
|
+
pattern,
|
|
92
|
+
`from root:`,
|
|
93
|
+
root
|
|
94
|
+
);
|
|
140
95
|
const segs = toPosix(pattern).split('/').filter(Boolean);
|
|
141
96
|
|
|
142
|
-
async function expandFrom(dir: string,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
.catch(() => []);
|
|
148
|
-
|
|
149
|
-
const seg = segs[idx];
|
|
97
|
+
async function expandFrom(dir: string, segIndex: number): Promise<string[]> {
|
|
98
|
+
console.log(`[expandFrom] Directory:`, dir, `Segment index:`, segIndex);
|
|
99
|
+
if (segIndex >= segs.length) return [dir];
|
|
100
|
+
const seg = segs[segIndex];
|
|
101
|
+
console.log(`[expandFrom] Segment:`, seg);
|
|
150
102
|
|
|
151
103
|
if (seg === '**') {
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
104
|
+
const results: string[] = [];
|
|
105
|
+
results.push(...(await expandFrom(dir, segIndex + 1)));
|
|
106
|
+
const entries = await fs
|
|
107
|
+
.readdir(dir, { withFileTypes: true })
|
|
108
|
+
.catch(() => []);
|
|
109
|
+
console.log(
|
|
110
|
+
`[expandFrom] '**' entries in ${dir}:`,
|
|
111
|
+
entries.map((e) => e.name)
|
|
156
112
|
);
|
|
157
|
-
|
|
158
|
-
|
|
113
|
+
for (const e of entries) {
|
|
114
|
+
if (!e.isDirectory()) continue;
|
|
115
|
+
console.log(
|
|
116
|
+
`[expandFrom] Recursing into subdir:`,
|
|
117
|
+
path.join(dir, e.name)
|
|
118
|
+
);
|
|
119
|
+
results.push(...(await expandFrom(path.join(dir, e.name), segIndex)));
|
|
120
|
+
}
|
|
121
|
+
return results;
|
|
159
122
|
}
|
|
160
123
|
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
124
|
+
const entries = await fs
|
|
125
|
+
.readdir(dir, { withFileTypes: true })
|
|
126
|
+
.catch(() => []);
|
|
127
|
+
console.log(
|
|
128
|
+
`[expandFrom] Entries in ${dir}:`,
|
|
129
|
+
entries.map((e) => e.name)
|
|
165
130
|
);
|
|
166
|
-
|
|
167
|
-
|
|
131
|
+
const results: string[] = [];
|
|
132
|
+
for (const e of entries) {
|
|
133
|
+
if (!e.isDirectory()) continue;
|
|
134
|
+
if (!matchSegment(seg, e.name)) continue;
|
|
135
|
+
console.log(
|
|
136
|
+
`[expandFrom] Matched segment '${seg}' with directory:`,
|
|
137
|
+
e.name
|
|
138
|
+
);
|
|
139
|
+
results.push(...(await expandFrom(path.join(dir, e.name), segIndex + 1)));
|
|
140
|
+
}
|
|
141
|
+
return results;
|
|
168
142
|
}
|
|
169
143
|
|
|
170
144
|
const dirs = await expandFrom(root, 0);
|
|
171
|
-
|
|
172
|
-
const pkgDirs =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
145
|
+
console.log(`[expandWorkspacePattern] Expanded directories:`, dirs);
|
|
146
|
+
const pkgDirs: string[] = [];
|
|
147
|
+
for (const d of dirs) {
|
|
148
|
+
const pkgPath = path.join(d, 'package.json');
|
|
149
|
+
if (await exists(pkgPath)) {
|
|
150
|
+
console.log(`[expandWorkspacePattern] Found package.json:`, pkgPath);
|
|
151
|
+
pkgDirs.push(d);
|
|
152
|
+
} else {
|
|
153
|
+
console.log(`[expandWorkspacePattern] No package.json in:`, d);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
console.log(`[expandWorkspacePattern] Final package directories:`, pkgDirs);
|
|
180
157
|
return [...new Set(pkgDirs)];
|
|
181
158
|
}
|
|
182
159
|
|
|
@@ -184,23 +161,46 @@ async function findWorkspacePackageDirs(
|
|
|
184
161
|
repoRoot: string,
|
|
185
162
|
workspacePatterns: string[]
|
|
186
163
|
): Promise<string[]> {
|
|
187
|
-
|
|
188
|
-
|
|
164
|
+
console.log(
|
|
165
|
+
`[findWorkspacePackageDirs] repoRoot:`,
|
|
166
|
+
repoRoot,
|
|
167
|
+
`workspacePatterns:`,
|
|
168
|
+
workspacePatterns
|
|
189
169
|
);
|
|
190
|
-
|
|
170
|
+
const dirs: string[] = [];
|
|
171
|
+
for (const pat of workspacePatterns) {
|
|
172
|
+
console.log(`[findWorkspacePackageDirs] Expanding pattern:`, pat);
|
|
173
|
+
const expanded = await expandWorkspacePattern(repoRoot, pat);
|
|
174
|
+
console.log(
|
|
175
|
+
`[findWorkspacePackageDirs] Expanded dirs for pattern '${pat}':`,
|
|
176
|
+
expanded
|
|
177
|
+
);
|
|
178
|
+
dirs.push(...expanded);
|
|
179
|
+
}
|
|
180
|
+
const uniqueDirs = [...new Set(dirs)];
|
|
181
|
+
console.log(
|
|
182
|
+
`[findWorkspacePackageDirs] Final unique package dirs:`,
|
|
183
|
+
uniqueDirs
|
|
184
|
+
);
|
|
185
|
+
return uniqueDirs;
|
|
191
186
|
}
|
|
192
187
|
|
|
193
|
-
|
|
194
|
-
/* Mono config + commands */
|
|
195
|
-
/* -------------------------------------------------------------------------- */
|
|
196
|
-
|
|
188
|
+
// ---------- .mono parsing ----------
|
|
197
189
|
async function readMonoConfig(): Promise<MonoConfig | null> {
|
|
198
190
|
const configPath = path.join(MONO_DIR, 'config.json');
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
191
|
+
console.log(`[readMonoConfig] Looking for mono config at:`, configPath);
|
|
192
|
+
if (!(await exists(configPath))) {
|
|
193
|
+
console.log(`[readMonoConfig] No mono config found.`);
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const config = await readJson<any>(configPath);
|
|
198
|
+
console.log(`[readMonoConfig] Loaded mono config.`);
|
|
199
|
+
return { path: configPath, config };
|
|
200
|
+
} catch (err) {
|
|
201
|
+
console.error(`[readMonoConfig] Failed to load mono config:`, err);
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
function commandNameFromFile(filePath: string): string {
|
|
@@ -208,68 +208,390 @@ function commandNameFromFile(filePath: string): string {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
async function readMonoCommands(): Promise<MonoCommand[]> {
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
console.log(`[readMonoCommands] Reading mono commands from:`, MONO_DIR);
|
|
212
|
+
if (!(await exists(MONO_DIR))) {
|
|
213
|
+
console.log(`[readMonoCommands] Mono directory does not exist.`);
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
213
216
|
const entries = await listDir(MONO_DIR);
|
|
214
217
|
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
218
|
+
const jsonFiles = entries
|
|
219
|
+
.filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.json'))
|
|
220
|
+
.map((e) => path.join(MONO_DIR, e.name))
|
|
221
|
+
.filter((p) => path.basename(p).toLowerCase() !== 'config.json');
|
|
222
|
+
|
|
223
|
+
console.log(`[readMonoCommands] Found JSON files:`, jsonFiles);
|
|
224
|
+
const commands: MonoCommand[] = [];
|
|
225
|
+
for (const file of jsonFiles) {
|
|
226
|
+
try {
|
|
227
|
+
console.log(`[readMonoCommands] Reading command file:`, file);
|
|
228
|
+
const j = await readJson<any>(file);
|
|
229
|
+
commands.push({
|
|
230
|
+
name: commandNameFromFile(file),
|
|
231
|
+
file,
|
|
232
|
+
json: j,
|
|
233
|
+
});
|
|
234
|
+
console.log(
|
|
235
|
+
`[readMonoCommands] Successfully loaded command:`,
|
|
236
|
+
commandNameFromFile(file)
|
|
237
|
+
);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
console.error(
|
|
240
|
+
`[readMonoCommands] Failed to load command file:`,
|
|
241
|
+
file,
|
|
242
|
+
err
|
|
243
|
+
);
|
|
244
|
+
// skip invalid json
|
|
245
|
+
}
|
|
246
|
+
}
|
|
231
247
|
|
|
232
|
-
|
|
248
|
+
commands.sort((a, b) => a.name.localeCompare(b.name));
|
|
249
|
+
console.log(
|
|
250
|
+
`[readMonoCommands] Final sorted commands:`,
|
|
251
|
+
commands.map((c) => c.name)
|
|
252
|
+
);
|
|
253
|
+
return commands;
|
|
233
254
|
}
|
|
234
255
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
256
|
+
// ---------- mono docs formatting ----------
|
|
257
|
+
type OptionSchema = {
|
|
258
|
+
key: string;
|
|
259
|
+
kind: 'boolean' | 'value';
|
|
260
|
+
type: string;
|
|
261
|
+
description: string;
|
|
262
|
+
shortcut: string;
|
|
263
|
+
default: any;
|
|
264
|
+
allowed: string[] | null;
|
|
265
|
+
allowAll: boolean;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
type MonoConfig = {
|
|
269
|
+
path: string;
|
|
270
|
+
config: any;
|
|
271
|
+
};
|
|
238
272
|
|
|
239
273
|
function parseOptionsSchema(optionsObj: unknown): OptionSchema[] {
|
|
274
|
+
// New structure supports:
|
|
275
|
+
// - optionKey: { type: "string", default, options: [], allowAll, shortcut, description }
|
|
276
|
+
// - boolean toggle: { shortcut, description } (no type)
|
|
240
277
|
if (!isObject(optionsObj)) return [];
|
|
241
278
|
|
|
242
|
-
|
|
243
|
-
|
|
279
|
+
const entries: OptionSchema[] = Object.entries(optionsObj).map(
|
|
280
|
+
([key, raw]) => {
|
|
244
281
|
const o = isObject(raw) ? raw : {};
|
|
245
|
-
const hasType = typeof o.type === 'string';
|
|
246
|
-
|
|
282
|
+
const hasType = typeof o.type === 'string' && o.type.trim().length > 0;
|
|
283
|
+
const isBoolToggle = !hasType; // in your examples, booleans omit `type`
|
|
247
284
|
return {
|
|
248
285
|
key,
|
|
249
|
-
kind:
|
|
250
|
-
type: hasType ? (o.type
|
|
286
|
+
kind: isBoolToggle ? 'boolean' : 'value',
|
|
287
|
+
type: hasType ? String(o.type) : 'boolean',
|
|
251
288
|
description: typeof o.description === 'string' ? o.description : '',
|
|
252
289
|
shortcut: typeof o.shortcut === 'string' ? o.shortcut : '',
|
|
253
290
|
default: o.default,
|
|
254
|
-
allowed:
|
|
255
|
-
Array.isArray(o.options) ?
|
|
256
|
-
o.options.filter((x): x is string => typeof x === 'string')
|
|
257
|
-
: null,
|
|
291
|
+
allowed: Array.isArray(o.options) ? o.options : null,
|
|
258
292
|
allowAll: o.allowAll === true,
|
|
259
293
|
};
|
|
260
|
-
}
|
|
261
|
-
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
298
|
+
return entries;
|
|
262
299
|
}
|
|
263
300
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
301
|
+
function buildUsageExample(
|
|
302
|
+
commandName: string,
|
|
303
|
+
cmdJson: any,
|
|
304
|
+
options: OptionSchema[]
|
|
305
|
+
): string {
|
|
306
|
+
const arg = cmdJson?.argument;
|
|
307
|
+
const hasArg = isObject(arg);
|
|
308
|
+
const argToken = hasArg ? `<${commandName}-arg>` : '';
|
|
309
|
+
|
|
310
|
+
// choose a representative value option to show
|
|
311
|
+
const valueOpts = options.filter((o) => o.kind === 'value');
|
|
312
|
+
const boolOpts = options.filter((o) => o.kind === 'boolean');
|
|
313
|
+
|
|
314
|
+
const exampleParts = [`yarn mono ${commandName}`];
|
|
315
|
+
if (argToken) exampleParts.push(argToken);
|
|
316
|
+
|
|
317
|
+
// include at most 2 value options and 1 boolean in the example for readability
|
|
318
|
+
for (const o of valueOpts.slice(0, 2)) {
|
|
319
|
+
const flag = `--${o.key}`;
|
|
320
|
+
const val =
|
|
321
|
+
o.default !== undefined ? o.default : (o.allowed?.[0] ?? '<value>');
|
|
322
|
+
exampleParts.push(`${flag} ${val}`);
|
|
323
|
+
}
|
|
324
|
+
if (boolOpts.length) {
|
|
325
|
+
exampleParts.push(`--${boolOpts[0].key}`);
|
|
326
|
+
}
|
|
267
327
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
328
|
+
return exampleParts.join(' ');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function formatMonoConfigSection(monoConfig: MonoConfig | null): string {
|
|
332
|
+
const lines: string[] = [];
|
|
333
|
+
lines.push('## Mono configuration');
|
|
334
|
+
lines.push('');
|
|
335
|
+
|
|
336
|
+
if (!monoConfig) {
|
|
337
|
+
lines.push('_No `.mono/config.json` found._');
|
|
338
|
+
return lines.join('\n');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const c = monoConfig.config;
|
|
342
|
+
lines.push(
|
|
343
|
+
`Source: \`${toPosix(path.relative(REPO_ROOT, monoConfig.path))}\``
|
|
344
|
+
);
|
|
345
|
+
lines.push('');
|
|
346
|
+
|
|
347
|
+
if (Array.isArray(c.envMap) && c.envMap.length) {
|
|
348
|
+
lines.push('### envMap');
|
|
349
|
+
lines.push('');
|
|
350
|
+
lines.push(
|
|
351
|
+
'- ' + c.envMap.map((x: string) => `\`${mdEscapeInline(x)}\``).join(', ')
|
|
352
|
+
);
|
|
353
|
+
lines.push('');
|
|
271
354
|
}
|
|
272
355
|
|
|
356
|
+
const pkgMaps = c?.workspace?.packageMaps;
|
|
357
|
+
if (pkgMaps && isObject(pkgMaps) && Object.keys(pkgMaps).length) {
|
|
358
|
+
lines.push('### Workspace aliases (packageMaps)');
|
|
359
|
+
lines.push('');
|
|
360
|
+
const entries = Object.entries(pkgMaps).sort(([a], [b]) =>
|
|
361
|
+
a.localeCompare(b)
|
|
362
|
+
);
|
|
363
|
+
for (const [alias, target] of entries) {
|
|
364
|
+
lines.push(
|
|
365
|
+
`- \`${mdEscapeInline(alias)}\` → \`${mdEscapeInline(String(target))}\``
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
lines.push('');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const pre = c?.workspace?.preactions;
|
|
372
|
+
if (Array.isArray(pre) && pre.length) {
|
|
373
|
+
lines.push('### Global preactions');
|
|
374
|
+
lines.push('');
|
|
375
|
+
lines.push('```bash');
|
|
376
|
+
for (const p of pre) lines.push(String(p));
|
|
377
|
+
lines.push('```');
|
|
378
|
+
lines.push('');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (typeof c.prodFlag === 'string' && c.prodFlag.trim()) {
|
|
382
|
+
lines.push('### prodFlag');
|
|
383
|
+
lines.push('');
|
|
384
|
+
lines.push(
|
|
385
|
+
`Production flag keyword: \`${mdEscapeInline(c.prodFlag.trim())}\``
|
|
386
|
+
);
|
|
387
|
+
lines.push('');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return lines.join('\n');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
type MonoCommand = {
|
|
394
|
+
name: string;
|
|
395
|
+
file: string;
|
|
396
|
+
json: any;
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
function formatMonoCommandsSection(commands: MonoCommand[]): string {
|
|
400
|
+
const lines: string[] = [];
|
|
401
|
+
lines.push('## Mono commands');
|
|
402
|
+
lines.push('');
|
|
403
|
+
lines.push(
|
|
404
|
+
'Generated from `.mono/*.json` (excluding `config.json`). Each filename becomes a command:'
|
|
405
|
+
);
|
|
406
|
+
lines.push('');
|
|
407
|
+
lines.push('```bash');
|
|
408
|
+
lines.push('yarn mono <command> [argument] [--options]');
|
|
409
|
+
lines.push('```');
|
|
410
|
+
lines.push('');
|
|
411
|
+
|
|
412
|
+
if (!commands.length) {
|
|
413
|
+
lines.push('_No mono command JSON files found._');
|
|
414
|
+
return lines.join('\n');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Index
|
|
418
|
+
lines.push('### Command index');
|
|
419
|
+
lines.push('');
|
|
420
|
+
for (const c of commands) {
|
|
421
|
+
const desc =
|
|
422
|
+
typeof c.json?.description === 'string' ? c.json.description.trim() : '';
|
|
423
|
+
const suffix = desc ? ` — ${desc}` : '';
|
|
424
|
+
lines.push(
|
|
425
|
+
`- [\`${mdEscapeInline(c.name)}\`](#mono-command-${mdEscapeInline(c.name).toLowerCase()})${suffix}`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
lines.push('');
|
|
429
|
+
|
|
430
|
+
for (const c of commands) {
|
|
431
|
+
const j = c.json || {};
|
|
432
|
+
const rel = toPosix(path.relative(REPO_ROOT, c.file));
|
|
433
|
+
const anchor = `mono-command-${c.name.toLowerCase()}`;
|
|
434
|
+
|
|
435
|
+
const desc = typeof j.description === 'string' ? j.description.trim() : '';
|
|
436
|
+
const arg = j.argument;
|
|
437
|
+
const options = parseOptionsSchema(j.options);
|
|
438
|
+
|
|
439
|
+
lines.push('---');
|
|
440
|
+
lines.push(`### Mono command: ${c.name}`);
|
|
441
|
+
lines.push(`<a id="${anchor}"></a>`);
|
|
442
|
+
lines.push('');
|
|
443
|
+
lines.push(`Source: \`${rel}\``);
|
|
444
|
+
lines.push('');
|
|
445
|
+
|
|
446
|
+
if (desc) {
|
|
447
|
+
lines.push(`**Description:** ${mdEscapeInline(desc)}`);
|
|
448
|
+
lines.push('');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Usage
|
|
452
|
+
lines.push('**Usage**');
|
|
453
|
+
lines.push('');
|
|
454
|
+
lines.push('```bash');
|
|
455
|
+
lines.push(
|
|
456
|
+
`yarn mono ${c.name}${isObject(arg) ? ` <${c.name}-arg>` : ''} [--options]`
|
|
457
|
+
);
|
|
458
|
+
lines.push('```');
|
|
459
|
+
lines.push('');
|
|
460
|
+
lines.push('Example:');
|
|
461
|
+
lines.push('');
|
|
462
|
+
lines.push('```bash');
|
|
463
|
+
lines.push(buildUsageExample(c.name, j, options));
|
|
464
|
+
lines.push('```');
|
|
465
|
+
lines.push('');
|
|
466
|
+
|
|
467
|
+
// Argument
|
|
468
|
+
if (isObject(arg)) {
|
|
469
|
+
lines.push('**Argument**');
|
|
470
|
+
lines.push('');
|
|
471
|
+
const bits: string[] = [];
|
|
472
|
+
if (typeof arg.type === 'string')
|
|
473
|
+
bits.push(`type: \`${mdEscapeInline(arg.type)}\``);
|
|
474
|
+
if (arg.default !== undefined)
|
|
475
|
+
bits.push(`default: \`${mdEscapeInline(String(arg.default))}\``);
|
|
476
|
+
if (typeof arg.description === 'string')
|
|
477
|
+
bits.push(mdEscapeInline(arg.description));
|
|
478
|
+
lines.push(`- ${bits.join(' • ') || '_(no details)_'} `);
|
|
479
|
+
lines.push('');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Options
|
|
483
|
+
if (options.length) {
|
|
484
|
+
lines.push('**Options**');
|
|
485
|
+
lines.push('');
|
|
486
|
+
lines.push('| Option | Type | Shortcut | Default | Allowed | Notes |');
|
|
487
|
+
lines.push('|---|---:|:---:|---:|---|---|');
|
|
488
|
+
for (const o of options) {
|
|
489
|
+
const optCol =
|
|
490
|
+
o.kind === 'boolean' ?
|
|
491
|
+
`\`--${mdEscapeInline(o.key)}\``
|
|
492
|
+
: `\`--${mdEscapeInline(o.key)} <${mdEscapeInline(o.key)}>\``;
|
|
493
|
+
const typeCol = `\`${mdEscapeInline(o.type)}\``;
|
|
494
|
+
const shortCol = o.shortcut ? `\`-${mdEscapeInline(o.shortcut)}\`` : '';
|
|
495
|
+
const defCol =
|
|
496
|
+
o.default !== undefined ? `\`${mdEscapeInline(o.default)}\`` : '';
|
|
497
|
+
const allowedCol =
|
|
498
|
+
o.allowed ?
|
|
499
|
+
o.allowed.map((x) => `\`${mdEscapeInline(x)}\``).join(', ')
|
|
500
|
+
: '';
|
|
501
|
+
const notes = [
|
|
502
|
+
o.allowAll ? 'allowAll' : '',
|
|
503
|
+
o.description ? mdEscapeInline(o.description) : '',
|
|
504
|
+
]
|
|
505
|
+
.filter(Boolean)
|
|
506
|
+
.join(' • ');
|
|
507
|
+
lines.push(
|
|
508
|
+
`| ${optCol} | ${typeCol} | ${shortCol} | ${defCol} | ${allowedCol} | ${notes} |`
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
lines.push('');
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Environments
|
|
515
|
+
if (
|
|
516
|
+
j.environments &&
|
|
517
|
+
isObject(j.environments) &&
|
|
518
|
+
Object.keys(j.environments).length
|
|
519
|
+
) {
|
|
520
|
+
lines.push('**Environment Variables**');
|
|
521
|
+
lines.push('');
|
|
522
|
+
const envs = Object.entries(j.environments).sort(([a], [b]) =>
|
|
523
|
+
a.localeCompare(b)
|
|
524
|
+
);
|
|
525
|
+
for (const [envName, envObj] of envs) {
|
|
526
|
+
lines.push(`- \`${mdEscapeInline(envName)}\``);
|
|
527
|
+
if (isObject(envObj) && Object.keys(envObj).length) {
|
|
528
|
+
const kv = Object.entries(envObj).sort(([a], [b]) =>
|
|
529
|
+
a.localeCompare(b)
|
|
530
|
+
);
|
|
531
|
+
lines.push(
|
|
532
|
+
indentLines(
|
|
533
|
+
kv
|
|
534
|
+
.map(
|
|
535
|
+
([k, v]) =>
|
|
536
|
+
`- \`${mdEscapeInline(k)}\` = \`${mdEscapeInline(String(v))}\``
|
|
537
|
+
)
|
|
538
|
+
.join('\n'),
|
|
539
|
+
2
|
|
540
|
+
)
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
lines.push('');
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// preactions/actions
|
|
548
|
+
if (Array.isArray(j.preactions) && j.preactions.length) {
|
|
549
|
+
lines.push('**Preactions**');
|
|
550
|
+
lines.push('');
|
|
551
|
+
lines.push('```bash');
|
|
552
|
+
for (const p of j.preactions) lines.push(String(p));
|
|
553
|
+
lines.push('```');
|
|
554
|
+
lines.push('');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (Array.isArray(j.actions) && j.actions.length) {
|
|
558
|
+
lines.push('**Actions**');
|
|
559
|
+
lines.push('');
|
|
560
|
+
lines.push('```bash');
|
|
561
|
+
for (const a of j.actions) lines.push(String(a));
|
|
562
|
+
lines.push('```');
|
|
563
|
+
lines.push('');
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return lines.join('\n');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// ---------- workspace scripts summary ----------
|
|
571
|
+
|
|
572
|
+
// Define PackageInfo type
|
|
573
|
+
type PackageInfo = {
|
|
574
|
+
name: string;
|
|
575
|
+
dir: string;
|
|
576
|
+
scripts: Record<string, string>;
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
function collectScripts(packages: PackageInfo[]): Map<string, string[]> {
|
|
580
|
+
const scriptToPackages = new Map<string, string[]>();
|
|
581
|
+
for (const p of packages) {
|
|
582
|
+
for (const scriptName of Object.keys(p.scripts || {})) {
|
|
583
|
+
if (!scriptToPackages.has(scriptName))
|
|
584
|
+
scriptToPackages.set(scriptName, []);
|
|
585
|
+
scriptToPackages.get(scriptName)!.push(p.name);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return scriptToPackages;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// ---------- main ----------
|
|
592
|
+
async function main(): Promise<void> {
|
|
593
|
+
if (!(await exists(ROOT_PKG_JSON)))
|
|
594
|
+
throw new Error(`Missing: ${ROOT_PKG_JSON}`);
|
|
273
595
|
await ensureParentDir(OUTPUT_PATH);
|
|
274
596
|
|
|
275
597
|
const rootPkg = await readJson<any>(ROOT_PKG_JSON);
|
|
@@ -277,47 +599,59 @@ async function main(): Promise<void> {
|
|
|
277
599
|
|
|
278
600
|
const monoConfig = await readMonoConfig();
|
|
279
601
|
const monoCommands = await readMonoCommands();
|
|
280
|
-
const pkgDirs = await findWorkspacePackageDirs(REPO_ROOT, workspacePatterns);
|
|
281
602
|
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
603
|
+
const pkgDirs = await findWorkspacePackageDirs(REPO_ROOT, workspacePatterns);
|
|
604
|
+
console.log(`[main] Package directories found:`, pkgDirs);
|
|
605
|
+
const packages: PackageInfo[] = [];
|
|
606
|
+
for (const dir of pkgDirs) {
|
|
607
|
+
try {
|
|
608
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
609
|
+
console.log(`[main] Reading package.json:`, pkgPath);
|
|
610
|
+
const pj = await readJson<any>(pkgPath);
|
|
611
|
+
packages.push({
|
|
286
612
|
name:
|
|
287
613
|
pj.name ||
|
|
288
614
|
toPosix(path.relative(REPO_ROOT, dir)) ||
|
|
289
615
|
path.basename(dir),
|
|
290
616
|
dir,
|
|
291
|
-
scripts: pj.scripts
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
|
|
617
|
+
scripts: pj.scripts || {},
|
|
618
|
+
});
|
|
619
|
+
console.log(`[main] Loaded package:`, pj.name || dir);
|
|
620
|
+
} catch (err) {
|
|
621
|
+
console.error(`[main] Failed to load package.json for:`, dir, err);
|
|
622
|
+
// skip
|
|
623
|
+
}
|
|
624
|
+
}
|
|
295
625
|
|
|
296
626
|
const parts: string[] = [];
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
627
|
+
parts.push(`# ⚙️ Command Line Reference
|
|
628
|
+
|
|
300
629
|
> Generated by \`scripts/generate-readme.mjs\`.
|
|
301
630
|
> Update \`.mono/config.json\`, \`.mono/*.json\`, and workspace package scripts to change this output.
|
|
302
631
|
|
|
303
632
|
`);
|
|
633
|
+
parts.push(formatMonoConfigSection(monoConfig));
|
|
634
|
+
parts.push('');
|
|
635
|
+
parts.push(formatMonoCommandsSection(monoCommands));
|
|
636
|
+
parts.push('');
|
|
304
637
|
|
|
305
|
-
const
|
|
638
|
+
const val = await generateDocsIndex({
|
|
306
639
|
docsDir: path.join(REPO_ROOT, 'docs'),
|
|
307
640
|
excludeFile: 'command-line.md',
|
|
308
641
|
});
|
|
309
642
|
|
|
310
|
-
parts.push(
|
|
643
|
+
val.split('\n').forEach((line) => parts.push(line));
|
|
311
644
|
|
|
645
|
+
await ensureParentDir(OUTPUT_README);
|
|
312
646
|
await fs.writeFile(OUTPUT_README, parts.join('\n'), 'utf8');
|
|
313
647
|
|
|
314
|
-
console.log(`Generated: ${OUTPUT_README}`);
|
|
315
|
-
console.log(
|
|
316
|
-
console.log(
|
|
317
|
-
console.log(
|
|
648
|
+
console.log(`[main] Generated: ${OUTPUT_README}`);
|
|
649
|
+
console.log(`[main] mono config: ${monoConfig ? 'yes' : 'no'}`);
|
|
650
|
+
console.log(`[main] mono commands: ${monoCommands.length}`);
|
|
651
|
+
console.log(`[main] workspace packages: ${packages.length}`);
|
|
318
652
|
}
|
|
319
653
|
|
|
320
654
|
main().catch((err) => {
|
|
321
|
-
console.error(err);
|
|
655
|
+
console.error(err?.stack || String(err));
|
|
322
656
|
process.exitCode = 1;
|
|
323
657
|
});
|