@mono-labs/cli 0.0.206 → 0.0.207
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/dist/project/build-mono-readme.js +76 -148
- package/package.json +1 -1
- package/src/project/build-mono-readme.ts +132 -208
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// scripts/generate-readme.
|
|
2
|
+
// scripts/generate-readme.mjs
|
|
3
3
|
// Node >= 18 recommended
|
|
4
4
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
5
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -9,21 +9,18 @@ const node_fs_1 = require("node:fs");
|
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
10
|
const generate_docs_js_1 = require("./generate-docs.js");
|
|
11
11
|
/* -------------------------------------------------------------------------- */
|
|
12
|
-
/*
|
|
12
|
+
/* Constants */
|
|
13
13
|
/* -------------------------------------------------------------------------- */
|
|
14
|
-
// Always use the working directory as the root for all file actions
|
|
15
14
|
const REPO_ROOT = node_path_1.default.resolve(process.cwd());
|
|
16
15
|
const MONO_DIR = node_path_1.default.join(REPO_ROOT, '.mono');
|
|
17
16
|
const ROOT_PKG_JSON = node_path_1.default.join(REPO_ROOT, 'package.json');
|
|
18
17
|
const OUTPUT_PATH = node_path_1.default.join(REPO_ROOT, 'docs');
|
|
19
18
|
const OUTPUT_README = node_path_1.default.join(OUTPUT_PATH, 'command-line.md');
|
|
20
19
|
/* -------------------------------------------------------------------------- */
|
|
21
|
-
/*
|
|
20
|
+
/* Utils */
|
|
22
21
|
/* -------------------------------------------------------------------------- */
|
|
23
22
|
async function ensureParentDir(filePath) {
|
|
24
|
-
|
|
25
|
-
const dir = node_path_1.default.resolve(process.cwd(), node_path_1.default.dirname(filePath));
|
|
26
|
-
await node_fs_1.promises.mkdir(dir, { recursive: true });
|
|
23
|
+
await node_fs_1.promises.mkdir(node_path_1.default.dirname(filePath), { recursive: true });
|
|
27
24
|
}
|
|
28
25
|
async function exists(p) {
|
|
29
26
|
try {
|
|
@@ -35,21 +32,16 @@ async function exists(p) {
|
|
|
35
32
|
}
|
|
36
33
|
}
|
|
37
34
|
function isObject(v) {
|
|
38
|
-
return
|
|
35
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
39
36
|
}
|
|
40
37
|
function toPosix(p) {
|
|
41
38
|
return p.split(node_path_1.default.sep).join('/');
|
|
42
39
|
}
|
|
43
40
|
async function readJson(filePath) {
|
|
44
|
-
|
|
45
|
-
const absPath = node_path_1.default.resolve(process.cwd(), filePath);
|
|
46
|
-
const raw = await node_fs_1.promises.readFile(absPath, 'utf8');
|
|
47
|
-
return JSON.parse(raw);
|
|
41
|
+
return JSON.parse(await node_fs_1.promises.readFile(filePath, 'utf8'));
|
|
48
42
|
}
|
|
49
43
|
async function listDir(dir) {
|
|
50
|
-
|
|
51
|
-
const absDir = node_path_1.default.resolve(process.cwd(), dir);
|
|
52
|
-
return node_fs_1.promises.readdir(absDir, { withFileTypes: true });
|
|
44
|
+
return node_fs_1.promises.readdir(dir, { withFileTypes: true });
|
|
53
45
|
}
|
|
54
46
|
function normalizeWorkspacePatterns(workspacesField) {
|
|
55
47
|
if (Array.isArray(workspacesField))
|
|
@@ -60,18 +52,18 @@ function normalizeWorkspacePatterns(workspacesField) {
|
|
|
60
52
|
}
|
|
61
53
|
return [];
|
|
62
54
|
}
|
|
63
|
-
function mdEscapeInline(
|
|
64
|
-
return
|
|
55
|
+
function mdEscapeInline(s) {
|
|
56
|
+
return s.replaceAll('`', '\\`');
|
|
65
57
|
}
|
|
66
58
|
function indentLines(s, spaces = 2) {
|
|
67
59
|
const pad = ' '.repeat(spaces);
|
|
68
60
|
return s
|
|
69
61
|
.split('\n')
|
|
70
|
-
.map((
|
|
62
|
+
.map((l) => pad + l)
|
|
71
63
|
.join('\n');
|
|
72
64
|
}
|
|
73
65
|
/* -------------------------------------------------------------------------- */
|
|
74
|
-
/*
|
|
66
|
+
/* Workspace globbing */
|
|
75
67
|
/* -------------------------------------------------------------------------- */
|
|
76
68
|
function matchSegment(patternSeg, name) {
|
|
77
69
|
if (patternSeg === '*')
|
|
@@ -79,113 +71,78 @@ function matchSegment(patternSeg, name) {
|
|
|
79
71
|
if (!patternSeg.includes('*'))
|
|
80
72
|
return patternSeg === name;
|
|
81
73
|
const escaped = patternSeg.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
82
|
-
|
|
83
|
-
return regex.test(name);
|
|
74
|
+
return new RegExp(`^${escaped.replaceAll('*', '.*')}$`).test(name);
|
|
84
75
|
}
|
|
85
76
|
async function expandWorkspacePattern(root, pattern) {
|
|
86
|
-
const
|
|
87
|
-
async function expandFrom(dir,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (index >= segments.length)
|
|
91
|
-
return [absDir];
|
|
92
|
-
const seg = segments[index];
|
|
93
|
-
if (seg === '**') {
|
|
94
|
-
const results = [];
|
|
95
|
-
results.push(...(await expandFrom(absDir, index + 1)));
|
|
96
|
-
const entries = await node_fs_1.promises
|
|
97
|
-
.readdir(absDir, { withFileTypes: true })
|
|
98
|
-
.catch(() => []);
|
|
99
|
-
for (const entry of entries) {
|
|
100
|
-
if (!entry.isDirectory())
|
|
101
|
-
continue;
|
|
102
|
-
results.push(...(await expandFrom(node_path_1.default.join(absDir, entry.name), index)));
|
|
103
|
-
}
|
|
104
|
-
return results;
|
|
105
|
-
}
|
|
77
|
+
const segs = toPosix(pattern).split('/').filter(Boolean);
|
|
78
|
+
async function expandFrom(dir, idx) {
|
|
79
|
+
if (idx >= segs.length)
|
|
80
|
+
return [dir];
|
|
106
81
|
const entries = await node_fs_1.promises
|
|
107
|
-
.readdir(
|
|
82
|
+
.readdir(dir, { withFileTypes: true })
|
|
108
83
|
.catch(() => []);
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
results.push(...(await expandFrom(node_path_1.default.join(absDir, entry.name), index + 1)));
|
|
84
|
+
const seg = segs[idx];
|
|
85
|
+
if (seg === '**') {
|
|
86
|
+
const nested = await Promise.all(entries
|
|
87
|
+
.filter((e) => e.isDirectory())
|
|
88
|
+
.map((e) => expandFrom(node_path_1.default.join(dir, e.name), idx)));
|
|
89
|
+
return [...(await expandFrom(dir, idx + 1)), ...nested.flat()];
|
|
116
90
|
}
|
|
117
|
-
|
|
91
|
+
const nested = await Promise.all(entries
|
|
92
|
+
.filter((e) => e.isDirectory() && matchSegment(seg, e.name))
|
|
93
|
+
.map((e) => expandFrom(node_path_1.default.join(dir, e.name), idx + 1)));
|
|
94
|
+
return nested.flat();
|
|
118
95
|
}
|
|
119
96
|
const dirs = await expandFrom(root, 0);
|
|
120
|
-
const pkgDirs =
|
|
121
|
-
|
|
122
|
-
if (await exists(node_path_1.default.join(d, 'package.json'))) {
|
|
123
|
-
pkgDirs.push(d);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return Array.from(new Set(pkgDirs));
|
|
97
|
+
const pkgDirs = (await Promise.all(dirs.map(async (d) => (await exists(node_path_1.default.join(d, 'package.json'))) ? d : null))).filter(Boolean);
|
|
98
|
+
return [...new Set(pkgDirs)];
|
|
127
99
|
}
|
|
128
|
-
async function findWorkspacePackageDirs(repoRoot,
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
dirs.push(...(await expandWorkspacePattern(repoRoot, pat)));
|
|
132
|
-
}
|
|
133
|
-
return Array.from(new Set(dirs));
|
|
100
|
+
async function findWorkspacePackageDirs(repoRoot, workspacePatterns) {
|
|
101
|
+
const resolved = await Promise.all(workspacePatterns.map((p) => expandWorkspacePattern(repoRoot, p)));
|
|
102
|
+
return [...new Set(resolved.flat())];
|
|
134
103
|
}
|
|
135
104
|
/* -------------------------------------------------------------------------- */
|
|
136
|
-
/*
|
|
105
|
+
/* Mono config + commands */
|
|
137
106
|
/* -------------------------------------------------------------------------- */
|
|
138
107
|
async function readMonoConfig() {
|
|
139
|
-
|
|
140
|
-
const configPath = node_path_1.default.resolve(process.cwd(), node_path_1.default.join(MONO_DIR, 'config.json'));
|
|
108
|
+
const configPath = node_path_1.default.join(MONO_DIR, 'config.json');
|
|
141
109
|
if (!(await exists(configPath)))
|
|
142
110
|
return null;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
111
|
+
return {
|
|
112
|
+
path: configPath,
|
|
113
|
+
config: await readJson(configPath),
|
|
114
|
+
};
|
|
150
115
|
}
|
|
151
116
|
function commandNameFromFile(filePath) {
|
|
152
117
|
return node_path_1.default.basename(filePath).replace(/\.json$/i, '');
|
|
153
118
|
}
|
|
154
119
|
async function readMonoCommands() {
|
|
155
|
-
|
|
156
|
-
const monoDirAbs = node_path_1.default.resolve(process.cwd(), MONO_DIR);
|
|
157
|
-
if (!(await exists(monoDirAbs)))
|
|
120
|
+
if (!(await exists(MONO_DIR)))
|
|
158
121
|
return [];
|
|
159
|
-
const entries = await listDir(
|
|
160
|
-
const
|
|
161
|
-
.filter((e) => e.isFile() && e.name.endsWith('.json'))
|
|
162
|
-
.map((e) =>
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
json,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
/* ignore invalid JSON */
|
|
176
|
-
}
|
|
177
|
-
}
|
|
122
|
+
const entries = await listDir(MONO_DIR);
|
|
123
|
+
const commands = await Promise.all(entries
|
|
124
|
+
.filter((e) => e.isFile() && e.name.endsWith('.json') && e.name !== 'config.json')
|
|
125
|
+
.map(async (e) => {
|
|
126
|
+
const file = node_path_1.default.join(MONO_DIR, e.name);
|
|
127
|
+
const json = await readJson(file);
|
|
128
|
+
return {
|
|
129
|
+
name: commandNameFromFile(file),
|
|
130
|
+
file,
|
|
131
|
+
json,
|
|
132
|
+
};
|
|
133
|
+
}));
|
|
178
134
|
return commands.sort((a, b) => a.name.localeCompare(b.name));
|
|
179
135
|
}
|
|
180
136
|
/* -------------------------------------------------------------------------- */
|
|
181
|
-
/*
|
|
137
|
+
/* Options parsing */
|
|
182
138
|
/* -------------------------------------------------------------------------- */
|
|
183
139
|
function parseOptionsSchema(optionsObj) {
|
|
184
140
|
if (!isObject(optionsObj))
|
|
185
141
|
return [];
|
|
186
|
-
|
|
142
|
+
return Object.entries(optionsObj)
|
|
143
|
+
.map(([key, raw]) => {
|
|
187
144
|
const o = isObject(raw) ? raw : {};
|
|
188
|
-
const hasType = typeof o.type === 'string'
|
|
145
|
+
const hasType = typeof o.type === 'string';
|
|
189
146
|
return {
|
|
190
147
|
key,
|
|
191
148
|
kind: hasType ? 'value' : 'boolean',
|
|
@@ -193,41 +150,20 @@ function parseOptionsSchema(optionsObj) {
|
|
|
193
150
|
description: typeof o.description === 'string' ? o.description : '',
|
|
194
151
|
shortcut: typeof o.shortcut === 'string' ? o.shortcut : '',
|
|
195
152
|
default: o.default,
|
|
196
|
-
allowed: Array.isArray(o.options) ?
|
|
153
|
+
allowed: Array.isArray(o.options) ?
|
|
154
|
+
o.options.filter((x) => typeof x === 'string')
|
|
155
|
+
: null,
|
|
197
156
|
allowAll: o.allowAll === true,
|
|
198
157
|
};
|
|
199
|
-
})
|
|
200
|
-
|
|
158
|
+
})
|
|
159
|
+
.sort((a, b) => a.key.localeCompare(b.key));
|
|
201
160
|
}
|
|
202
161
|
/* -------------------------------------------------------------------------- */
|
|
203
|
-
/*
|
|
204
|
-
/* -------------------------------------------------------------------------- */
|
|
205
|
-
function buildUsageExample(commandName, cmdJson, options) {
|
|
206
|
-
const arg = cmdJson.argument;
|
|
207
|
-
const hasArg = isObject(arg);
|
|
208
|
-
const parts = [`yarn mono ${commandName}`];
|
|
209
|
-
if (hasArg)
|
|
210
|
-
parts.push(`<${commandName}-arg>`);
|
|
211
|
-
const valueOpts = options.filter((o) => o.kind === 'value');
|
|
212
|
-
const boolOpts = options.filter((o) => o.kind === 'boolean');
|
|
213
|
-
for (const o of valueOpts.slice(0, 2)) {
|
|
214
|
-
const value = o.default !== undefined ?
|
|
215
|
-
String(o.default)
|
|
216
|
-
: (o.allowed?.[0] ?? '<value>');
|
|
217
|
-
parts.push(`--${o.key} ${value}`);
|
|
218
|
-
}
|
|
219
|
-
if (boolOpts[0]) {
|
|
220
|
-
parts.push(`--${boolOpts[0].key}`);
|
|
221
|
-
}
|
|
222
|
-
return parts.join(' ');
|
|
223
|
-
}
|
|
224
|
-
/* -------------------------------------------------------------------------- */
|
|
225
|
-
/* Main */
|
|
162
|
+
/* Main */
|
|
226
163
|
/* -------------------------------------------------------------------------- */
|
|
227
164
|
async function main() {
|
|
228
|
-
// Always resolve all paths relative to working directory
|
|
229
165
|
if (!(await exists(ROOT_PKG_JSON))) {
|
|
230
|
-
throw new Error(`Missing ${ROOT_PKG_JSON}`);
|
|
166
|
+
throw new Error(`Missing: ${ROOT_PKG_JSON}`);
|
|
231
167
|
}
|
|
232
168
|
await ensureParentDir(OUTPUT_PATH);
|
|
233
169
|
const rootPkg = await readJson(ROOT_PKG_JSON);
|
|
@@ -235,36 +171,28 @@ async function main() {
|
|
|
235
171
|
const monoConfig = await readMonoConfig();
|
|
236
172
|
const monoCommands = await readMonoCommands();
|
|
237
173
|
const pkgDirs = await findWorkspacePackageDirs(REPO_ROOT, workspacePatterns);
|
|
238
|
-
const packages =
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
catch {
|
|
251
|
-
/* ignore */
|
|
252
|
-
}
|
|
253
|
-
}
|
|
174
|
+
const packages = await Promise.all(pkgDirs.map(async (dir) => {
|
|
175
|
+
const pj = await readJson(node_path_1.default.join(dir, 'package.json'));
|
|
176
|
+
return {
|
|
177
|
+
name: pj.name ||
|
|
178
|
+
toPosix(node_path_1.default.relative(REPO_ROOT, dir)) ||
|
|
179
|
+
node_path_1.default.basename(dir),
|
|
180
|
+
dir,
|
|
181
|
+
scripts: pj.scripts ?? {},
|
|
182
|
+
};
|
|
183
|
+
}));
|
|
254
184
|
const parts = [];
|
|
255
185
|
parts.push(`# Mono Command-Line Reference
|
|
256
186
|
|
|
257
|
-
> Generated by \`scripts/generate-readme.
|
|
187
|
+
> Generated by \`scripts/generate-readme.mjs\`.
|
|
188
|
+
> Update \`.mono/config.json\`, \`.mono/*.json\`, and workspace package scripts to change this output.
|
|
258
189
|
|
|
259
190
|
`);
|
|
260
|
-
// Reuse your existing formatters here
|
|
261
|
-
// (unchanged logic, now fully typed)
|
|
262
191
|
const docsIndex = await (0, generate_docs_js_1.generateDocsIndex)({
|
|
263
192
|
docsDir: node_path_1.default.join(REPO_ROOT, 'docs'),
|
|
264
193
|
excludeFile: 'command-line.md',
|
|
265
194
|
});
|
|
266
195
|
parts.push(docsIndex);
|
|
267
|
-
await ensureParentDir(OUTPUT_README);
|
|
268
196
|
await node_fs_1.promises.writeFile(OUTPUT_README, parts.join('\n'), 'utf8');
|
|
269
197
|
console.log(`Generated: ${OUTPUT_README}`);
|
|
270
198
|
console.log(`- mono config: ${monoConfig ? 'yes' : 'no'}`);
|
|
@@ -272,6 +200,6 @@ async function main() {
|
|
|
272
200
|
console.log(`- workspace packages: ${packages.length}`);
|
|
273
201
|
}
|
|
274
202
|
main().catch((err) => {
|
|
275
|
-
console.error(err
|
|
276
|
-
process.
|
|
203
|
+
console.error(err);
|
|
204
|
+
process.exitCode = 1;
|
|
277
205
|
});
|
package/package.json
CHANGED
|
@@ -1,31 +1,15 @@
|
|
|
1
|
-
// scripts/generate-readme.
|
|
1
|
+
// scripts/generate-readme.mjs
|
|
2
2
|
// Node >= 18 recommended
|
|
3
3
|
|
|
4
|
-
import { promises as fs } from 'node:fs';
|
|
5
|
-
import { Dirent } from 'node:fs';
|
|
4
|
+
import { promises as fs, Dirent } from 'node:fs';
|
|
6
5
|
import path from 'node:path';
|
|
7
|
-
import { fileURLToPath } from 'node:url';
|
|
8
|
-
|
|
9
6
|
import { generateDocsIndex } from './generate-docs.js';
|
|
10
7
|
|
|
11
8
|
/* -------------------------------------------------------------------------- */
|
|
12
|
-
/*
|
|
13
|
-
/* -------------------------------------------------------------------------- */
|
|
14
|
-
|
|
15
|
-
// Always use the working directory as the root for all file actions
|
|
16
|
-
const REPO_ROOT = path.resolve(process.cwd());
|
|
17
|
-
const MONO_DIR = path.join(REPO_ROOT, '.mono');
|
|
18
|
-
const ROOT_PKG_JSON = path.join(REPO_ROOT, 'package.json');
|
|
19
|
-
const OUTPUT_PATH = path.join(REPO_ROOT, 'docs');
|
|
20
|
-
const OUTPUT_README = path.join(OUTPUT_PATH, 'command-line.md');
|
|
21
|
-
|
|
22
|
-
/* -------------------------------------------------------------------------- */
|
|
23
|
-
/* Types */
|
|
9
|
+
/* Types */
|
|
24
10
|
/* -------------------------------------------------------------------------- */
|
|
25
11
|
|
|
26
|
-
type
|
|
27
|
-
|
|
28
|
-
interface MonoConfig {
|
|
12
|
+
type MonoConfig = {
|
|
29
13
|
path: string;
|
|
30
14
|
config: {
|
|
31
15
|
envMap?: string[];
|
|
@@ -35,21 +19,33 @@ interface MonoConfig {
|
|
|
35
19
|
preactions?: string[];
|
|
36
20
|
};
|
|
37
21
|
};
|
|
38
|
-
}
|
|
22
|
+
};
|
|
39
23
|
|
|
40
|
-
|
|
24
|
+
type MonoCommand = {
|
|
41
25
|
name: string;
|
|
42
26
|
file: string;
|
|
43
|
-
json:
|
|
44
|
-
|
|
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
|
+
};
|
|
45
41
|
|
|
46
|
-
|
|
42
|
+
type PackageInfo = {
|
|
47
43
|
name: string;
|
|
48
44
|
dir: string;
|
|
49
45
|
scripts: Record<string, string>;
|
|
50
|
-
}
|
|
46
|
+
};
|
|
51
47
|
|
|
52
|
-
|
|
48
|
+
type OptionSchema = {
|
|
53
49
|
key: string;
|
|
54
50
|
kind: 'boolean' | 'value';
|
|
55
51
|
type: string;
|
|
@@ -58,16 +54,24 @@ interface OptionSchema {
|
|
|
58
54
|
default: unknown;
|
|
59
55
|
allowed: string[] | null;
|
|
60
56
|
allowAll: boolean;
|
|
61
|
-
}
|
|
57
|
+
};
|
|
62
58
|
|
|
63
59
|
/* -------------------------------------------------------------------------- */
|
|
64
|
-
/*
|
|
60
|
+
/* Constants */
|
|
61
|
+
/* -------------------------------------------------------------------------- */
|
|
62
|
+
|
|
63
|
+
const REPO_ROOT = path.resolve(process.cwd());
|
|
64
|
+
const MONO_DIR = path.join(REPO_ROOT, '.mono');
|
|
65
|
+
const ROOT_PKG_JSON = path.join(REPO_ROOT, 'package.json');
|
|
66
|
+
const OUTPUT_PATH = path.join(REPO_ROOT, 'docs');
|
|
67
|
+
const OUTPUT_README = path.join(OUTPUT_PATH, 'command-line.md');
|
|
68
|
+
|
|
69
|
+
/* -------------------------------------------------------------------------- */
|
|
70
|
+
/* Utils */
|
|
65
71
|
/* -------------------------------------------------------------------------- */
|
|
66
72
|
|
|
67
73
|
async function ensureParentDir(filePath: string): Promise<void> {
|
|
68
|
-
|
|
69
|
-
const dir = path.resolve(process.cwd(), path.dirname(filePath));
|
|
70
|
-
await fs.mkdir(dir, { recursive: true });
|
|
74
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
async function exists(p: string): Promise<boolean> {
|
|
@@ -79,25 +83,20 @@ async function exists(p: string): Promise<boolean> {
|
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
function isObject(v: unknown): v is
|
|
83
|
-
return
|
|
86
|
+
function isObject(v: unknown): v is Record<string, unknown> {
|
|
87
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
function toPosix(p: string): string {
|
|
87
91
|
return p.split(path.sep).join('/');
|
|
88
92
|
}
|
|
89
93
|
|
|
90
|
-
async function readJson<T
|
|
91
|
-
|
|
92
|
-
const absPath = path.resolve(process.cwd(), filePath);
|
|
93
|
-
const raw = await fs.readFile(absPath, 'utf8');
|
|
94
|
-
return JSON.parse(raw) as T;
|
|
94
|
+
async function readJson<T>(filePath: string): Promise<T> {
|
|
95
|
+
return JSON.parse(await fs.readFile(filePath, 'utf8')) as T;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
async function listDir(dir: string): Promise<Dirent[]> {
|
|
98
|
-
|
|
99
|
-
const absDir = path.resolve(process.cwd(), dir);
|
|
100
|
-
return fs.readdir(absDir, { withFileTypes: true });
|
|
99
|
+
return fs.readdir(dir, { withFileTypes: true });
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
function normalizeWorkspacePatterns(workspacesField: unknown): string[] {
|
|
@@ -111,121 +110,97 @@ function normalizeWorkspacePatterns(workspacesField: unknown): string[] {
|
|
|
111
110
|
return [];
|
|
112
111
|
}
|
|
113
112
|
|
|
114
|
-
function mdEscapeInline(
|
|
115
|
-
return
|
|
113
|
+
function mdEscapeInline(s: string): string {
|
|
114
|
+
return s.replaceAll('`', '\\`');
|
|
116
115
|
}
|
|
117
116
|
|
|
118
117
|
function indentLines(s: string, spaces = 2): string {
|
|
119
118
|
const pad = ' '.repeat(spaces);
|
|
120
119
|
return s
|
|
121
120
|
.split('\n')
|
|
122
|
-
.map((
|
|
121
|
+
.map((l) => pad + l)
|
|
123
122
|
.join('\n');
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
/* -------------------------------------------------------------------------- */
|
|
127
|
-
/*
|
|
126
|
+
/* Workspace globbing */
|
|
128
127
|
/* -------------------------------------------------------------------------- */
|
|
129
128
|
|
|
130
129
|
function matchSegment(patternSeg: string, name: string): boolean {
|
|
131
130
|
if (patternSeg === '*') return true;
|
|
132
131
|
if (!patternSeg.includes('*')) return patternSeg === name;
|
|
133
|
-
|
|
134
132
|
const escaped = patternSeg.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
135
|
-
|
|
136
|
-
return regex.test(name);
|
|
133
|
+
return new RegExp(`^${escaped.replaceAll('*', '.*')}$`).test(name);
|
|
137
134
|
}
|
|
138
135
|
|
|
139
136
|
async function expandWorkspacePattern(
|
|
140
137
|
root: string,
|
|
141
138
|
pattern: string
|
|
142
139
|
): Promise<string[]> {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
async function expandFrom(dir: string, index: number): Promise<string[]> {
|
|
146
|
-
// Always resolve dir relative to working directory
|
|
147
|
-
const absDir = path.resolve(process.cwd(), dir);
|
|
148
|
-
if (index >= segments.length) return [absDir];
|
|
140
|
+
const segs = toPosix(pattern).split('/').filter(Boolean);
|
|
149
141
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (seg === '**') {
|
|
153
|
-
const results: string[] = [];
|
|
154
|
-
results.push(...(await expandFrom(absDir, index + 1)));
|
|
155
|
-
|
|
156
|
-
const entries = await fs
|
|
157
|
-
.readdir(absDir, { withFileTypes: true })
|
|
158
|
-
.catch(() => []);
|
|
159
|
-
|
|
160
|
-
for (const entry of entries) {
|
|
161
|
-
if (!entry.isDirectory()) continue;
|
|
162
|
-
results.push(
|
|
163
|
-
...(await expandFrom(path.join(absDir, entry.name), index))
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
return results;
|
|
167
|
-
}
|
|
142
|
+
async function expandFrom(dir: string, idx: number): Promise<string[]> {
|
|
143
|
+
if (idx >= segs.length) return [dir];
|
|
168
144
|
|
|
169
145
|
const entries = await fs
|
|
170
|
-
.readdir(
|
|
146
|
+
.readdir(dir, { withFileTypes: true })
|
|
171
147
|
.catch(() => []);
|
|
172
148
|
|
|
173
|
-
const
|
|
174
|
-
for (const entry of entries) {
|
|
175
|
-
if (!entry.isDirectory()) continue;
|
|
176
|
-
if (!matchSegment(seg, entry.name)) continue;
|
|
149
|
+
const seg = segs[idx];
|
|
177
150
|
|
|
178
|
-
|
|
179
|
-
|
|
151
|
+
if (seg === '**') {
|
|
152
|
+
const nested = await Promise.all(
|
|
153
|
+
entries
|
|
154
|
+
.filter((e) => e.isDirectory())
|
|
155
|
+
.map((e) => expandFrom(path.join(dir, e.name), idx))
|
|
180
156
|
);
|
|
157
|
+
|
|
158
|
+
return [...(await expandFrom(dir, idx + 1)), ...nested.flat()];
|
|
181
159
|
}
|
|
182
160
|
|
|
183
|
-
|
|
161
|
+
const nested = await Promise.all(
|
|
162
|
+
entries
|
|
163
|
+
.filter((e) => e.isDirectory() && matchSegment(seg, e.name))
|
|
164
|
+
.map((e) => expandFrom(path.join(dir, e.name), idx + 1))
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
return nested.flat();
|
|
184
168
|
}
|
|
185
169
|
|
|
186
170
|
const dirs = await expandFrom(root, 0);
|
|
187
|
-
const pkgDirs: string[] = [];
|
|
188
171
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
172
|
+
const pkgDirs = (
|
|
173
|
+
await Promise.all(
|
|
174
|
+
dirs.map(async (d) =>
|
|
175
|
+
(await exists(path.join(d, 'package.json'))) ? d : null
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
).filter(Boolean) as string[];
|
|
194
179
|
|
|
195
|
-
return
|
|
180
|
+
return [...new Set(pkgDirs)];
|
|
196
181
|
}
|
|
197
182
|
|
|
198
183
|
async function findWorkspacePackageDirs(
|
|
199
184
|
repoRoot: string,
|
|
200
|
-
|
|
185
|
+
workspacePatterns: string[]
|
|
201
186
|
): Promise<string[]> {
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return Array.from(new Set(dirs));
|
|
187
|
+
const resolved = await Promise.all(
|
|
188
|
+
workspacePatterns.map((p) => expandWorkspacePattern(repoRoot, p))
|
|
189
|
+
);
|
|
190
|
+
return [...new Set(resolved.flat())];
|
|
209
191
|
}
|
|
210
192
|
|
|
211
193
|
/* -------------------------------------------------------------------------- */
|
|
212
|
-
/*
|
|
194
|
+
/* Mono config + commands */
|
|
213
195
|
/* -------------------------------------------------------------------------- */
|
|
214
196
|
|
|
215
197
|
async function readMonoConfig(): Promise<MonoConfig | null> {
|
|
216
|
-
|
|
217
|
-
const configPath = path.resolve(
|
|
218
|
-
process.cwd(),
|
|
219
|
-
path.join(MONO_DIR, 'config.json')
|
|
220
|
-
);
|
|
198
|
+
const configPath = path.join(MONO_DIR, 'config.json');
|
|
221
199
|
if (!(await exists(configPath))) return null;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
} catch {
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
200
|
+
return {
|
|
201
|
+
path: configPath,
|
|
202
|
+
config: await readJson<MonoConfig['config']>(configPath),
|
|
203
|
+
};
|
|
229
204
|
}
|
|
230
205
|
|
|
231
206
|
function commandNameFromFile(filePath: string): string {
|
|
@@ -233,150 +208,100 @@ function commandNameFromFile(filePath: string): string {
|
|
|
233
208
|
}
|
|
234
209
|
|
|
235
210
|
async function readMonoCommands(): Promise<MonoCommand[]> {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
});
|
|
257
|
-
} catch {
|
|
258
|
-
/* ignore invalid JSON */
|
|
259
|
-
}
|
|
260
|
-
}
|
|
211
|
+
if (!(await exists(MONO_DIR))) return [];
|
|
212
|
+
|
|
213
|
+
const entries = await listDir(MONO_DIR);
|
|
214
|
+
|
|
215
|
+
const commands = await Promise.all(
|
|
216
|
+
entries
|
|
217
|
+
.filter(
|
|
218
|
+
(e) =>
|
|
219
|
+
e.isFile() && e.name.endsWith('.json') && e.name !== 'config.json'
|
|
220
|
+
)
|
|
221
|
+
.map(async (e) => {
|
|
222
|
+
const file = path.join(MONO_DIR, e.name);
|
|
223
|
+
const json = await readJson<MonoCommand['json']>(file);
|
|
224
|
+
return {
|
|
225
|
+
name: commandNameFromFile(file),
|
|
226
|
+
file,
|
|
227
|
+
json,
|
|
228
|
+
};
|
|
229
|
+
})
|
|
230
|
+
);
|
|
261
231
|
|
|
262
232
|
return commands.sort((a, b) => a.name.localeCompare(b.name));
|
|
263
233
|
}
|
|
264
234
|
|
|
265
235
|
/* -------------------------------------------------------------------------- */
|
|
266
|
-
/*
|
|
236
|
+
/* Options parsing */
|
|
267
237
|
/* -------------------------------------------------------------------------- */
|
|
268
238
|
|
|
269
239
|
function parseOptionsSchema(optionsObj: unknown): OptionSchema[] {
|
|
270
240
|
if (!isObject(optionsObj)) return [];
|
|
271
241
|
|
|
272
|
-
|
|
273
|
-
([key, raw]) => {
|
|
242
|
+
return Object.entries(optionsObj)
|
|
243
|
+
.map(([key, raw]) => {
|
|
274
244
|
const o = isObject(raw) ? raw : {};
|
|
275
|
-
const hasType = typeof o.type === 'string'
|
|
245
|
+
const hasType = typeof o.type === 'string';
|
|
276
246
|
|
|
277
247
|
return {
|
|
278
248
|
key,
|
|
279
|
-
kind: hasType ? 'value' : 'boolean',
|
|
249
|
+
kind: hasType ? ('value' as const) : ('boolean' as const),
|
|
280
250
|
type: hasType ? (o.type as string) : 'boolean',
|
|
281
251
|
description: typeof o.description === 'string' ? o.description : '',
|
|
282
252
|
shortcut: typeof o.shortcut === 'string' ? o.shortcut : '',
|
|
283
253
|
default: o.default,
|
|
284
|
-
allowed:
|
|
254
|
+
allowed:
|
|
255
|
+
Array.isArray(o.options) ?
|
|
256
|
+
o.options.filter((x): x is string => typeof x === 'string')
|
|
257
|
+
: null,
|
|
285
258
|
allowAll: o.allowAll === true,
|
|
286
259
|
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
260
|
+
})
|
|
261
|
+
.sort((a, b) => a.key.localeCompare(b.key));
|
|
291
262
|
}
|
|
292
263
|
|
|
293
264
|
/* -------------------------------------------------------------------------- */
|
|
294
|
-
/*
|
|
295
|
-
/* -------------------------------------------------------------------------- */
|
|
296
|
-
|
|
297
|
-
function buildUsageExample(
|
|
298
|
-
commandName: string,
|
|
299
|
-
cmdJson: JsonObject,
|
|
300
|
-
options: OptionSchema[]
|
|
301
|
-
): string {
|
|
302
|
-
const arg = cmdJson.argument;
|
|
303
|
-
const hasArg = isObject(arg);
|
|
304
|
-
|
|
305
|
-
const parts: string[] = [`yarn mono ${commandName}`];
|
|
306
|
-
|
|
307
|
-
if (hasArg) parts.push(`<${commandName}-arg>`);
|
|
308
|
-
|
|
309
|
-
const valueOpts = options.filter((o) => o.kind === 'value');
|
|
310
|
-
const boolOpts = options.filter((o) => o.kind === 'boolean');
|
|
311
|
-
|
|
312
|
-
for (const o of valueOpts.slice(0, 2)) {
|
|
313
|
-
const value =
|
|
314
|
-
o.default !== undefined ?
|
|
315
|
-
String(o.default)
|
|
316
|
-
: (o.allowed?.[0] ?? '<value>');
|
|
317
|
-
parts.push(`--${o.key} ${value}`);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (boolOpts[0]) {
|
|
321
|
-
parts.push(`--${boolOpts[0].key}`);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return parts.join(' ');
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/* -------------------------------------------------------------------------- */
|
|
328
|
-
/* Main */
|
|
265
|
+
/* Main */
|
|
329
266
|
/* -------------------------------------------------------------------------- */
|
|
330
267
|
|
|
331
268
|
async function main(): Promise<void> {
|
|
332
|
-
// Always resolve all paths relative to working directory
|
|
333
269
|
if (!(await exists(ROOT_PKG_JSON))) {
|
|
334
|
-
throw new Error(`Missing ${ROOT_PKG_JSON}`);
|
|
270
|
+
throw new Error(`Missing: ${ROOT_PKG_JSON}`);
|
|
335
271
|
}
|
|
336
272
|
|
|
337
273
|
await ensureParentDir(OUTPUT_PATH);
|
|
338
274
|
|
|
339
|
-
const rootPkg = await readJson<
|
|
275
|
+
const rootPkg = await readJson<any>(ROOT_PKG_JSON);
|
|
340
276
|
const workspacePatterns = normalizeWorkspacePatterns(rootPkg.workspaces);
|
|
341
277
|
|
|
342
278
|
const monoConfig = await readMonoConfig();
|
|
343
279
|
const monoCommands = await readMonoCommands();
|
|
344
|
-
|
|
345
280
|
const pkgDirs = await findWorkspacePackageDirs(REPO_ROOT, workspacePatterns);
|
|
346
281
|
|
|
347
|
-
const packages: PackageInfo[] =
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const pkg = await readJson<{
|
|
352
|
-
name?: string;
|
|
353
|
-
scripts?: Record<string, string>;
|
|
354
|
-
}>(path.join(dir, 'package.json'));
|
|
355
|
-
|
|
356
|
-
packages.push({
|
|
282
|
+
const packages: PackageInfo[] = await Promise.all(
|
|
283
|
+
pkgDirs.map(async (dir) => {
|
|
284
|
+
const pj = await readJson<any>(path.join(dir, 'package.json'));
|
|
285
|
+
return {
|
|
357
286
|
name:
|
|
358
|
-
|
|
359
|
-
toPosix(path.relative(REPO_ROOT, dir))
|
|
287
|
+
pj.name ||
|
|
288
|
+
toPosix(path.relative(REPO_ROOT, dir)) ||
|
|
360
289
|
path.basename(dir),
|
|
361
290
|
dir,
|
|
362
|
-
scripts:
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
}
|
|
291
|
+
scripts: pj.scripts ?? {},
|
|
292
|
+
};
|
|
293
|
+
})
|
|
294
|
+
);
|
|
368
295
|
|
|
369
296
|
const parts: string[] = [];
|
|
370
297
|
|
|
371
298
|
parts.push(`# Mono Command-Line Reference
|
|
372
299
|
|
|
373
|
-
> Generated by \`scripts/generate-readme.
|
|
300
|
+
> Generated by \`scripts/generate-readme.mjs\`.
|
|
301
|
+
> Update \`.mono/config.json\`, \`.mono/*.json\`, and workspace package scripts to change this output.
|
|
374
302
|
|
|
375
303
|
`);
|
|
376
304
|
|
|
377
|
-
// Reuse your existing formatters here
|
|
378
|
-
// (unchanged logic, now fully typed)
|
|
379
|
-
|
|
380
305
|
const docsIndex = await generateDocsIndex({
|
|
381
306
|
docsDir: path.join(REPO_ROOT, 'docs'),
|
|
382
307
|
excludeFile: 'command-line.md',
|
|
@@ -384,7 +309,6 @@ async function main(): Promise<void> {
|
|
|
384
309
|
|
|
385
310
|
parts.push(docsIndex);
|
|
386
311
|
|
|
387
|
-
await ensureParentDir(OUTPUT_README);
|
|
388
312
|
await fs.writeFile(OUTPUT_README, parts.join('\n'), 'utf8');
|
|
389
313
|
|
|
390
314
|
console.log(`Generated: ${OUTPUT_README}`);
|
|
@@ -394,6 +318,6 @@ async function main(): Promise<void> {
|
|
|
394
318
|
}
|
|
395
319
|
|
|
396
320
|
main().catch((err) => {
|
|
397
|
-
console.error(err
|
|
398
|
-
process.
|
|
321
|
+
console.error(err);
|
|
322
|
+
process.exitCode = 1;
|
|
399
323
|
});
|