genlayer 0.29.0 → 0.30.0

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.
@@ -0,0 +1,121 @@
1
+ name: Generate CLI Docs and PR to genlayer-docs
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ release:
6
+ types: [published]
7
+
8
+ jobs:
9
+ generate-and-sync:
10
+ # Skip for pre-releases (tags containing '-') unless manually dispatched
11
+ if: github.event_name != 'release' || !contains(github.event.release.tag_name, '-')
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: read
15
+ steps:
16
+ - name: Checkout CLI repo
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Setup Node.js
20
+ uses: actions/setup-node@v4
21
+ with:
22
+ node-version: '20'
23
+ cache: 'npm'
24
+
25
+ - name: Install dependencies
26
+ run: npm ci
27
+
28
+ - name: Build
29
+ run: npm run build
30
+
31
+ - name: Determine version for docs
32
+ id: version
33
+ run: |
34
+ if [ "${{ github.event_name }}" = "release" ]; then
35
+ echo "value=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
36
+ else
37
+ # Prefer package.json version when not a release event
38
+ echo "value=$(node -p \"require('./package.json').version\")" >> $GITHUB_OUTPUT
39
+ fi
40
+
41
+ - name: Generate CLI docs (MDX)
42
+ env:
43
+ DOCS_CLEAN: 'true'
44
+ DOCS_VERSION: ${{ steps.version.outputs.value }}
45
+ run: node scripts/generate-cli-docs.mjs | cat
46
+
47
+ - name: Set up Git (for committing to CLI repo)
48
+ run: |
49
+ git config user.name "github-actions[bot]"
50
+ git config user.email "github-actions[bot]@users.noreply.github.com"
51
+
52
+ - name: Commit and push docs back to CLI repo (non-beta releases)
53
+ if: github.event_name == 'release' && !contains(github.event.release.tag_name, '-')
54
+ run: |
55
+ set -euo pipefail
56
+ if [ -n "$(git status --porcelain docs/api-references || true)" ]; then
57
+ git add docs/api-references
58
+ VERSION=${{ steps.version.outputs.value }}
59
+ git commit -m "docs(cli): update API reference for ${VERSION}"
60
+ git push
61
+ else
62
+ echo "No docs changes to commit"
63
+ fi
64
+
65
+ - name: Checkout docs repo
66
+ uses: actions/checkout@v4
67
+ with:
68
+ repository: genlayerlabs/genlayer-docs
69
+ token: ${{ secrets.DOCS_REPO_TOKEN || secrets.GITHUB_TOKEN }}
70
+ path: docs-repo
71
+ fetch-depth: 0
72
+
73
+ - name: Prepare branch
74
+ working-directory: docs-repo
75
+ run: |
76
+ set -euo pipefail
77
+ git config user.name "github-actions[bot]"
78
+ git config user.email "github-actions[bot]@users.noreply.github.com"
79
+ BRANCH="docs/cli/${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }}"
80
+ git switch -c "$BRANCH" || git switch "$BRANCH"
81
+ echo "BRANCH=$BRANCH" >> $GITHUB_ENV
82
+
83
+ - name: Sync CLI docs into docs repo
84
+ run: |
85
+ set -euo pipefail
86
+ mkdir -p docs-repo/pages/api-references/genlayer-cli
87
+ rsync -a --delete "${{ github.workspace }}/docs/api-references/" docs-repo/pages/api-references/genlayer-cli/
88
+ echo "Synced files:" && ls -la docs-repo/pages/api-references/genlayer-cli | cat
89
+
90
+ - name: Commit changes
91
+ working-directory: docs-repo
92
+ run: |
93
+ set -euo pipefail
94
+ if [ -n "$(git status --porcelain)" ]; then
95
+ git add pages/api-references/genlayer-cli
96
+ git commit -m "docs(cli): sync API reference ${VERSION:-${{ env.VERSION }}}"
97
+ git push --set-upstream origin "$BRANCH"
98
+ echo "HAS_CHANGES=true" >> $GITHUB_ENV
99
+ else
100
+ echo "No changes to commit"
101
+ echo "HAS_CHANGES=false" >> $GITHUB_ENV
102
+ fi
103
+
104
+ - name: Create PR in docs repo
105
+ if: env.HAS_CHANGES == 'true'
106
+ uses: peter-evans/create-pull-request@v6
107
+ with:
108
+ token: ${{ secrets.DOCS_REPO_TOKEN || secrets.GITHUB_TOKEN }}
109
+ path: docs-repo
110
+ commit-message: "docs(cli): sync API reference ${{ steps.version.outputs.value }}"
111
+ branch: ${{ env.BRANCH }}
112
+ title: "docs(cli): sync CLI API reference ${{ steps.version.outputs.value }}"
113
+ body: |
114
+ This PR updates the GenlayerCLI API Reference generated automatically from `${{ github.repository }}`.
115
+
116
+ - Version: `${{ steps.version.outputs.value }}`
117
+ - Source commit: `${{ github.sha }}`
118
+ - Trigger: `${{ github.event_name }}`
119
+ labels: documentation, cli
120
+
121
+
@@ -5,6 +5,8 @@ on:
5
5
  push:
6
6
  branches:
7
7
  - main
8
+ paths-ignore:
9
+ - 'docs/**'
8
10
 
9
11
  jobs:
10
12
  release:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.30.0 (2025-09-03)
4
+
5
+ ### Features
6
+
7
+ * genlayercli api reference auto generated ([#247](https://github.com/yeagerai/genlayer-cli/issues/247)) ([b08c342](https://github.com/yeagerai/genlayer-cli/commit/b08c34218b9f02a1d8d7f1c0b532ab55cc4ca5af))
8
+
3
9
  ## 0.29.0 (2025-09-03)
4
10
 
5
11
  ### Features
package/dist/index.js CHANGED
@@ -17856,7 +17856,7 @@ var require_semver2 = __commonJS({
17856
17856
  import { program } from "commander";
17857
17857
 
17858
17858
  // package.json
17859
- var version = "0.29.0";
17859
+ var version = "0.30.0";
17860
17860
  var package_default = {
17861
17861
  name: "genlayer",
17862
17862
  version,
@@ -17874,7 +17874,8 @@ var package_default = {
17874
17874
  build: "cross-env NODE_ENV=production node esbuild.config.js",
17875
17875
  release: "release-it --ci",
17876
17876
  "release-beta": "release-it --ci --preRelease=beta",
17877
- postinstall: "node ./scripts/postinstall.js"
17877
+ postinstall: "node ./scripts/postinstall.js",
17878
+ "docs:cli": "node scripts/generate-cli-docs.mjs"
17878
17879
  },
17879
17880
  repository: {
17880
17881
  type: "git",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genlayer",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "GenLayer Command Line Tool",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -15,7 +15,8 @@
15
15
  "build": "cross-env NODE_ENV=production node esbuild.config.js",
16
16
  "release": "release-it --ci",
17
17
  "release-beta": "release-it --ci --preRelease=beta",
18
- "postinstall": "node ./scripts/postinstall.js"
18
+ "postinstall": "node ./scripts/postinstall.js",
19
+ "docs:cli": "node scripts/generate-cli-docs.mjs"
19
20
  },
20
21
  "repository": {
21
22
  "type": "git",
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env node
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { spawnSync } from 'node:child_process';
6
+
7
+ function escapeMdx(text) {
8
+ if (!text) return '';
9
+ return String(text).replace(/</g, '&lt;').replace(/>/g, '&gt;');
10
+ }
11
+
12
+ function formatArg(arg) {
13
+ const base = arg.variadic ? `${arg.name}...` : arg.name;
14
+ return arg.required ? `<${base}>` : `[${base}]`;
15
+ }
16
+ function toSlug(text) {
17
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
18
+ }
19
+
20
+ function makeCommandFilepath(commandPath) {
21
+ const parts = commandPath.split(' ');
22
+ if (parts.length === 1) return { relDir: '', filename: `${toSlug(parts[0])}.mdx` };
23
+ return { relDir: parts.slice(0, -1).map(toSlug).join('/'), filename: `${toSlug(parts[parts.length - 1])}.mdx` };
24
+ }
25
+
26
+ function renderOptionsTable(options) {
27
+ if (!options.length) return 'No options.\n';
28
+ const rows = options
29
+ .map((o) => `| ${escapeMdx(o.short ?? '')} | ${escapeMdx(o.long ?? '')} | ${escapeMdx(o.description ?? '')} | ${o.required ? 'Yes' : 'No'} | ${o.defaultValue === undefined ? '' : `\`${escapeMdx(String(o.defaultValue))}\``} |`)
30
+ .join('\n');
31
+ return ['| Short | Long | Description | Required | Default |', '| --- | --- | --- | :---: | --- |', rows].join('\n');
32
+ }
33
+
34
+ function renderArgsList(args) {
35
+ if (!args.length) return 'No positional arguments.\n';
36
+ return args.map((a) => `- \`${formatArg(a)}\``).join('\n');
37
+ }
38
+
39
+ function generatePageForCommand(help, programName, commandPath) {
40
+ const { description, usage, args, options, subcommands } = help;
41
+ const title = commandPath || programName;
42
+ const header = `---\ntitle: ${escapeMdx(title)}\n---`;
43
+ const parts = [header];
44
+ if (description) parts.push('', description);
45
+ if (usage) parts.push('', '### Usage', '', `\`${usage}\``);
46
+ if (args && args.length) parts.push('', '### Arguments', '', renderArgsList(args));
47
+ parts.push('', '### Options', '', renderOptionsTable(options || []));
48
+ if (subcommands && subcommands.length) {
49
+ parts.push('', '### Subcommands', '');
50
+ for (const sc of subcommands) {
51
+ parts.push(`- \`${programName} ${sc.name}\` — ${escapeMdx(sc.description ?? '')}`);
52
+ }
53
+ }
54
+ const body = parts.join('\n').trim() + '\n';
55
+ const { relDir, filename } = makeCommandFilepath(commandPath || programName);
56
+ return { relDir, filename, content: body };
57
+ }
58
+
59
+ function generateIndexPage(rootHelp, programName, pkgVersion, pkgDescription) {
60
+ const title = `${programName} Commands`;
61
+ const header = `---\ntitle: ${escapeMdx(title)}\n---`;
62
+ const intro = rootHelp.description || pkgDescription || '';
63
+ const lines = [header, '', intro, `Version: \`${pkgVersion}\``, '', '### Command List', ''];
64
+ for (const sc of rootHelp.subcommands || []) {
65
+ lines.push(`- \`${programName} ${sc.name}\` — ${escapeMdx(sc.description ?? '')}`);
66
+ }
67
+ lines.push('', '---', '', 'This reference is auto-generated. Do not edit manually.');
68
+ return { relDir: '', filename: 'index.mdx', content: lines.join('\n') + '\n' };
69
+ }
70
+
71
+ function runHelp(args) {
72
+ const res = spawnSync(process.execPath, ['dist/index.js', ...args, '--help'], {
73
+ encoding: 'utf8',
74
+ timeout: 30000,
75
+ });
76
+ if ((res.status !== 0 || res.error) && res.stdout.trim() === '') {
77
+ const reason = res.error?.message || res.stderr || `exitCode=${res.status}`;
78
+ throw new Error(`Failed to run help for: ${args.join(' ')} (${reason})`);
79
+ }
80
+ return res.stdout;
81
+ }
82
+
83
+ function parseHelp(text, programName, commandPath) {
84
+ const lines = text.split(/\r?\n/);
85
+ let description = '';
86
+ let usage = '';
87
+ const options = [];
88
+ const subcommands = [];
89
+ const args = [];
90
+
91
+ // Accumulate description between the first blank after Usage and before Options/Commands
92
+ let inOptions = false;
93
+ let inCommands = false;
94
+
95
+ // Usage
96
+ const usageIdx = lines.findIndex((l) => l.startsWith('Usage:'));
97
+ if (usageIdx !== -1) {
98
+ const usageLine = lines[usageIdx];
99
+ // Replace program binary name (index) by programName
100
+ const afterColon = usageLine.replace(/^Usage:\s*/, '');
101
+ const replaced = afterColon.replace(/^\S+/, programName);
102
+ usage = `$ ${replaced}`;
103
+ }
104
+
105
+ // Description
106
+ for (let i = usageIdx + 1; i < lines.length; i += 1) {
107
+ const l = lines[i];
108
+ if (l.trim() === '') continue;
109
+ if (l.startsWith('Options:') || l.startsWith('Commands:')) break;
110
+ description += (description ? '\n' : '') + l.trim();
111
+ }
112
+
113
+ // Parse sections
114
+ for (let i = 0; i < lines.length; i += 1) {
115
+ const l = lines[i];
116
+ if (l.startsWith('Options:')) { inOptions = true; inCommands = false; continue; }
117
+ if (l.startsWith('Commands:')) { inCommands = true; inOptions = false; continue; }
118
+ if (/^\s*$/.test(l)) continue;
119
+
120
+ if (inOptions) {
121
+ // e.g., " -V, --version output the version number"
122
+ const m = l.match(/^\s*(-\w)?,?\s*(--[\w-]+)?\s{2,}(.+)$/);
123
+ if (m) {
124
+ const short = m[1] || '';
125
+ const long = m[2] || '';
126
+ const desc = m[3] || '';
127
+ options.push({ short, long, description: desc, required: false });
128
+ }
129
+ } else if (inCommands) {
130
+ // e.g., " deploy [options] Deploy intelligent contracts"
131
+ const m = l.match(/^\s*([\w-]+)(?:\s<[^>]+>|\s\[[^\]]+\])*\s{2,}(.+)$/);
132
+ if (m) {
133
+ const cmdToken = m[1];
134
+ const desc = m[2] || '';
135
+ const name = cmdToken.trim();
136
+ if (name !== 'help') {
137
+ subcommands.push({ name, description: desc });
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ // Derive args from usage (after commandPath)
144
+ if (usage) {
145
+ const usageCmd = usage.replace(/^`?\$\s+/, '').replace(/`?$/, '');
146
+ const tokens = usageCmd.split(/\s+/);
147
+ // find starting index of commandPath tokens
148
+ const cmdTokens = (commandPath ? `${programName} ${commandPath}` : programName).split(' ');
149
+ const start = tokens.findIndex((t, idx) => tokens.slice(idx, idx + cmdTokens.length).join(' ') === cmdTokens.join(' '));
150
+ const after = start >= 0 ? tokens.slice(start + cmdTokens.length) : [];
151
+ for (const t of after) {
152
+ if (t === '[options]') continue;
153
+ const m = t.match(/^<(.*)>$/) || t.match(/^\[(.*)\]$/);
154
+ if (m) {
155
+ const variadic = m[1].endsWith('...');
156
+ const name = variadic ? m[1].slice(0, -3) : m[1];
157
+ const required = t.startsWith('<');
158
+ args.push({ name, variadic, required });
159
+ }
160
+ }
161
+ }
162
+
163
+ return { description, usage, options, subcommands, args };
164
+ }
165
+
166
+ async function ensureDir(dir) {
167
+ await fs.mkdir(dir, { recursive: true });
168
+ }
169
+
170
+ async function writePages(root, pages) {
171
+ for (const page of pages) {
172
+ const dir = path.join(root, page.relDir);
173
+ await ensureDir(dir);
174
+ const fullpath = path.join(dir, page.filename);
175
+ await fs.writeFile(fullpath, page.content, 'utf8');
176
+ }
177
+ }
178
+
179
+ async function rmrf(dir) {
180
+ try {
181
+ await fs.rm(dir, { recursive: true, force: true });
182
+ } catch {}
183
+ }
184
+
185
+ async function readPackageInfo() {
186
+ const here = path.dirname(fileURLToPath(import.meta.url));
187
+ const pkgPath = path.join(here, '..', 'package.json');
188
+ const raw = await fs.readFile(pkgPath, 'utf8');
189
+ const json = JSON.parse(raw);
190
+ return { version: json.version, description: json.description };
191
+ }
192
+
193
+ async function main() {
194
+ const { version: pkgVersion, description: pkgDescription } = await readPackageInfo();
195
+ const programName = 'genlayer';
196
+
197
+ // Ensure CLI is built before attempting to read help output
198
+ const here = path.dirname(fileURLToPath(import.meta.url));
199
+ const cliPath = path.join(here, '..', 'dist', 'index.js');
200
+ try {
201
+ await fs.access(cliPath);
202
+ } catch {
203
+ throw new Error('CLI not built. Please run "npm run build" first.');
204
+ }
205
+
206
+ const rootHelpText = runHelp([]);
207
+ const rootHelp = parseHelp(rootHelpText, programName, '');
208
+
209
+ const outputDirFromEnv = process.env.DOCS_OUTPUT_DIR;
210
+ const clean = (process.env.DOCS_CLEAN || '').toLowerCase() === 'true';
211
+
212
+ const outputs = [];
213
+ // filter out auto 'help' just in case
214
+ rootHelp.subcommands = (rootHelp.subcommands || []).filter((c) => c.name !== 'help');
215
+ outputs.push(generateIndexPage(rootHelp, programName, pkgVersion, pkgDescription));
216
+
217
+ // BFS through subcommands by invoking help for each
218
+ const queue = [...(rootHelp.subcommands || [])].map((c) => ({ path: c.name }));
219
+ while (queue.length) {
220
+ const { path: cmdPath } = queue.shift();
221
+ const helpText = runHelp(cmdPath.split(' '));
222
+ const help = parseHelp(helpText, programName, cmdPath);
223
+ outputs.push(generatePageForCommand(help, programName, cmdPath));
224
+ for (const sc of help.subcommands || []) {
225
+ queue.push({ path: `${cmdPath} ${sc.name}` });
226
+ }
227
+ }
228
+
229
+ const defaultOut = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'docs', 'api-references');
230
+ const rootOut = outputDirFromEnv ? outputDirFromEnv : defaultOut;
231
+ if (clean) await rmrf(rootOut);
232
+ await writePages(rootOut, outputs);
233
+
234
+ const meta = {};
235
+ for (const c of (rootHelp.subcommands || []).map((c) => toSlug(c.name))) meta[c] = c;
236
+ await fs.writeFile(path.join(rootOut, '_meta.json'), JSON.stringify(meta, null, 2), 'utf8');
237
+
238
+ console.log(`Generated ${outputs.length} pages at ${rootOut}`);
239
+ }
240
+
241
+ main().catch((err) => {
242
+ console.error(err);
243
+ process.exit(1);
244
+ });
245
+
246
+