@cleocode/cleo 2026.5.29 → 2026.5.33
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/cli/index.js +26840 -24057
- package/dist/cli/index.js.map +4 -4
- package/package.json +13 -9
- package/scripts/generate-command-manifest.mjs +147 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleocode/cleo",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.33",
|
|
4
4
|
"description": "CLEO CLI — the assembled product consuming @cleocode/core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cli/index.js",
|
|
@@ -29,16 +29,17 @@
|
|
|
29
29
|
"tree-sitter-ruby": "^0.23.1",
|
|
30
30
|
"tree-sitter-rust": "0.23.1",
|
|
31
31
|
"tree-sitter-typescript": "^0.23.2",
|
|
32
|
-
"@cleocode/
|
|
33
|
-
"@cleocode/
|
|
34
|
-
"@cleocode/
|
|
35
|
-
"@cleocode/
|
|
36
|
-
"@cleocode/
|
|
37
|
-
"@cleocode/
|
|
38
|
-
"@cleocode/
|
|
32
|
+
"@cleocode/animations": "2026.5.33",
|
|
33
|
+
"@cleocode/caamp": "2026.5.33",
|
|
34
|
+
"@cleocode/cant": "2026.5.33",
|
|
35
|
+
"@cleocode/contracts": "2026.5.33",
|
|
36
|
+
"@cleocode/lafs": "2026.5.33",
|
|
37
|
+
"@cleocode/nexus": "2026.5.33",
|
|
38
|
+
"@cleocode/playbooks": "2026.5.33",
|
|
39
|
+
"@cleocode/runtime": "2026.5.33"
|
|
39
40
|
},
|
|
40
41
|
"peerDependencies": {
|
|
41
|
-
"@cleocode/core": "2026.5.
|
|
42
|
+
"@cleocode/core": "2026.5.33"
|
|
42
43
|
},
|
|
43
44
|
"peerDependenciesMeta": {
|
|
44
45
|
"@cleocode/core": {
|
|
@@ -69,8 +70,11 @@
|
|
|
69
70
|
"directory": "packages/cleo"
|
|
70
71
|
},
|
|
71
72
|
"scripts": {
|
|
73
|
+
"gen:manifest": "node scripts/generate-command-manifest.mjs",
|
|
74
|
+
"prebuild": "node scripts/generate-command-manifest.mjs",
|
|
72
75
|
"build": "tsc",
|
|
73
76
|
"postbuild": "node scripts/assert-shebang.mjs",
|
|
77
|
+
"pretypecheck": "node scripts/generate-command-manifest.mjs",
|
|
74
78
|
"typecheck": "tsc --noEmit",
|
|
75
79
|
"test": "cd ../.. && vitest run packages/cleo/src",
|
|
76
80
|
"test:integration": "cd ../.. && vitest run --config packages/cleo/vitest.integration.config.ts",
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Build-time generator: scans `packages/cleo/src/cli/commands/*.ts` and emits
|
|
4
|
+
* `packages/cleo/src/cli/generated/command-manifest.ts` — a static index of
|
|
5
|
+
* `{ exportName, importPath, meta: { name, description } }` for every exported
|
|
6
|
+
* `xxxCommand` constant.
|
|
7
|
+
*
|
|
8
|
+
* Why
|
|
9
|
+
* ----
|
|
10
|
+
* The CLI entry point uses this manifest to register lazy command wrappers:
|
|
11
|
+
* `meta` is read eagerly (so `--help` works without loading every command),
|
|
12
|
+
* and the actual command module is `import()`-ed only when the user runs it.
|
|
13
|
+
*
|
|
14
|
+
* Each command file remains the single source of truth for its own metadata —
|
|
15
|
+
* this script just bridges that to a static index the bundler can analyse.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readdirSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
19
|
+
import { dirname, join, resolve } from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
|
|
22
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const PKG_ROOT = resolve(__dirname, '..');
|
|
24
|
+
const COMMANDS_DIR = join(PKG_ROOT, 'src', 'cli', 'commands');
|
|
25
|
+
const OUTPUT_DIR = join(PKG_ROOT, 'src', 'cli', 'generated');
|
|
26
|
+
const OUTPUT_FILE = join(OUTPUT_DIR, 'command-manifest.ts');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Match `export const fooCommand = defineCommand({` (with optional `as CommandDef`
|
|
30
|
+
* type assertions) plus the immediately-following `meta: { name: '...', description: '...' }`
|
|
31
|
+
* block. Multi-line descriptions are captured inside the same string literal.
|
|
32
|
+
*/
|
|
33
|
+
const COMMAND_BLOCK_RE =
|
|
34
|
+
/export\s+const\s+(\w+Command)\s*(?::\s*\w+)?\s*=\s*defineCommand\s*\(\s*\{[\s\S]*?meta\s*:\s*\{([\s\S]*?)\}/g;
|
|
35
|
+
|
|
36
|
+
const NAME_RE = /name\s*:\s*['"`]([^'"`]+)['"`]/;
|
|
37
|
+
const DESC_RE = /description\s*:\s*['"`]((?:[^'"`\\]|\\.)*)['"`]/;
|
|
38
|
+
|
|
39
|
+
function extractFromFile(file) {
|
|
40
|
+
const text = readFileSync(file, 'utf8');
|
|
41
|
+
const results = [];
|
|
42
|
+
let match;
|
|
43
|
+
while ((match = COMMAND_BLOCK_RE.exec(text)) !== null) {
|
|
44
|
+
const exportName = match[1];
|
|
45
|
+
const metaBlock = match[2];
|
|
46
|
+
const nameMatch = metaBlock.match(NAME_RE);
|
|
47
|
+
const descMatch = metaBlock.match(DESC_RE);
|
|
48
|
+
if (!nameMatch) continue; // skip if no name field
|
|
49
|
+
results.push({
|
|
50
|
+
exportName,
|
|
51
|
+
name: nameMatch[1],
|
|
52
|
+
description: descMatch ? descMatch[1] : '',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return results;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function main() {
|
|
59
|
+
const files = readdirSync(COMMANDS_DIR)
|
|
60
|
+
.filter((f) => f.endsWith('.ts') && !f.endsWith('.test.ts'))
|
|
61
|
+
.sort();
|
|
62
|
+
|
|
63
|
+
const entries = [];
|
|
64
|
+
for (const f of files) {
|
|
65
|
+
const path = join(COMMANDS_DIR, f);
|
|
66
|
+
const fileEntries = extractFromFile(path);
|
|
67
|
+
for (const e of fileEntries) {
|
|
68
|
+
entries.push({
|
|
69
|
+
...e,
|
|
70
|
+
// Import path used at runtime — relative to cli/generated/
|
|
71
|
+
importPath: `../commands/${f.replace(/\.ts$/, '.js')}`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (entries.length === 0) {
|
|
77
|
+
console.error('generate-command-manifest: no commands found in', COMMANDS_DIR);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Detect duplicate command names so we surface conflicts at build time.
|
|
82
|
+
const seenNames = new Map();
|
|
83
|
+
for (const e of entries) {
|
|
84
|
+
const prev = seenNames.get(e.name);
|
|
85
|
+
if (prev && prev.exportName !== e.exportName) {
|
|
86
|
+
console.error(
|
|
87
|
+
`generate-command-manifest: duplicate command name "${e.name}" — ` +
|
|
88
|
+
`${prev.exportName} (${prev.importPath}) and ${e.exportName} (${e.importPath})`,
|
|
89
|
+
);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
seenNames.set(e.name, e);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const banner = `/**
|
|
96
|
+
* AUTO-GENERATED by packages/cleo/scripts/generate-command-manifest.mjs.
|
|
97
|
+
* DO NOT EDIT MANUALLY — regenerate via \`pnpm --filter @cleocode/cleo run gen:manifest\`.
|
|
98
|
+
*
|
|
99
|
+
* This file is the eagerly-loaded index of CLI commands. Each entry carries the
|
|
100
|
+
* static \`meta\` plus a dynamic \`load()\` that imports the command on demand.
|
|
101
|
+
*
|
|
102
|
+
* The point: \`cleo --help\` and \`cleo --version\` no longer transitively load
|
|
103
|
+
* 111 command modules + their CORE/SQLite/drizzle dependency trees. Only the
|
|
104
|
+
* matched command's module is loaded at run time.
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
import type { CommandDef } from 'citty';
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* One entry per exported \`xxxCommand\` discovered in src/cli/commands/.
|
|
111
|
+
* Identified by both \`exportName\` (some files export multiple commands)
|
|
112
|
+
* and \`name\` (the user-facing CLI subcommand).
|
|
113
|
+
*/
|
|
114
|
+
export interface CommandManifestEntry {
|
|
115
|
+
/** The exported binding inside the command module (e.g. \`findCommand\`). */
|
|
116
|
+
readonly exportName: string;
|
|
117
|
+
/** The user-facing CLI name (e.g. \`find\`). Matches \`meta.name\` in source. */
|
|
118
|
+
readonly name: string;
|
|
119
|
+
/** Short description shown in \`cleo --help\`. Matches \`meta.description\` in source. */
|
|
120
|
+
readonly description: string;
|
|
121
|
+
/** Dynamic import — resolves to the full \`CommandDef\` only when invoked. */
|
|
122
|
+
readonly load: () => Promise<CommandDef>;
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
const lines = [banner, '', 'export const COMMAND_MANIFEST: readonly CommandManifestEntry[] = [', ];
|
|
127
|
+
for (const e of entries) {
|
|
128
|
+
const desc = e.description.replace(/'/g, "\\'");
|
|
129
|
+
lines.push(
|
|
130
|
+
` {`,
|
|
131
|
+
` exportName: '${e.exportName}',`,
|
|
132
|
+
` name: '${e.name}',`,
|
|
133
|
+
` description: '${desc}',`,
|
|
134
|
+
` load: async () => (await import('${e.importPath}')).${e.exportName} as CommandDef,`,
|
|
135
|
+
` },`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
lines.push('] as const;', '');
|
|
139
|
+
|
|
140
|
+
mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
141
|
+
writeFileSync(OUTPUT_FILE, lines.join('\n'), 'utf8');
|
|
142
|
+
console.info(
|
|
143
|
+
`generate-command-manifest: wrote ${entries.length} entries to ${OUTPUT_FILE.replace(PKG_ROOT + '/', '')}`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main();
|