@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.
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // scripts/generate-readme.ts
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
- /* Path helpers */
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
- /* Utils */
20
+ /* Utils */
22
21
  /* -------------------------------------------------------------------------- */
23
22
  async function ensureParentDir(filePath) {
24
- // Always resolve parent dir relative to working directory
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 v !== null && typeof v === 'object' && !Array.isArray(v);
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
- // Always resolve filePath relative to working directory
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
- // Always resolve dir relative to working directory
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(value) {
64
- return String(value ?? '').replaceAll('`', '\\`');
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((line) => pad + line)
62
+ .map((l) => pad + l)
71
63
  .join('\n');
72
64
  }
73
65
  /* -------------------------------------------------------------------------- */
74
- /* Workspace glob pattern expansion */
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
- const regex = new RegExp(`^${escaped.replaceAll('*', '.*')}$`);
83
- return regex.test(name);
74
+ return new RegExp(`^${escaped.replaceAll('*', '.*')}$`).test(name);
84
75
  }
85
76
  async function expandWorkspacePattern(root, pattern) {
86
- const segments = toPosix(pattern).split('/').filter(Boolean);
87
- async function expandFrom(dir, index) {
88
- // Always resolve dir relative to working directory
89
- const absDir = node_path_1.default.resolve(process.cwd(), dir);
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(absDir, { withFileTypes: true })
82
+ .readdir(dir, { withFileTypes: true })
108
83
  .catch(() => []);
109
- const results = [];
110
- for (const entry of entries) {
111
- if (!entry.isDirectory())
112
- continue;
113
- if (!matchSegment(seg, entry.name))
114
- continue;
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
- return results;
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
- for (const d of dirs) {
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, patterns) {
129
- const dirs = [];
130
- for (const pat of patterns) {
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
- /* .mono configuration */
105
+ /* Mono config + commands */
137
106
  /* -------------------------------------------------------------------------- */
138
107
  async function readMonoConfig() {
139
- // Always resolve configPath relative to working directory
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
- try {
144
- const config = await readJson(configPath);
145
- return { path: configPath, config };
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
- // Always resolve MONO_DIR relative to working directory
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(monoDirAbs);
160
- const jsonFiles = entries
161
- .filter((e) => e.isFile() && e.name.endsWith('.json'))
162
- .map((e) => node_path_1.default.join(monoDirAbs, e.name))
163
- .filter((p) => node_path_1.default.basename(p) !== 'config.json');
164
- const commands = [];
165
- for (const file of jsonFiles) {
166
- try {
167
- const json = await readJson(file);
168
- commands.push({
169
- name: commandNameFromFile(file),
170
- file,
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
- /* Options schema parsing */
137
+ /* Options parsing */
182
138
  /* -------------------------------------------------------------------------- */
183
139
  function parseOptionsSchema(optionsObj) {
184
140
  if (!isObject(optionsObj))
185
141
  return [];
186
- const entries = Object.entries(optionsObj).map(([key, raw]) => {
142
+ return Object.entries(optionsObj)
143
+ .map(([key, raw]) => {
187
144
  const o = isObject(raw) ? raw : {};
188
- const hasType = typeof o.type === 'string' && o.type.length > 0;
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) ? o.options : null,
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
- return entries.sort((a, b) => a.key.localeCompare(b.key));
158
+ })
159
+ .sort((a, b) => a.key.localeCompare(b.key));
201
160
  }
202
161
  /* -------------------------------------------------------------------------- */
203
- /* Formatting */
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
- for (const dir of pkgDirs) {
240
- try {
241
- const pkg = await readJson(node_path_1.default.join(dir, 'package.json'));
242
- packages.push({
243
- name: pkg.name ??
244
- toPosix(node_path_1.default.relative(REPO_ROOT, dir)) ??
245
- node_path_1.default.basename(dir),
246
- dir,
247
- scripts: pkg.scripts ?? {},
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.ts\`.
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 instanceof Error ? err.stack : err);
276
- process.exit(1);
203
+ console.error(err);
204
+ process.exitCode = 1;
277
205
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mono-labs/cli",
3
- "version": "0.0.206",
3
+ "version": "0.0.207",
4
4
  "description": "A CLI tool for building and deploying projects",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/types.d.ts",
@@ -1,31 +1,15 @@
1
- // scripts/generate-readme.ts
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
- /* Path helpers */
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 JsonObject = Record<string, unknown>;
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
- interface MonoCommand {
24
+ type MonoCommand = {
41
25
  name: string;
42
26
  file: string;
43
- json: JsonObject;
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
- interface PackageInfo {
42
+ type PackageInfo = {
47
43
  name: string;
48
44
  dir: string;
49
45
  scripts: Record<string, string>;
50
- }
46
+ };
51
47
 
52
- interface OptionSchema {
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
- /* Utils */
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
- // Always resolve parent dir relative to working directory
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 JsonObject {
83
- return v !== null && typeof v === 'object' && !Array.isArray(v);
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 = unknown>(filePath: string): Promise<T> {
91
- // Always resolve filePath relative to working directory
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
- // Always resolve dir relative to working directory
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(value: unknown): string {
115
- return String(value ?? '').replaceAll('`', '\\`');
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((line) => pad + line)
121
+ .map((l) => pad + l)
123
122
  .join('\n');
124
123
  }
125
124
 
126
125
  /* -------------------------------------------------------------------------- */
127
- /* Workspace glob pattern expansion */
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
- const regex = new RegExp(`^${escaped.replaceAll('*', '.*')}$`);
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 segments = toPosix(pattern).split('/').filter(Boolean);
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
- const seg = segments[index];
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(absDir, { withFileTypes: true })
146
+ .readdir(dir, { withFileTypes: true })
171
147
  .catch(() => []);
172
148
 
173
- const results: string[] = [];
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
- results.push(
179
- ...(await expandFrom(path.join(absDir, entry.name), index + 1))
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
- return results;
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
- for (const d of dirs) {
190
- if (await exists(path.join(d, 'package.json'))) {
191
- pkgDirs.push(d);
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 Array.from(new Set(pkgDirs));
180
+ return [...new Set(pkgDirs)];
196
181
  }
197
182
 
198
183
  async function findWorkspacePackageDirs(
199
184
  repoRoot: string,
200
- patterns: string[]
185
+ workspacePatterns: string[]
201
186
  ): Promise<string[]> {
202
- const dirs: string[] = [];
203
-
204
- for (const pat of patterns) {
205
- dirs.push(...(await expandWorkspacePattern(repoRoot, pat)));
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
- /* .mono configuration */
194
+ /* Mono config + commands */
213
195
  /* -------------------------------------------------------------------------- */
214
196
 
215
197
  async function readMonoConfig(): Promise<MonoConfig | null> {
216
- // Always resolve configPath relative to working directory
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
- try {
224
- const config = await readJson<MonoConfig['config']>(configPath);
225
- return { path: configPath, config };
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
- // Always resolve MONO_DIR relative to working directory
237
- const monoDirAbs = path.resolve(process.cwd(), MONO_DIR);
238
- if (!(await exists(monoDirAbs))) return [];
239
-
240
- const entries = await listDir(monoDirAbs);
241
-
242
- const jsonFiles = entries
243
- .filter((e) => e.isFile() && e.name.endsWith('.json'))
244
- .map((e) => path.join(monoDirAbs, e.name))
245
- .filter((p) => path.basename(p) !== 'config.json');
246
-
247
- const commands: MonoCommand[] = [];
248
-
249
- for (const file of jsonFiles) {
250
- try {
251
- const json = await readJson<JsonObject>(file);
252
- commands.push({
253
- name: commandNameFromFile(file),
254
- file,
255
- json,
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
- /* Options schema parsing */
236
+ /* Options parsing */
267
237
  /* -------------------------------------------------------------------------- */
268
238
 
269
239
  function parseOptionsSchema(optionsObj: unknown): OptionSchema[] {
270
240
  if (!isObject(optionsObj)) return [];
271
241
 
272
- const entries: OptionSchema[] = Object.entries(optionsObj).map(
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' && o.type.length > 0;
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: Array.isArray(o.options) ? (o.options as string[]) : null,
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
- /* Formatting */
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<{ workspaces?: unknown }>(ROOT_PKG_JSON);
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
- for (const dir of pkgDirs) {
350
- try {
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
- pkg.name ??
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: pkg.scripts ?? {},
363
- });
364
- } catch {
365
- /* ignore */
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.ts\`.
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 instanceof Error ? err.stack : err);
398
- process.exit(1);
321
+ console.error(err);
322
+ process.exitCode = 1;
399
323
  });