@aws/agentcore 0.22.0 → 0.24.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.
- package/dist/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +2 -2
- package/dist/assets/agents/AGENTS.md +1 -1
- package/dist/assets/python/http/strands/base/model/load.py +1 -1
- package/dist/cli/index.mjs +557 -530
- package/dist/lib/constants.d.ts +8 -4
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +13 -7
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/packaging/build-args.d.ts +6 -0
- package/dist/lib/packaging/build-args.d.ts.map +1 -1
- package/dist/lib/packaging/build-args.js +9 -0
- package/dist/lib/packaging/build-args.js.map +1 -1
- package/dist/lib/packaging/build-context-dockerignore.d.ts +23 -0
- package/dist/lib/packaging/build-context-dockerignore.d.ts.map +1 -0
- package/dist/lib/packaging/build-context-dockerignore.js +63 -0
- package/dist/lib/packaging/build-context-dockerignore.js.map +1 -0
- package/dist/lib/packaging/build-context.d.ts +21 -0
- package/dist/lib/packaging/build-context.d.ts.map +1 -0
- package/dist/lib/packaging/build-context.js +21 -0
- package/dist/lib/packaging/build-context.js.map +1 -0
- package/dist/lib/packaging/container.d.ts.map +1 -1
- package/dist/lib/packaging/container.js +15 -4
- package/dist/lib/packaging/container.js.map +1 -1
- package/dist/lib/packaging/index.d.ts +2 -0
- package/dist/lib/packaging/index.d.ts.map +1 -1
- package/dist/lib/packaging/index.js +5 -1
- package/dist/lib/packaging/index.js.map +1 -1
- package/dist/lib/schemas/io/config-io.d.ts +8 -0
- package/dist/lib/schemas/io/config-io.d.ts.map +1 -1
- package/dist/lib/schemas/io/config-io.js +12 -0
- package/dist/lib/schemas/io/config-io.js.map +1 -1
- package/dist/schema/schemas/agent-env.d.ts +22 -0
- package/dist/schema/schemas/agent-env.d.ts.map +1 -1
- package/dist/schema/schemas/agent-env.js +117 -15
- package/dist/schema/schemas/agent-env.js.map +1 -1
- package/dist/schema/schemas/agentcore-project.d.ts +24 -10
- package/dist/schema/schemas/agentcore-project.d.ts.map +1 -1
- package/dist/schema/schemas/agentcore-project.js +19 -6
- package/dist/schema/schemas/agentcore-project.js.map +1 -1
- package/dist/schema/schemas/aws-targets.d.ts +12 -0
- package/dist/schema/schemas/aws-targets.d.ts.map +1 -1
- package/dist/schema/schemas/aws-targets.js +4 -0
- package/dist/schema/schemas/aws-targets.js.map +1 -1
- package/dist/schema/schemas/mcp.d.ts +29 -18
- package/dist/schema/schemas/mcp.d.ts.map +1 -1
- package/dist/schema/schemas/mcp.js +29 -175
- package/dist/schema/schemas/mcp.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/scripts/extract-cli-model.mjs +228 -0
- package/scripts/render_adoc.py +274 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// extract-cli-model.mjs — agentcore CLI -> doc-model JSON
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// Runs `agentcore <cmd> --help` for every command, parses the Commander.js help
|
|
6
|
+
// output, and emits the SAME shared doc-model schema (v1) as the SDK extractors
|
|
7
|
+
// so the one shared renderer (_shared/render_adoc.py) produces consistent .adoc.
|
|
8
|
+
//
|
|
9
|
+
// Why --help scraping (not AST): it is 1:1 with what users actually see, and it
|
|
10
|
+
// survives refactors of the command source. Commander.js help is
|
|
11
|
+
// stable and easy to parse (Usage / Arguments / Options blocks).
|
|
12
|
+
//
|
|
13
|
+
// Decision: ALL ~30 commands, arranged into groups (one group == one .adoc).
|
|
14
|
+
//
|
|
15
|
+
// NOTE: if these workflows are later consolidated into a single shared reusable
|
|
16
|
+
// workflow, this script is vendored there and selected via `language: cli`.
|
|
17
|
+
// Standalone here for the draft.
|
|
18
|
+
// =============================================================================
|
|
19
|
+
import { execFileSync } from 'node:child_process';
|
|
20
|
+
import { writeFileSync } from 'node:fs';
|
|
21
|
+
import { argv } from 'node:process';
|
|
22
|
+
|
|
23
|
+
// Command grouping (arranged properly, per the request). Mirrors the sections
|
|
24
|
+
// in agentcore-cli/docs/commands.md. Any command discovered from the binary but
|
|
25
|
+
// not listed here lands in the "Other" group so nothing is silently dropped.
|
|
26
|
+
const GROUPS = [
|
|
27
|
+
{
|
|
28
|
+
id: 'project-lifecycle',
|
|
29
|
+
title: 'Project Lifecycle',
|
|
30
|
+
commands: ['create', 'deploy', 'dev', 'package', 'export', 'update', 'validate'],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'invocation',
|
|
34
|
+
title: 'Invocation & Runtime',
|
|
35
|
+
commands: ['invoke', 'exec', 'run', 'logs', 'traces', 'status', 'fetch', 'view'],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'resources',
|
|
39
|
+
title: 'Resource Management',
|
|
40
|
+
commands: ['add', 'remove', 'import'],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'evaluation',
|
|
44
|
+
title: 'Evaluation & Datasets',
|
|
45
|
+
commands: ['evals', 'batch-evaluations', 'dataset'],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'optimization',
|
|
49
|
+
title: 'Optimization & Config Bundles',
|
|
50
|
+
commands: ['config-bundle', 'promote', 'archive'],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'operations',
|
|
54
|
+
title: 'Operations & Settings',
|
|
55
|
+
commands: ['pause', 'resume', 'stop', 'config', 'telemetry', 'feedback'],
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
// AGENTCORE_BIN may be a single command ("agentcore") OR a multi-token
|
|
60
|
+
// invocation ("node /path/to/index.mjs"). Split into [executable, ...prefixArgs]
|
|
61
|
+
// so execFileSync gets a real executable and forwards the rest as args —
|
|
62
|
+
// passing the whole string as argv[0] would make execFileSync look for an
|
|
63
|
+
// executable literally named "node /path/...", which fails silently to empty.
|
|
64
|
+
const BIN_TOKENS = (process.env.AGENTCORE_BIN || 'agentcore').split(/\s+/);
|
|
65
|
+
const BIN = BIN_TOKENS[0];
|
|
66
|
+
const BIN_PREFIX_ARGS = BIN_TOKENS.slice(1);
|
|
67
|
+
|
|
68
|
+
function getArg(flag) {
|
|
69
|
+
const i = argv.indexOf(flag);
|
|
70
|
+
return i >= 0 ? argv[i + 1] : undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function help(args) {
|
|
74
|
+
try {
|
|
75
|
+
return execFileSync(BIN, [...BIN_PREFIX_ARGS, ...args, '--help'], {
|
|
76
|
+
encoding: 'utf8',
|
|
77
|
+
// CLI launches a TUI without args; --help forces non-interactive text.
|
|
78
|
+
env: { ...process.env, NO_COLOR: '1', CI: '1' },
|
|
79
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
80
|
+
});
|
|
81
|
+
} catch (e) {
|
|
82
|
+
// Commander exits non-zero for some help paths; stdout still holds text.
|
|
83
|
+
return (e.stdout && e.stdout.toString()) || '';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Parse a Commander.js help blob into { summary, signature, params, options }.
|
|
88
|
+
function parseHelp(text, name) {
|
|
89
|
+
const lines = text.split('\n');
|
|
90
|
+
const out = { summary: '', signature: '', args: [], options: [] };
|
|
91
|
+
let section = 'head';
|
|
92
|
+
const descLines = [];
|
|
93
|
+
|
|
94
|
+
for (const raw of lines) {
|
|
95
|
+
const line = raw.replace(/\s+$/, '');
|
|
96
|
+
if (/^Usage:/.test(line)) {
|
|
97
|
+
out.signature = line.replace(/^Usage:\s*/, '').trim();
|
|
98
|
+
section = 'desc';
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (/^Arguments:/.test(line)) {
|
|
102
|
+
section = 'args';
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (/^Options:/.test(line)) {
|
|
106
|
+
section = 'options';
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (/^Commands:/.test(line)) {
|
|
110
|
+
section = 'commands';
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (section === 'desc') {
|
|
115
|
+
if (line.trim()) descLines.push(line.trim());
|
|
116
|
+
} else if (section === 'args') {
|
|
117
|
+
const m = line.match(/^\s+(\S+)\s{2,}(.*)$/);
|
|
118
|
+
if (m) out.args.push({ name: m[1], type: null, required: true, description: m[2].trim() });
|
|
119
|
+
} else if (section === 'options') {
|
|
120
|
+
const m = line.match(/^\s+(-[^\s].*?)\s{2,}(.*)$/);
|
|
121
|
+
if (m) out.options.push({ name: m[1].trim(), type: null, required: false, description: m[2].trim() });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
out.summary = descLines.join(' ');
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function entryForCommand(name) {
|
|
129
|
+
const raw = help([name]);
|
|
130
|
+
// Fail loudly on phantom commands / drift. A real command's help names itself
|
|
131
|
+
// in the usage line (`Usage: agentcore <name> ...`); an unknown command falls
|
|
132
|
+
// back to the root usage (`Usage: agentcore [options] [command]`), which does
|
|
133
|
+
// NOT contain the name. Without this guard such commands would silently render
|
|
134
|
+
// as empty stubs (synthesized signature, no description/params).
|
|
135
|
+
if (!new RegExp(`^Usage:\\s+agentcore\\s+${name}\\b`, 'm').test(raw)) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Command "${name}" produced no self-describing help output — it is likely ` +
|
|
138
|
+
`not a real CLI command (phantom/renamed). Remove it from GROUPS or fix the name.`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const parsed = parseHelp(raw, name);
|
|
142
|
+
// subcommands (e.g. `add agent`, `remove tool`) show under "Commands:"—
|
|
143
|
+
// we surface the top-level command; nested ones can be expanded later.
|
|
144
|
+
return {
|
|
145
|
+
kind: 'command',
|
|
146
|
+
name: `agentcore ${name}`,
|
|
147
|
+
signature: parsed.signature || `agentcore ${name} [options]`,
|
|
148
|
+
summary: parsed.summary,
|
|
149
|
+
description: '',
|
|
150
|
+
// reuse params for both positional args and flags, flagged by required.
|
|
151
|
+
// The renderer already wraps param names in backticks, so don't add our
|
|
152
|
+
// own (that produced double-backticks). Drop the ubiquitous help flag.
|
|
153
|
+
params: [...parsed.args, ...parsed.options.filter(o => !/^-h,?\s|--help\b/.test(o.name))],
|
|
154
|
+
returns: null,
|
|
155
|
+
raises: [],
|
|
156
|
+
examples: [],
|
|
157
|
+
members: [],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function discoverCommands() {
|
|
162
|
+
// Parse the root help "Commands:" block so we don't miss anything new.
|
|
163
|
+
// In Commander output, a command entry starts at EXACTLY 2 spaces of indent
|
|
164
|
+
// (e.g. " deploy|dp [options] ...") while wrapped description lines are
|
|
165
|
+
// indented much deeper (~36 spaces). Matching only the 2-space indent avoids
|
|
166
|
+
// scraping the first word of a wrapped description ("locally", "past", ...).
|
|
167
|
+
// We also strip the "|alias" and trailing "[options]"/"<arg>" tokens.
|
|
168
|
+
const text = help([]);
|
|
169
|
+
const found = [];
|
|
170
|
+
let inCmds = false;
|
|
171
|
+
for (const line of text.split('\n')) {
|
|
172
|
+
if (/^Commands:/.test(line)) {
|
|
173
|
+
inCmds = true;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (!inCmds) continue;
|
|
177
|
+
// stop if we hit a new top-level section (e.g. a footer) — non-indented text
|
|
178
|
+
if (line.trim() && !/^ {2}\S/.test(line) && !/^ {3,}/.test(line)) break;
|
|
179
|
+
const m = line.match(/^ {2}([a-z][a-z0-9-]*)(?:\|[a-z0-9-]+)?\b/);
|
|
180
|
+
if (m) found.push(m[1]);
|
|
181
|
+
}
|
|
182
|
+
return found;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function main() {
|
|
186
|
+
const outPath = getArg('--out');
|
|
187
|
+
const version = getArg('--version') || 'unknown';
|
|
188
|
+
|
|
189
|
+
const discovered = new Set(discoverCommands());
|
|
190
|
+
const placed = new Set();
|
|
191
|
+
const groups = [];
|
|
192
|
+
|
|
193
|
+
for (const g of GROUPS) {
|
|
194
|
+
const entries = [];
|
|
195
|
+
for (const cmd of g.commands) {
|
|
196
|
+
entries.push(entryForCommand(cmd));
|
|
197
|
+
placed.add(cmd);
|
|
198
|
+
}
|
|
199
|
+
groups.push({ id: g.id, title: g.title, summary: '', entries });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Built-in meta-commands we intentionally don't document as API surface.
|
|
203
|
+
const IGNORED = new Set(['help']);
|
|
204
|
+
|
|
205
|
+
// Catch anything the binary exposes that we didn't group — no silent drops.
|
|
206
|
+
const leftovers = [...discovered].filter(c => !placed.has(c) && !IGNORED.has(c));
|
|
207
|
+
if (leftovers.length) {
|
|
208
|
+
process.stderr.write(`WARNING: ungrouped commands -> "Other": ${leftovers.join(', ')}\n`);
|
|
209
|
+
groups.push({
|
|
210
|
+
id: 'other',
|
|
211
|
+
title: 'Other Commands',
|
|
212
|
+
summary: '',
|
|
213
|
+
entries: leftovers.map(entryForCommand),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const model = {
|
|
218
|
+
source: 'cli',
|
|
219
|
+
package: '@aws/agentcore',
|
|
220
|
+
version,
|
|
221
|
+
language: 'cli',
|
|
222
|
+
groups,
|
|
223
|
+
};
|
|
224
|
+
writeFileSync(outPath, JSON.stringify(model, null, 2));
|
|
225
|
+
process.stderr.write(`Wrote doc-model: ${groups.length} groups, version ${version}\n`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
main();
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# render_adoc.py — shared doc-model -> AsciiDoc renderer
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Renders a normalized "doc-model" JSON (produced by any of the three
|
|
6
|
+
# extractors) into .adoc files for the documentation repository.
|
|
7
|
+
#
|
|
8
|
+
# It is deliberately source-agnostic: the Python extractor, the TypeScript
|
|
9
|
+
# TypeDoc extractor, and the CLI --help extractor all emit the SAME doc-model
|
|
10
|
+
# schema, so this one renderer produces consistent output for all three.
|
|
11
|
+
#
|
|
12
|
+
# NOTE: if these workflows are later consolidated into a single shared reusable
|
|
13
|
+
# workflow, this file is vendored there ONCE and every source repo's caller
|
|
14
|
+
# invokes it. Keep it dependency-free (stdlib only) so it drops cleanly into any
|
|
15
|
+
# runner.
|
|
16
|
+
#
|
|
17
|
+
# -----------------------------------------------------------------------------
|
|
18
|
+
# doc-model schema (v1) — the contract every extractor must emit:
|
|
19
|
+
# {
|
|
20
|
+
# "source": "python-sdk" | "ts-sdk" | "cli",
|
|
21
|
+
# "package": "bedrock-agentcore",
|
|
22
|
+
# "version": "1.16.0",
|
|
23
|
+
# "language": "python" | "typescript" | "cli",
|
|
24
|
+
# "groups": [ # a group -> one .adoc file
|
|
25
|
+
# {
|
|
26
|
+
# "id": "runtime", # -> <prefix>-runtime.adoc, and anchor id
|
|
27
|
+
# "title": "Runtime",
|
|
28
|
+
# "summary": "Runtime management and application context.",
|
|
29
|
+
# "entries": [ # classes / functions / commands
|
|
30
|
+
# {
|
|
31
|
+
# "kind": "class" | "function" | "command",
|
|
32
|
+
# "name": "BedrockAgentCoreApp",
|
|
33
|
+
# "signature": "BedrockAgentCoreApp(params)",
|
|
34
|
+
# "summary": "one-line summary",
|
|
35
|
+
# "description": "longer prose (optional)",
|
|
36
|
+
# "params": [ {"name","type","required","description"} ],
|
|
37
|
+
# "returns": {"type","description"} | null,
|
|
38
|
+
# "raises": [ {"type","description"} ],
|
|
39
|
+
# "examples": [ {"lang","code"} ],
|
|
40
|
+
# "members": [ <entry>, ... ] # methods on a class (recursive)
|
|
41
|
+
# }
|
|
42
|
+
# ]
|
|
43
|
+
# }
|
|
44
|
+
# ]
|
|
45
|
+
# }
|
|
46
|
+
# -----------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
import argparse
|
|
49
|
+
import json
|
|
50
|
+
import os
|
|
51
|
+
import re
|
|
52
|
+
import sys
|
|
53
|
+
import textwrap
|
|
54
|
+
|
|
55
|
+
SCHEMA_VERSION = 1
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def esc(text):
|
|
59
|
+
"""Escape AsciiDoc-significant characters in inline text."""
|
|
60
|
+
if not text:
|
|
61
|
+
return ""
|
|
62
|
+
# Guard the couple of chars that start AsciiDoc markup in running prose.
|
|
63
|
+
return (
|
|
64
|
+
text.replace("|", "\\|")
|
|
65
|
+
.replace("{", "\\{")
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Match markdown code fences that may be indented (reST/Google docstrings often
|
|
70
|
+
# indent example blocks). `re.MULTILINE` lets ^ match each line start; the
|
|
71
|
+
# leading-whitespace groups are stripped from the captured code.
|
|
72
|
+
_FENCE_RE = re.compile(r"^[ \t]*```(\w*)[ \t]*\n(.*?)\n[ \t]*```[ \t]*$", re.DOTALL | re.MULTILINE)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def render_prose(text):
|
|
76
|
+
"""Render description prose that may contain markdown ``` code fences.
|
|
77
|
+
|
|
78
|
+
Docstrings/TSDoc frequently embed fenced code blocks in the description
|
|
79
|
+
(not just in @example). Left alone they leak literal backticks into the
|
|
80
|
+
AsciiDoc. Split the prose on fences: escape the prose spans, and convert
|
|
81
|
+
each fenced block into an AsciiDoc [source] block (verbatim, not escaped).
|
|
82
|
+
"""
|
|
83
|
+
if not text:
|
|
84
|
+
return []
|
|
85
|
+
out = []
|
|
86
|
+
pos = 0
|
|
87
|
+
for m in _FENCE_RE.finditer(text):
|
|
88
|
+
before = text[pos:m.start()].strip()
|
|
89
|
+
if before:
|
|
90
|
+
out.append(esc(before))
|
|
91
|
+
out.append("")
|
|
92
|
+
lang = m.group(1) or ""
|
|
93
|
+
out.append(f"[source,{lang}]" if lang else "[source]")
|
|
94
|
+
out.append("----")
|
|
95
|
+
# Dedent the captured code (fences are often indented in docstrings).
|
|
96
|
+
out.append(textwrap.dedent(m.group(2)).rstrip())
|
|
97
|
+
out.append("----")
|
|
98
|
+
out.append("")
|
|
99
|
+
pos = m.end()
|
|
100
|
+
tail = text[pos:].strip()
|
|
101
|
+
if tail:
|
|
102
|
+
out.append(esc(tail))
|
|
103
|
+
return out
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def block(lines):
|
|
107
|
+
return "\n".join(lines)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def render_params(params, out):
|
|
111
|
+
if not params:
|
|
112
|
+
return
|
|
113
|
+
out.append("*Parameters*")
|
|
114
|
+
out.append("")
|
|
115
|
+
for p in params:
|
|
116
|
+
req = "" if p.get("required") else " _(optional)_"
|
|
117
|
+
typ = f"`{p['type']}`" if p.get("type") else ""
|
|
118
|
+
out.append(f"`{p['name']}`{req} {typ}::")
|
|
119
|
+
out.append(esc(p.get("description", "")) or "_No description._")
|
|
120
|
+
out.append("")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def render_returns(returns, out):
|
|
124
|
+
if not returns:
|
|
125
|
+
return
|
|
126
|
+
typ = f"`{returns['type']}` — " if returns.get("type") else ""
|
|
127
|
+
out.append("*Returns*")
|
|
128
|
+
out.append("")
|
|
129
|
+
out.append(f"{typ}{esc(returns.get('description', ''))}".strip())
|
|
130
|
+
out.append("")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def render_raises(raises, out):
|
|
134
|
+
if not raises:
|
|
135
|
+
return
|
|
136
|
+
out.append("*Raises*")
|
|
137
|
+
out.append("")
|
|
138
|
+
for r in raises:
|
|
139
|
+
out.append(f"`{r.get('type', 'Error')}`:: {esc(r.get('description', ''))}")
|
|
140
|
+
out.append("")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def clean_example_code(code):
|
|
144
|
+
"""Strip stray markdown fence lines from example code.
|
|
145
|
+
|
|
146
|
+
Extractors try to remove ``` fences, but real docstrings put them mid-buffer
|
|
147
|
+
(e.g. a fence followed by extra "Notes:"/"Thread Safety:" prose swept into
|
|
148
|
+
the example). Since this text is already going inside an AsciiDoc [source]
|
|
149
|
+
block, any line that is just a fence is spurious — drop it, and drop trailing
|
|
150
|
+
non-code prose that follows a closing fence.
|
|
151
|
+
"""
|
|
152
|
+
lines = code.split("\n")
|
|
153
|
+
kept = []
|
|
154
|
+
closed = False
|
|
155
|
+
for line in lines:
|
|
156
|
+
if re.match(r"^[ \t]*```", line):
|
|
157
|
+
# A closing fence marks the end of the real code; ignore the fence
|
|
158
|
+
# line itself and stop taking subsequent prose.
|
|
159
|
+
if kept:
|
|
160
|
+
closed = True
|
|
161
|
+
continue
|
|
162
|
+
if closed and line.strip() == "":
|
|
163
|
+
continue
|
|
164
|
+
if closed and line.strip():
|
|
165
|
+
break # prose after the closing fence — not part of the example
|
|
166
|
+
kept.append(line)
|
|
167
|
+
return textwrap.dedent("\n".join(kept)).strip()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def render_examples(examples, out):
|
|
171
|
+
for ex in examples or []:
|
|
172
|
+
lang = ex.get("lang", "text")
|
|
173
|
+
code = clean_example_code(ex.get("code", ""))
|
|
174
|
+
if not code:
|
|
175
|
+
continue
|
|
176
|
+
out.append(f"[source,{lang}]")
|
|
177
|
+
out.append("----")
|
|
178
|
+
out.append(code)
|
|
179
|
+
out.append("----")
|
|
180
|
+
out.append("")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def render_entry(entry, level, out):
|
|
184
|
+
"""Render a single class/function/command as an AsciiDoc section."""
|
|
185
|
+
heading = "=" * level
|
|
186
|
+
name = entry.get("name", "")
|
|
187
|
+
out.append(f"{heading} {name}")
|
|
188
|
+
out.append("")
|
|
189
|
+
|
|
190
|
+
sig = entry.get("signature")
|
|
191
|
+
if sig:
|
|
192
|
+
out.append("[source]")
|
|
193
|
+
out.append("----")
|
|
194
|
+
out.append(sig)
|
|
195
|
+
out.append("----")
|
|
196
|
+
out.append("")
|
|
197
|
+
|
|
198
|
+
if entry.get("summary"):
|
|
199
|
+
out.extend(render_prose(entry["summary"]))
|
|
200
|
+
out.append("")
|
|
201
|
+
if entry.get("description"):
|
|
202
|
+
out.extend(render_prose(entry["description"]))
|
|
203
|
+
out.append("")
|
|
204
|
+
|
|
205
|
+
render_params(entry.get("params"), out)
|
|
206
|
+
render_returns(entry.get("returns"), out)
|
|
207
|
+
render_raises(entry.get("raises"), out)
|
|
208
|
+
render_examples(entry.get("examples"), out)
|
|
209
|
+
|
|
210
|
+
# methods / subcommands nest one heading level deeper
|
|
211
|
+
for member in entry.get("members", []):
|
|
212
|
+
render_entry(member, level + 1, out)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def render_group(group, model, prefix):
|
|
216
|
+
"""Render one group -> one .adoc file body (string)."""
|
|
217
|
+
out = []
|
|
218
|
+
gid = group["id"]
|
|
219
|
+
# AsciiDoc anchor so the TOC / other pages can xref into it.
|
|
220
|
+
out.append(f"[[{prefix}-{gid}]]")
|
|
221
|
+
out.append(f"= {group['title']}")
|
|
222
|
+
out.append("")
|
|
223
|
+
out.append(
|
|
224
|
+
f"_Auto-generated from `{model['package']}` "
|
|
225
|
+
f"v{model['version']} — do not edit by hand._"
|
|
226
|
+
)
|
|
227
|
+
out.append("")
|
|
228
|
+
if group.get("summary"):
|
|
229
|
+
out.extend(render_prose(group["summary"]))
|
|
230
|
+
out.append("")
|
|
231
|
+
|
|
232
|
+
for entry in group.get("entries", []):
|
|
233
|
+
render_entry(entry, level=2, out=out)
|
|
234
|
+
|
|
235
|
+
return block(out).rstrip() + "\n"
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def main():
|
|
239
|
+
ap = argparse.ArgumentParser(description="Render doc-model JSON to .adoc")
|
|
240
|
+
ap.add_argument("--model", required=True, help="path to doc-model JSON")
|
|
241
|
+
ap.add_argument("--out-dir", required=True, help="output dir for .adoc files")
|
|
242
|
+
ap.add_argument(
|
|
243
|
+
"--prefix",
|
|
244
|
+
required=True,
|
|
245
|
+
help="filename + anchor prefix, e.g. 'python-sdk', 'ts-sdk', 'cli'",
|
|
246
|
+
)
|
|
247
|
+
args = ap.parse_args()
|
|
248
|
+
|
|
249
|
+
with open(args.model) as f:
|
|
250
|
+
model = json.load(f)
|
|
251
|
+
|
|
252
|
+
os.makedirs(args.out_dir, exist_ok=True)
|
|
253
|
+
written = []
|
|
254
|
+
for group in model.get("groups", []):
|
|
255
|
+
body = render_group(group, model, args.prefix)
|
|
256
|
+
fname = f"{args.prefix}-{group['id']}.adoc"
|
|
257
|
+
path = os.path.join(args.out_dir, fname)
|
|
258
|
+
with open(path, "w") as f:
|
|
259
|
+
f.write(body)
|
|
260
|
+
written.append(fname)
|
|
261
|
+
|
|
262
|
+
# Emit the list of includes the caller can splice into the docs TOC file.
|
|
263
|
+
manifest = os.path.join(args.out_dir, f"{args.prefix}-includes.txt")
|
|
264
|
+
with open(manifest, "w") as f:
|
|
265
|
+
for fname in written:
|
|
266
|
+
f.write(f"include::{args.prefix}/{fname}[leveloffset=+1]\n")
|
|
267
|
+
|
|
268
|
+
print(f"Rendered {len(written)} .adoc files to {args.out_dir}", file=sys.stderr)
|
|
269
|
+
for fname in written:
|
|
270
|
+
print(f" - {fname}", file=sys.stderr)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
if __name__ == "__main__":
|
|
274
|
+
main()
|