@open-agent-toolkit/cli 0.2.55 → 0.2.56
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/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/configuration.md +12 -2
- package/assets/docs/docs-tooling/commands.md +36 -5
- package/assets/docs/reference/oat-directory-structure.md +56 -4
- package/assets/public-package-versions.json +4 -4
- package/dist/commands/config/index.d.ts +1 -0
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +51 -4
- package/dist/commands/docs/index-generate/generator.d.ts +43 -1
- package/dist/commands/docs/index-generate/generator.d.ts.map +1 -1
- package/dist/commands/docs/index-generate/generator.js +169 -3
- package/dist/commands/docs/index-generate/index.d.ts +8 -3
- package/dist/commands/docs/index-generate/index.d.ts.map +1 -1
- package/dist/commands/docs/index-generate/index.js +340 -24
- package/dist/commands/docs/init/scaffold.d.ts +11 -1
- package/dist/commands/docs/init/scaffold.d.ts.map +1 -1
- package/dist/commands/docs/init/scaffold.js +11 -2
- package/dist/config/oat-config.d.ts +14 -0
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +61 -0
- package/dist/fs/assets.d.ts +33 -4
- package/dist/fs/assets.d.ts.map +1 -1
- package/dist/fs/assets.js +127 -12
- package/package.json +2 -2
|
@@ -1,12 +1,48 @@
|
|
|
1
|
-
import { writeFile } from 'node:fs/promises';
|
|
2
|
-
import { join, relative } from 'node:path';
|
|
1
|
+
import { readFile, readlink, realpath, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep, } from 'node:path';
|
|
3
3
|
import { buildCommandContext, } from '../../../app/command-context.js';
|
|
4
4
|
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
5
|
-
import { readOatConfig, writeOatConfig } from '../../../config/oat-config.js';
|
|
6
|
-
import {
|
|
5
|
+
import { readOatConfig, writeOatConfig, } from '../../../config/oat-config.js';
|
|
6
|
+
import { CliError } from '../../../errors/cli-error.js';
|
|
7
|
+
import { dirExists } from '../../../fs/io.js';
|
|
8
|
+
import { normalizeToPosixPath, resolveProjectRoot } from '../../../fs/paths.js';
|
|
7
9
|
import { Command, Option } from 'commander';
|
|
8
10
|
import { generateIndex, renderIndex } from './generator.js';
|
|
9
11
|
export const GENERATED_INDEX_WARNING = '<!-- AUTOGENERATED by `oat docs generate-index`. Do not hand-edit; changes are clobbered on every `predev` / `prebuild`. -->';
|
|
12
|
+
const YAML_EXTENSIONS = ['.yml', '.yaml'];
|
|
13
|
+
/**
|
|
14
|
+
* Exit codes, per the `packages/cli/AGENTS.md` taxonomy.
|
|
15
|
+
*
|
|
16
|
+
* A refused output target is an actionable user error: the operator passed a
|
|
17
|
+
* path the command will not write to, and the message names what to pass
|
|
18
|
+
* instead. Unusable path *configuration* keeps the exit code the external plan
|
|
19
|
+
* mandates in its step 1, matching the `oat-config.ts` precedent for an invalid
|
|
20
|
+
* repository config value paired with a repair command.
|
|
21
|
+
*/
|
|
22
|
+
const REFUSAL_EXIT_CODE = 1;
|
|
23
|
+
const CONFIGURATION_EXIT_CODE = 2;
|
|
24
|
+
async function readFileIfPresent(path) {
|
|
25
|
+
try {
|
|
26
|
+
return await readFile(path, 'utf8');
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
const code = error.code;
|
|
30
|
+
if (code === 'ENOENT' || code === 'ENOTDIR') {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** The link destination when `path` is a symlink (dangling or not), else null. */
|
|
37
|
+
async function readLinkIfSymlink(path) {
|
|
38
|
+
try {
|
|
39
|
+
return await readlink(path);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// EINVAL (not a symlink) and ENOENT (absent) are both "nothing to follow".
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
10
46
|
const DEFAULT_FILE_DEPS = {
|
|
11
47
|
generateIndex,
|
|
12
48
|
renderIndex,
|
|
@@ -14,6 +50,10 @@ const DEFAULT_FILE_DEPS = {
|
|
|
14
50
|
readOatConfig,
|
|
15
51
|
writeOatConfig,
|
|
16
52
|
resolveRepoRoot: resolveProjectRoot,
|
|
53
|
+
dirExists,
|
|
54
|
+
readFileIfPresent,
|
|
55
|
+
realpath,
|
|
56
|
+
readLinkIfSymlink,
|
|
17
57
|
};
|
|
18
58
|
const DEFAULT_DEPENDENCIES = {
|
|
19
59
|
buildCommandContext,
|
|
@@ -25,35 +65,310 @@ function buildGeneratedIndexOutput(content) {
|
|
|
25
65
|
}
|
|
26
66
|
return `${GENERATED_INDEX_WARNING}\n\n${content}`;
|
|
27
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* True when the relative path escapes its base. A bare `startsWith('..')` test
|
|
70
|
+
* is wrong: it also matches ordinary children such as `..draft/page.md`, which
|
|
71
|
+
* would let a target inside the indexed tree pass the safety checks.
|
|
72
|
+
*/
|
|
73
|
+
function escapesBase(relativePath) {
|
|
74
|
+
return (isAbsolute(relativePath) ||
|
|
75
|
+
relativePath === '..' ||
|
|
76
|
+
relativePath.startsWith(`..${sep}`));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* True when `candidate` is `parent` itself or lives underneath it. Used for the
|
|
80
|
+
* output-safety checks, so an exact match must count as "inside".
|
|
81
|
+
*/
|
|
82
|
+
function isAtOrInside(parent, candidate) {
|
|
83
|
+
const relativePath = relative(parent, candidate);
|
|
84
|
+
return relativePath === '' || !escapesBase(relativePath);
|
|
85
|
+
}
|
|
86
|
+
/** True when `candidate` lives strictly underneath `parent`. */
|
|
87
|
+
function isInside(parent, candidate) {
|
|
88
|
+
const relativePath = relative(parent, candidate);
|
|
89
|
+
return relativePath !== '' && !escapesBase(relativePath);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Maximum symlink hops followed while canonicalizing, mirroring the kernel's
|
|
93
|
+
* own bound. `realpath` handles resolvable chains; this only bounds the
|
|
94
|
+
* dangling-link hops resolved below.
|
|
95
|
+
*/
|
|
96
|
+
const MAX_SYMLINK_HOPS = 32;
|
|
97
|
+
/**
|
|
98
|
+
* Absolute path with symlinks resolved, so a symlinked parent cannot smuggle a
|
|
99
|
+
* write into a protected tree past the lexical containment checks.
|
|
100
|
+
*
|
|
101
|
+
* Output paths usually do not exist yet, so `realpath` fails and the nearest
|
|
102
|
+
* existing ancestor is resolved instead. A *dangling* symlink also fails
|
|
103
|
+
* `realpath`, yet `writeFile` still follows it, so read its destination
|
|
104
|
+
* explicitly. Components are resolved parent-first, in filesystem order, so a
|
|
105
|
+
* relative link destination is joined to the parent's *real* location rather
|
|
106
|
+
* than its lexical spelling.
|
|
107
|
+
*/
|
|
108
|
+
async function canonicalize(target, deps, linkHops = 0) {
|
|
109
|
+
const absolute = resolve(target);
|
|
110
|
+
try {
|
|
111
|
+
return await deps.realpath(absolute);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const code = error.code;
|
|
115
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR') {
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const parent = dirname(absolute);
|
|
120
|
+
if (parent === absolute) {
|
|
121
|
+
return absolute;
|
|
122
|
+
}
|
|
123
|
+
const canonicalParent = await canonicalize(parent, deps, linkHops);
|
|
124
|
+
const candidate = join(canonicalParent, basename(absolute));
|
|
125
|
+
const linkTarget = await deps.readLinkIfSymlink(candidate);
|
|
126
|
+
if (linkTarget === null) {
|
|
127
|
+
return candidate;
|
|
128
|
+
}
|
|
129
|
+
if (linkHops >= MAX_SYMLINK_HOPS) {
|
|
130
|
+
// Fail closed: falling through would hand the writer an unresolved link.
|
|
131
|
+
throw new CliError(`Refusing to resolve ${target}: its symlink chain exceeds ${MAX_SYMLINK_HOPS} hops. ` +
|
|
132
|
+
'Pass --output with a path that is not a deep symlink chain.', REFUSAL_EXIT_CODE);
|
|
133
|
+
}
|
|
134
|
+
return canonicalize(resolve(canonicalParent, linkTarget), deps, linkHops + 1);
|
|
135
|
+
}
|
|
136
|
+
function trimmed(value) {
|
|
137
|
+
const trimmedValue = value?.trim();
|
|
138
|
+
return trimmedValue ? trimmedValue : null;
|
|
139
|
+
}
|
|
140
|
+
/** Commander collector for the repeatable `--exclude` flag. */
|
|
141
|
+
function collectExclude(value, previous) {
|
|
142
|
+
return [...(previous ?? []), value];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The exclusion list handed to the generator: `documentation.excludes` first,
|
|
146
|
+
* then the `--exclude` flags.
|
|
147
|
+
*
|
|
148
|
+
* Flags **extend** the configured defaults rather than replacing them, so a
|
|
149
|
+
* one-off exclusion on the command line cannot silently republish the pages a
|
|
150
|
+
* repository deliberately excluded. Entries are trimmed, blanks are dropped,
|
|
151
|
+
* and duplicates collapse while preserving first-seen order.
|
|
152
|
+
*/
|
|
153
|
+
function resolveExcludes(config, flagValues) {
|
|
154
|
+
const merged = [];
|
|
155
|
+
for (const value of [
|
|
156
|
+
...(config.documentation?.excludes ?? []),
|
|
157
|
+
...(flagValues ?? []),
|
|
158
|
+
]) {
|
|
159
|
+
const entry = value.trim();
|
|
160
|
+
if (entry && !merged.includes(entry)) {
|
|
161
|
+
merged.push(entry);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return merged;
|
|
165
|
+
}
|
|
166
|
+
function missingRootError(flag) {
|
|
167
|
+
return new CliError(`Cannot resolve ${flag === '--docs-dir' ? 'the docs directory' : 'the output path'}: ` +
|
|
168
|
+
'`documentation.root` is not set in .oat/config.json. ' +
|
|
169
|
+
'Set it with `oat config set documentation.root <path>` (or run `oat docs init`), ' +
|
|
170
|
+
`or pass ${flag} <path> explicitly.`, CONFIGURATION_EXIT_CODE);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Resolve the docs directory and output path.
|
|
174
|
+
*
|
|
175
|
+
* Omitted options come from repository configuration; explicitly supplied
|
|
176
|
+
* options stay CWD-relative for backward compatibility. `documentation.root`
|
|
177
|
+
* canonically names the docs **app root** written by `oat docs init`. The
|
|
178
|
+
* `<root>/docs` preference is compatibility behavior for legacy configs whose
|
|
179
|
+
* `root` names a docs *source* directory; `--docs-dir` overrides it.
|
|
180
|
+
*/
|
|
181
|
+
async function resolveIndexGeneratePaths(context, options, deps) {
|
|
182
|
+
const repoRoot = await deps.resolveRepoRoot(context.cwd);
|
|
183
|
+
const config = await deps.readOatConfig(repoRoot);
|
|
184
|
+
const configuredRootValue = trimmed(config.documentation?.root);
|
|
185
|
+
const configuredRoot = configuredRootValue
|
|
186
|
+
? resolve(repoRoot, configuredRootValue)
|
|
187
|
+
: null;
|
|
188
|
+
// Either omitted option needs a usable configured root, so validate it once
|
|
189
|
+
// up front rather than only on the path that happens to be derived first.
|
|
190
|
+
if (options.docsDir === undefined || options.output === undefined) {
|
|
191
|
+
if (!configuredRoot) {
|
|
192
|
+
throw missingRootError(options.docsDir === undefined ? '--docs-dir' : '--output');
|
|
193
|
+
}
|
|
194
|
+
if (!(await deps.dirExists(configuredRoot))) {
|
|
195
|
+
// Fail with the config-repair path rather than letting the generator or
|
|
196
|
+
// the writer surface a raw ENOENT under the generic exit code.
|
|
197
|
+
throw new CliError(`Cannot resolve documentation paths: \`documentation.root\` is set to "${configuredRootValue}" ` +
|
|
198
|
+
`in .oat/config.json, but ${configuredRoot} is not a directory. ` +
|
|
199
|
+
'Repair it with `oat config set documentation.root <path>`, or pass --docs-dir and --output explicitly.', CONFIGURATION_EXIT_CODE);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
let docsDir;
|
|
203
|
+
let docsDirSource;
|
|
204
|
+
if (options.docsDir !== undefined) {
|
|
205
|
+
docsDir = resolve(context.cwd, options.docsDir);
|
|
206
|
+
docsDirSource = 'flag';
|
|
207
|
+
}
|
|
208
|
+
else if (await deps.dirExists(join(configuredRoot, 'docs'))) {
|
|
209
|
+
docsDir = join(configuredRoot, 'docs');
|
|
210
|
+
docsDirSource = 'config-docs-subdirectory';
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
docsDir = configuredRoot;
|
|
214
|
+
docsDirSource = 'config-root';
|
|
215
|
+
}
|
|
216
|
+
const outputIsExplicit = options.output !== undefined;
|
|
217
|
+
const outputPath = outputIsExplicit
|
|
218
|
+
? resolve(context.cwd, options.output)
|
|
219
|
+
: join(configuredRoot, 'index.md');
|
|
220
|
+
return {
|
|
221
|
+
repoRoot,
|
|
222
|
+
config,
|
|
223
|
+
configuredRoot,
|
|
224
|
+
docsDir,
|
|
225
|
+
docsDirSource,
|
|
226
|
+
outputPath,
|
|
227
|
+
outputIsExplicit,
|
|
228
|
+
excludes: resolveExcludes(config, options.exclude),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/** `canonicalize`, but an unresolvable path yields null instead of throwing. */
|
|
232
|
+
async function canonicalizeOrNull(target, deps) {
|
|
233
|
+
try {
|
|
234
|
+
return await canonicalize(target, deps);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
async function canonicalizeIndexGeneratePaths(resolved, deps) {
|
|
241
|
+
const documentationConfigValue = trimmed(resolved.config.documentation?.config);
|
|
242
|
+
const lexicalDocumentationConfig = documentationConfigValue
|
|
243
|
+
? resolve(resolved.repoRoot, documentationConfigValue)
|
|
244
|
+
: null;
|
|
245
|
+
return {
|
|
246
|
+
docsDir: await canonicalize(resolved.docsDir, deps),
|
|
247
|
+
outputPath: await canonicalize(resolved.outputPath, deps),
|
|
248
|
+
// An unusable configured root only makes the config write ineligible; it
|
|
249
|
+
// must not fail a run whose paths were both supplied explicitly.
|
|
250
|
+
configuredRoot: resolved.configuredRoot
|
|
251
|
+
? await canonicalizeOrNull(resolved.configuredRoot, deps)
|
|
252
|
+
: null,
|
|
253
|
+
lexicalDocumentationConfig,
|
|
254
|
+
// Falling back to the lexical path keeps the refusal guard intact when the
|
|
255
|
+
// configured tool config cannot be canonicalized.
|
|
256
|
+
documentationConfig: lexicalDocumentationConfig
|
|
257
|
+
? ((await canonicalizeOrNull(lexicalDocumentationConfig, deps)) ??
|
|
258
|
+
lexicalDocumentationConfig)
|
|
259
|
+
: null,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Reject unsafe output targets before anything is generated or written. Applies
|
|
264
|
+
* to derived and explicit paths alike, and compares canonical paths so a
|
|
265
|
+
* symlinked target cannot slip past. Messages name the operator-supplied path.
|
|
266
|
+
*/
|
|
267
|
+
async function assertOutputIsSafe(resolved, canonical, deps) {
|
|
268
|
+
const { docsDir, outputPath, outputIsExplicit } = resolved;
|
|
269
|
+
if (isAtOrInside(canonical.docsDir, canonical.outputPath)) {
|
|
270
|
+
throw new CliError(`Refusing to write the generated index inside the docs directory it indexes: ` +
|
|
271
|
+
`${outputPath} is inside ${docsDir}. Pass --output with a path outside the docs directory.`, REFUSAL_EXIT_CODE);
|
|
272
|
+
}
|
|
273
|
+
const configuredConfigPaths = new Set([
|
|
274
|
+
canonical.documentationConfig,
|
|
275
|
+
canonical.lexicalDocumentationConfig,
|
|
276
|
+
].filter((value) => value !== null));
|
|
277
|
+
if (configuredConfigPaths.has(canonical.outputPath) ||
|
|
278
|
+
configuredConfigPaths.has(outputPath)) {
|
|
279
|
+
throw new CliError(`Refusing to overwrite the documentation tool config at ${outputPath} ` +
|
|
280
|
+
'(`documentation.config` in .oat/config.json). Pass --output with a different path.', REFUSAL_EXIT_CODE);
|
|
281
|
+
}
|
|
282
|
+
const yamlCandidates = [
|
|
283
|
+
outputPath.toLowerCase(),
|
|
284
|
+
canonical.outputPath.toLowerCase(),
|
|
285
|
+
];
|
|
286
|
+
if (YAML_EXTENSIONS.some((extension) => yamlCandidates.some((candidate) => candidate.endsWith(extension)))) {
|
|
287
|
+
throw new CliError(`Refusing to write the generated Markdown index to a YAML file: ${outputPath}. ` +
|
|
288
|
+
'Pass --output with a Markdown path.', REFUSAL_EXIT_CODE);
|
|
289
|
+
}
|
|
290
|
+
if (outputIsExplicit) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const existing = await deps.readFileIfPresent(outputPath);
|
|
294
|
+
if (existing !== null && !existing.includes(GENERATED_INDEX_WARNING)) {
|
|
295
|
+
throw new CliError(`Refusing to overwrite ${outputPath}: the file exists and is not a generated index ` +
|
|
296
|
+
'(it has no `oat docs generate-index` AUTOGENERATED header). ' +
|
|
297
|
+
'Move or delete it, or pass --output to name it explicitly.', REFUSAL_EXIT_CODE);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* The only configuration write this command performs: the Fumadocs bootstrap
|
|
302
|
+
* transition that records the generated app-root manifest in
|
|
303
|
+
* `documentation.index`. MkDocs (and any other declared tooling) never writes.
|
|
304
|
+
*/
|
|
305
|
+
function isFumadocsManifestTransition(resolved, canonical) {
|
|
306
|
+
const { config } = resolved;
|
|
307
|
+
if (!canonical.configuredRoot ||
|
|
308
|
+
!isInside(canonical.configuredRoot, canonical.outputPath)) {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
const tooling = trimmed(config.documentation?.tooling)?.toLowerCase();
|
|
312
|
+
if (tooling) {
|
|
313
|
+
return tooling === 'fumadocs';
|
|
314
|
+
}
|
|
315
|
+
return trimmed(config.documentation?.config) === null;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* The repo-relative value recorded in `documentation.index`.
|
|
319
|
+
*
|
|
320
|
+
* Eligibility is decided on canonical paths, so an operator who supplied an
|
|
321
|
+
* aliased spelling of the output (`/private/tmp/...` for `/tmp/...`) would
|
|
322
|
+
* otherwise persist a machine-specific path into tracked config. Take the
|
|
323
|
+
* canonical suffix inside the configured root and rebase it onto that root's
|
|
324
|
+
* configured spelling, which is repo-relative by construction.
|
|
325
|
+
*/
|
|
326
|
+
function buildDocumentationIndexValue(resolved, canonical) {
|
|
327
|
+
const suffix = relative(canonical.configuredRoot, canonical.outputPath);
|
|
328
|
+
const portableOutput = join(resolved.configuredRoot, suffix);
|
|
329
|
+
const relativeOutput = relative(resolved.repoRoot, portableOutput);
|
|
330
|
+
// An empty relative path means the output *is* the repository root directory,
|
|
331
|
+
// which is never written. A `documentation.root` outside the repository would
|
|
332
|
+
// persist an absolute or `../` path. Withhold the key in both cases rather
|
|
333
|
+
// than record a value pointing at a file this run did not produce.
|
|
334
|
+
if (!relativeOutput || escapesBase(relativeOutput)) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
return normalizeToPosixPath(relativeOutput);
|
|
338
|
+
}
|
|
28
339
|
async function runIndexGenerate(context, options, deps) {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const entries = await deps.generateIndex(docsDir
|
|
340
|
+
const resolved = await resolveIndexGeneratePaths(context, options, deps);
|
|
341
|
+
const canonical = await canonicalizeIndexGeneratePaths(resolved, deps);
|
|
342
|
+
await assertOutputIsSafe(resolved, canonical, deps);
|
|
343
|
+
const { config, docsDir, outputPath, repoRoot } = resolved;
|
|
344
|
+
const entries = await deps.generateIndex(docsDir, {
|
|
345
|
+
excludes: resolved.excludes,
|
|
346
|
+
});
|
|
34
347
|
const content = buildGeneratedIndexOutput(deps.renderIndex(entries));
|
|
35
348
|
await deps.writeFile(outputPath, content, 'utf8');
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
}
|
|
349
|
+
if (isFumadocsManifestTransition(resolved, canonical)) {
|
|
350
|
+
const indexPath = buildDocumentationIndexValue(resolved, canonical);
|
|
351
|
+
if (indexPath !== null && config.documentation?.index !== indexPath) {
|
|
352
|
+
await deps.writeOatConfig(repoRoot, {
|
|
353
|
+
...config,
|
|
354
|
+
documentation: {
|
|
355
|
+
...config.documentation,
|
|
356
|
+
index: indexPath,
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
}
|
|
47
360
|
}
|
|
48
361
|
if (context.json) {
|
|
49
362
|
context.logger.json({
|
|
50
363
|
status: 'ok',
|
|
51
364
|
entriesGenerated: entries.length,
|
|
365
|
+
docsDir,
|
|
366
|
+
docsDirSource: resolved.docsDirSource,
|
|
52
367
|
outputPath,
|
|
53
368
|
});
|
|
54
369
|
return;
|
|
55
370
|
}
|
|
56
|
-
context.logger.info(`Generated index with ${entries.length} entries → ${outputPath}`);
|
|
371
|
+
context.logger.info(`Generated index with ${entries.length} entries from ${docsDir} → ${outputPath}`);
|
|
57
372
|
}
|
|
58
373
|
async function runIndexGenerateCommand(context, options, dependencies) {
|
|
59
374
|
try {
|
|
@@ -68,7 +383,7 @@ async function runIndexGenerateCommand(context, options, dependencies) {
|
|
|
68
383
|
else {
|
|
69
384
|
context.logger.error(message);
|
|
70
385
|
}
|
|
71
|
-
process.exitCode = 1;
|
|
386
|
+
process.exitCode = error instanceof CliError ? error.exitCode : 1;
|
|
72
387
|
}
|
|
73
388
|
}
|
|
74
389
|
export function createDocsGenerateIndexCommand(overrides = {}) {
|
|
@@ -78,8 +393,9 @@ export function createDocsGenerateIndexCommand(overrides = {}) {
|
|
|
78
393
|
};
|
|
79
394
|
return new Command('generate-index')
|
|
80
395
|
.description('Generate a docs index from markdown files')
|
|
81
|
-
.addOption(new Option('--docs-dir <path>', 'Documentation source directory
|
|
82
|
-
.addOption(new Option('--output <path>', 'Output file path (default: index.md)'))
|
|
396
|
+
.addOption(new Option('--docs-dir <path>', 'Documentation source directory, resolved from the CWD (default: `<documentation.root>/docs` when that directory exists, otherwise `<documentation.root>`)'))
|
|
397
|
+
.addOption(new Option('--output <path>', 'Output file path, resolved from the CWD (default: `<documentation.root>/index.md`)'))
|
|
398
|
+
.addOption(new Option('--exclude <glob>', 'Exclude matching paths, relative to the docs directory (`*` within a segment, `**` across segments, trailing `/` for directories only). Repeatable, and additive to `documentation.excludes`').argParser(collectExclude))
|
|
83
399
|
.action(async (options, command) => {
|
|
84
400
|
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
85
401
|
await runIndexGenerateCommand(context, options, dependencies);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OatDocumentationConfig } from '../../../config/oat-config.js';
|
|
2
|
-
import type { DocsInitResolvedOptions } from './resolve-options.js';
|
|
2
|
+
import type { DocsFramework, DocsInitResolvedOptions } from './resolve-options.js';
|
|
3
3
|
export interface ScaffoldDocsAppOptions extends DocsInitResolvedOptions {
|
|
4
4
|
assetsRoot: string;
|
|
5
5
|
}
|
|
@@ -21,6 +21,16 @@ export interface ScaffoldDocsAppDependencies {
|
|
|
21
21
|
export declare const PNPM_FALLBACK = "10.13.1";
|
|
22
22
|
export declare function detectIsOatRepo(repoRoot: string): Promise<boolean>;
|
|
23
23
|
export declare function resolveOatDepContext(repoRoot: string, assetsRoot: string): Promise<OatDepContext>;
|
|
24
|
+
/**
|
|
25
|
+
* Seed `documentation.*` for a freshly scaffolded app.
|
|
26
|
+
*
|
|
27
|
+
* For Fumadocs, `index` names the generated app-root manifest that
|
|
28
|
+
* `oat docs generate-index` writes (`<root>/index.md`), not the authored source
|
|
29
|
+
* page at `<root>/docs/index.md`: the scaffold must be correct before the first
|
|
30
|
+
* build rather than depending on a later config mutation. The MkDocs seed keeps
|
|
31
|
+
* pointing at the nav/config YAML, which is that framework's entry point.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildDocumentationConfig(framework: DocsFramework, targetDir: string): OatDocumentationConfig;
|
|
24
34
|
export declare function scaffoldDocsApp(options: ScaffoldDocsAppOptions, overrides?: Partial<ScaffoldDocsAppDependencies>): Promise<ScaffoldDocsAppResult>;
|
|
25
35
|
export {};
|
|
26
36
|
//# sourceMappingURL=scaffold.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/init/scaffold.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAKjE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/init/scaffold.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAKjE,OAAO,KAAK,EAEV,aAAa,EACb,uBAAuB,EAExB,MAAM,mBAAmB,CAAC;AAmF3B,MAAM,WAAW,sBAAuB,SAAQ,uBAAuB;IACrE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,mBAAmB,EAAE,sBAAsB,CAAC;CAC7C;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5C;AAED,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEtC,MAAM,WAAW,2BAA2B;IAC1C,iBAAiB,EAAE,MAAM,YAAY,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACjE,0BAA0B,EAAE,CAC1B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,KACZ,YAAY,CAAC,OAAO,CAAC,CAAC;CAC5B;AAUD,eAAO,MAAM,aAAa,YAAY,CAAC;AAEvC,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOxE;AAgDD,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,CAAC,CAaxB;AAiMD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,aAAa,EACxB,SAAS,EAAE,MAAM,GAChB,sBAAsB,CAexB;AAaD,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,EAC/B,SAAS,GAAE,OAAO,CAAC,2BAA2B,CAAM,GACnD,OAAO,CAAC,qBAAqB,CAAC,CAkDhC"}
|
|
@@ -256,12 +256,21 @@ function renderTemplate(template, options, depContext, packageManagerField) {
|
|
|
256
256
|
};
|
|
257
257
|
return Object.entries(replacements).reduce((content, [token, value]) => content.replaceAll(token, value), template);
|
|
258
258
|
}
|
|
259
|
-
|
|
259
|
+
/**
|
|
260
|
+
* Seed `documentation.*` for a freshly scaffolded app.
|
|
261
|
+
*
|
|
262
|
+
* For Fumadocs, `index` names the generated app-root manifest that
|
|
263
|
+
* `oat docs generate-index` writes (`<root>/index.md`), not the authored source
|
|
264
|
+
* page at `<root>/docs/index.md`: the scaffold must be correct before the first
|
|
265
|
+
* build rather than depending on a later config mutation. The MkDocs seed keeps
|
|
266
|
+
* pointing at the nav/config YAML, which is that framework's entry point.
|
|
267
|
+
*/
|
|
268
|
+
export function buildDocumentationConfig(framework, targetDir) {
|
|
260
269
|
if (framework === 'fumadocs') {
|
|
261
270
|
return {
|
|
262
271
|
root: targetDir,
|
|
263
272
|
tooling: 'fumadocs',
|
|
264
|
-
index: join(targetDir, '
|
|
273
|
+
index: join(targetDir, 'index.md'),
|
|
265
274
|
};
|
|
266
275
|
}
|
|
267
276
|
return {
|
|
@@ -6,6 +6,12 @@ export interface OatDocumentationConfig {
|
|
|
6
6
|
config?: string;
|
|
7
7
|
index?: string;
|
|
8
8
|
requireForProjectCompletion?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Globs, relative to the docs directory `oat docs generate-index` indexes,
|
|
11
|
+
* whose matches are left out of the generated index. Trimmed, de-duplicated,
|
|
12
|
+
* and order-preserving.
|
|
13
|
+
*/
|
|
14
|
+
excludes?: string[];
|
|
9
15
|
}
|
|
10
16
|
export interface OatGitConfig {
|
|
11
17
|
defaultBranch?: string;
|
|
@@ -203,6 +209,14 @@ export declare function normalizeProjectPath(repoRoot: string, pathValue: string
|
|
|
203
209
|
export declare function resolveLocalPaths(config: OatConfig): string[];
|
|
204
210
|
export declare function readOatConfig(repoRoot: string): Promise<OatConfig>;
|
|
205
211
|
export declare function readOatConfigForDefaultScopeRepair(repoRoot: string): Promise<OatConfig>;
|
|
212
|
+
/**
|
|
213
|
+
* Read shared config with an unusable `documentation.excludes` dropped, so
|
|
214
|
+
* `oat config set documentation.excludes ...` can perform the repair its own
|
|
215
|
+
* validation error prescribes. Mirrors the `projects.defaultScope` precedent
|
|
216
|
+
* above: without it, the only key-specific repair command the error names is
|
|
217
|
+
* itself blocked by the invalid value.
|
|
218
|
+
*/
|
|
219
|
+
export declare function readOatConfigForDocumentationExcludesRepair(repoRoot: string): Promise<OatConfig>;
|
|
206
220
|
export declare function readOatLocalConfig(repoRoot: string): Promise<OatLocalConfig>;
|
|
207
221
|
export declare function writeOatConfig(repoRoot: string, config: OatConfig): Promise<void>;
|
|
208
222
|
export declare function writeOatLocalConfig(repoRoot: string, config: OatLocalConfig): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oat-config.d.ts","sourceRoot":"","sources":["../../src/config/oat-config.ts"],"names":[],"mappings":"AAQA,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EACL,2BAA2B,EAC3B,8BAA8B,EAC9B,iCAAiC,EACjC,+BAA+B,EAC/B,iCAAiC,EACjC,2BAA2B,EAC3B,KAAK,6BAA6B,EAClC,KAAK,yBAAyB,EAC9B,KAAK,+BAA+B,EACpC,KAAK,6BAA6B,EAClC,KAAK,gCAAgC,EACrC,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,GACjC,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"oat-config.d.ts","sourceRoot":"","sources":["../../src/config/oat-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -626,6 +626,38 @@ async function normalizeReadableProjectPath(repoRoot, pathValue) {
|
|
|
626
626
|
return null;
|
|
627
627
|
}
|
|
628
628
|
}
|
|
629
|
+
/**
|
|
630
|
+
* Parse `documentation.excludes` into a trimmed, de-duplicated,
|
|
631
|
+
* order-preserving list.
|
|
632
|
+
*
|
|
633
|
+
* Unlike its scalar `documentation.*` siblings, a malformed value is rejected
|
|
634
|
+
* rather than silently dropped: a typo in an exclusion list would otherwise
|
|
635
|
+
* publish pages the operator believed were excluded, with no signal anywhere.
|
|
636
|
+
* An absent key and an empty array are both "no exclusions".
|
|
637
|
+
*/
|
|
638
|
+
function normalizeDocumentationExcludes(value, configPath) {
|
|
639
|
+
if (value === undefined) {
|
|
640
|
+
return [];
|
|
641
|
+
}
|
|
642
|
+
const invalid = () => {
|
|
643
|
+
throw new CliError(`Invalid documentation.excludes in ${configPath}: expected an array of non-empty strings. ` +
|
|
644
|
+
'Repair it with oat config set documentation.excludes "<glob>,<glob>" (an empty value clears it).', 2);
|
|
645
|
+
};
|
|
646
|
+
if (!Array.isArray(value)) {
|
|
647
|
+
invalid();
|
|
648
|
+
}
|
|
649
|
+
const normalized = [];
|
|
650
|
+
for (const entry of value) {
|
|
651
|
+
if (typeof entry !== 'string' || !entry.trim()) {
|
|
652
|
+
invalid();
|
|
653
|
+
}
|
|
654
|
+
const trimmedEntry = entry.trim();
|
|
655
|
+
if (!normalized.includes(trimmedEntry)) {
|
|
656
|
+
normalized.push(trimmedEntry);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return normalized;
|
|
660
|
+
}
|
|
629
661
|
function normalizeOatConfig(parsed, configPath = '.oat/config.json') {
|
|
630
662
|
const next = { ...DEFAULT_OAT_CONFIG };
|
|
631
663
|
if (!isRecord(parsed)) {
|
|
@@ -741,6 +773,10 @@ function normalizeOatConfig(parsed, configPath = '.oat/config.json') {
|
|
|
741
773
|
doc.requireForProjectCompletion =
|
|
742
774
|
parsed.documentation.requireForProjectCompletion;
|
|
743
775
|
}
|
|
776
|
+
const excludes = normalizeDocumentationExcludes(parsed.documentation.excludes, configPath);
|
|
777
|
+
if (excludes.length > 0) {
|
|
778
|
+
doc.excludes = excludes;
|
|
779
|
+
}
|
|
744
780
|
if (Object.keys(doc).length > 0) {
|
|
745
781
|
next.documentation = doc;
|
|
746
782
|
}
|
|
@@ -828,6 +864,31 @@ export async function readOatConfigForDefaultScopeRepair(repoRoot) {
|
|
|
828
864
|
throw error;
|
|
829
865
|
}
|
|
830
866
|
}
|
|
867
|
+
/**
|
|
868
|
+
* Read shared config with an unusable `documentation.excludes` dropped, so
|
|
869
|
+
* `oat config set documentation.excludes ...` can perform the repair its own
|
|
870
|
+
* validation error prescribes. Mirrors the `projects.defaultScope` precedent
|
|
871
|
+
* above: without it, the only key-specific repair command the error names is
|
|
872
|
+
* itself blocked by the invalid value.
|
|
873
|
+
*/
|
|
874
|
+
export async function readOatConfigForDocumentationExcludesRepair(repoRoot) {
|
|
875
|
+
const configPath = getConfigPath(repoRoot);
|
|
876
|
+
try {
|
|
877
|
+
const raw = await readFile(configPath, 'utf8');
|
|
878
|
+
const parsed = parseJsonConfig(raw, configPath);
|
|
879
|
+
if (isRecord(parsed) && isRecord(parsed.documentation)) {
|
|
880
|
+
const { excludes: _invalidExcludes, ...documentation } = parsed.documentation;
|
|
881
|
+
return normalizeOatConfig({ ...parsed, documentation }, configPath);
|
|
882
|
+
}
|
|
883
|
+
return normalizeOatConfig(parsed, configPath);
|
|
884
|
+
}
|
|
885
|
+
catch (error) {
|
|
886
|
+
if (isMissingFileError(error)) {
|
|
887
|
+
return { ...DEFAULT_OAT_CONFIG };
|
|
888
|
+
}
|
|
889
|
+
throw error;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
831
892
|
export async function readOatLocalConfig(repoRoot) {
|
|
832
893
|
const configPath = getLocalConfigPath(repoRoot);
|
|
833
894
|
try {
|