@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
|
@@ -90,6 +90,7 @@ Common keys in `.oat/config.json`:
|
|
|
90
90
|
- `worktrees.root` — where OAT-managed worktrees live
|
|
91
91
|
- `git.defaultBranch` — base branch fallback for PR workflows
|
|
92
92
|
- `documentation.root`, `documentation.tooling`, `documentation.config` — docs-surface ownership
|
|
93
|
+
- `documentation.excludes` — a JSON array of globs, relative to the docs directory, that `oat docs generate-index` leaves out of the generated index. `oat config set` takes the list as one comma-separated value (`"a/**,b.md"`) and stores it as an array; repeated `--exclude` flags extend it, and an empty value clears the key
|
|
93
94
|
- `documentation.requireForProjectCompletion` — whether docs sync is a completion gate
|
|
94
95
|
- `archive.s3Uri` — base S3 archive prefix
|
|
95
96
|
- `archive.s3SyncOnComplete` — upload archived projects to S3 during completion
|
|
@@ -242,8 +243,17 @@ packaged `assets/` directory next to the installed CLI. Setting a non-empty
|
|
|
242
243
|
directory.
|
|
243
244
|
- The override is validated exactly like the packaged root and fails closed:
|
|
244
245
|
the path must be a directory containing bundle metadata whose version matches
|
|
245
|
-
the running CLI (`OAT_VERSION`)
|
|
246
|
-
|
|
246
|
+
the running CLI (`OAT_VERSION`) **and** the seven top-level directories the
|
|
247
|
+
bundle producer creates (`skills`, `agents`, `templates`, `scripts`, `docs`,
|
|
248
|
+
`migration`, `config`), each of which must be a directory. A missing,
|
|
249
|
+
malformed, version-mismatched, or structurally incomplete override is an
|
|
250
|
+
error with exit code 2 that names the first offending path — the CLI never
|
|
251
|
+
silently falls back to packaged assets, and a metadata-only directory is no
|
|
252
|
+
longer accepted as an empty installation.
|
|
253
|
+
- Remedies are source-aware: an `OAT_ASSETS_DIR` failure tells you to fix or
|
|
254
|
+
unset the override, never to rebuild or reinstall the CLI; a packaged-bundle
|
|
255
|
+
failure keeps the rebuild/reinstall guidance. A directory that exists but
|
|
256
|
+
cannot be read reports the underlying error code.
|
|
247
257
|
- Produce a matching bundle with `bash packages/cli/scripts/bundle-assets.sh`
|
|
248
258
|
while `OAT_ASSETS_DIR` points at the target directory (the script already
|
|
249
259
|
honors the variable as its destination).
|
|
@@ -128,23 +128,54 @@ Key behavior:
|
|
|
128
128
|
- recursively walks the docs directory
|
|
129
129
|
- extracts page titles from frontmatter (falls back to first `# heading`, then filename title-case)
|
|
130
130
|
- includes descriptions from frontmatter when present
|
|
131
|
-
-
|
|
132
|
-
-
|
|
131
|
+
- resolves omitted paths from `.oat/config.json` rather than the current directory: `--docs-dir` defaults to `<documentation.root>/docs` when that directory exists and to `<documentation.root>` otherwise, and `--output` defaults to the app-root manifest `<documentation.root>/index.md`
|
|
132
|
+
- treats explicitly supplied `--docs-dir` / `--output` paths as overrides resolved from the current directory
|
|
133
|
+
- fails with exit code `2` before generating or writing anything when an omitted path has no non-empty `documentation.root` to resolve against, or when the configured root is not a directory
|
|
134
|
+
- refuses unsafe output targets with exit code `1` before generation, for derived and explicit paths alike, comparing symlink-resolved paths: inside the docs directory it indexes, equal to `documentation.config`, or ending in `.yml` / `.yaml` in any case. A derived output whose existing file lacks the `AUTOGENERATED` header is refused too; naming that path with `--output` overwrites it explicitly. A refused target is an actionable flag error (`1`); unusable path configuration is a separate condition and exits `2`, so scripts can branch on the two
|
|
135
|
+
- writes configuration only for the Fumadocs manifest transition — recording the manifest it just wrote inside `documentation.root` in `documentation.index`, when `documentation.tooling` is `fumadocs` or the config declares neither `tooling` nor `config`. MkDocs configurations are never written, and `documentation.root` and `documentation.config` are never modified
|
|
133
136
|
- prepends an `AUTOGENERATED` warning comment to the output and rewrites the file on every run; do not hand-edit the generated `index.md`
|
|
134
137
|
- should be freshness-checked against authored `docs/**/index.md` `## Contents` maps before treating it as navigation evidence
|
|
135
138
|
- sorting: `index.md` first, then directories before files, then lexical
|
|
139
|
+
- reports the derived docs directory in human output and as `docsDir` / `docsDirSource` under `--json`
|
|
140
|
+
- omits pages matching the exclusion list. Nothing is excluded by default; once listed, non-page Markdown such as `CLAUDE.md` or `AGENTS.md` stops reaching the manifest. Patterns come from `documentation.excludes` in `.oat/config.json` and from repeated `--exclude` flags, and the flags **extend** the configured list rather than replacing it. A directory left empty by exclusion emits no heading, and an empty list produces byte-identical output to no exclusions at all
|
|
136
141
|
|
|
137
142
|
Supported flags:
|
|
138
143
|
|
|
139
|
-
- `--docs-dir <path>` (default:
|
|
140
|
-
- `--output <path>` (default:
|
|
144
|
+
- `--docs-dir <path>` (default: `<documentation.root>/docs`, falling back to `<documentation.root>`)
|
|
145
|
+
- `--output <path>` (default: `<documentation.root>/index.md`)
|
|
146
|
+
- `--exclude <glob>` (repeatable; additive to `documentation.excludes`)
|
|
147
|
+
|
|
148
|
+
### Exclusion patterns
|
|
149
|
+
|
|
150
|
+
Patterns match the path of each candidate **relative to the docs directory being indexed** — `api/auth.md`, `api/nested` — never an absolute or current-directory-relative path, and never the repository-relative path.
|
|
151
|
+
|
|
152
|
+
| Pattern | Matches |
|
|
153
|
+
| -------------- | -------------------------------------------------------- |
|
|
154
|
+
| `CLAUDE.md` | only the root-level `CLAUDE.md`; patterns are anchored |
|
|
155
|
+
| `**/CLAUDE.md` | `CLAUDE.md` at any depth, including the docs root |
|
|
156
|
+
| `*.md` | root-level Markdown only; `*` never crosses `/` |
|
|
157
|
+
| `drafts/` | the `drafts` directory and everything beneath it |
|
|
158
|
+
| `drafts` | the same directory; the trailing `/` only forbids a file |
|
|
159
|
+
| `api/**/*.md` | Markdown at any depth under `api/`, including `api/x.md` |
|
|
160
|
+
|
|
161
|
+
A trailing `/` restricts a pattern to directories and never matches a file; without it, a pattern that matches a directory path still prunes that directory. `**` spans `/` only as a whole path segment — inside a segment (`a**b`) it is an ordinary single-segment wildcard. Matching is case-sensitive, `/` is the separator on every platform, and only `*` and `**` are metacharacters — every other character, `.` included, is literal. A leading `./` or `/` is stripped, so both spellings anchor at the docs root.
|
|
141
162
|
|
|
142
163
|
Example:
|
|
143
164
|
|
|
144
165
|
```bash
|
|
145
|
-
|
|
166
|
+
# Resolve both paths from .oat/config.json
|
|
167
|
+
oat docs generate-index
|
|
168
|
+
|
|
169
|
+
# Portable explicit form, resolved from the current directory
|
|
170
|
+
oat docs generate-index --docs-dir apps/oat-docs/docs --output apps/oat-docs/index.md
|
|
171
|
+
|
|
172
|
+
# Persist the repository's own exclusions, then add a one-off
|
|
173
|
+
oat config set documentation.excludes "**/CLAUDE.md,**/AGENTS.md"
|
|
174
|
+
oat docs generate-index --exclude 'drafts/'
|
|
146
175
|
```
|
|
147
176
|
|
|
177
|
+
See [Documentation path resolution](../reference/oat-directory-structure.md#documentation-path-resolution) for the app-root meaning of `documentation.root` and the legacy source-root compatibility rule.
|
|
178
|
+
|
|
148
179
|
The Fumadocs scaffold runs this automatically via `predev`/`prebuild` npm
|
|
149
180
|
script hooks.
|
|
150
181
|
|
|
@@ -117,10 +117,11 @@ Current schema keys:
|
|
|
117
117
|
| `projects.root` | `string` | `".oat/projects/shared"` | Default root directory for OAT projects |
|
|
118
118
|
| `projects.defaultScope` | `string` | `"synced"` | Scope used by project creation when `--scope` is omitted: `shared`, `local`, or `synced`. Environment override: `OAT_PROJECTS_DEFAULT_SCOPE`. |
|
|
119
119
|
| `localPaths` | `string[]` | - | Gitignored directories to sync between main repo and worktrees. Supports glob patterns. Managed via `oat local add/remove`. |
|
|
120
|
-
| `documentation.root` | `string` | - |
|
|
120
|
+
| `documentation.root` | `string` | - | Docs **app root** — the directory `oat docs init` scaffolds (e.g., `apps/docs`). Authored pages live under `<root>/docs`. See [Documentation path resolution](#documentation-path-resolution). |
|
|
121
121
|
| `documentation.tooling` | `string` | - | Documentation framework identifier (`mkdocs` or `fumadocs`) |
|
|
122
122
|
| `documentation.config` | `string` | - | Path to the documentation framework config file (e.g., `mkdocs.yml`, `next.config.js`) |
|
|
123
|
-
| `documentation.index` | `string` | - | Path to the docs surface entry point
|
|
123
|
+
| `documentation.index` | `string` | - | Path to the docs surface entry point: the generated app-root manifest `<root>/index.md` for Fumadocs, `mkdocs.yml` for MkDocs. Set by `oat docs init`. `oat docs generate-index` writes it only for the Fumadocs manifest transition and never for MkDocs. |
|
|
124
|
+
| `documentation.excludes` | `string[]` | - | Globs, relative to the docs directory, that `oat docs generate-index` leaves out of the generated index. See [Excluding pages from the generated index](#excluding-pages-from-the-generated-index). |
|
|
124
125
|
| `documentation.requireForProjectCompletion` | `boolean` | `false` | When `true`, OAT project completion gates require documentation to be updated |
|
|
125
126
|
| `git.defaultBranch` | `string` | `"main"` | Default branch for PR creation. Auto-detected during `oat init` via `gh repo view` or `origin/HEAD`. Used by `oat-project-pr-final` and `oat-project-pr-progress`. |
|
|
126
127
|
| `workflow.autoReviewAtHillCheckpoints` | `boolean` | unset | When `true`, completing a HiLL checkpoint automatically runs the extra lifecycle review. Does not control Tier 1 per-phase `oat-reviewer` gates. Can be overridden per-project via `oat_auto_review_at_hill_checkpoints` in `plan.md` frontmatter. Legacy `autoReviewAtCheckpoints` remains a fallback. |
|
|
@@ -157,14 +158,65 @@ Example:
|
|
|
157
158
|
"root": ".worktrees"
|
|
158
159
|
},
|
|
159
160
|
"documentation": {
|
|
160
|
-
"root": "apps/docs
|
|
161
|
+
"root": "apps/docs",
|
|
161
162
|
"tooling": "mkdocs",
|
|
162
|
-
"config": "mkdocs.yml",
|
|
163
|
+
"config": "apps/docs/mkdocs.yml",
|
|
164
|
+
"index": "apps/docs/mkdocs.yml",
|
|
163
165
|
"requireForProjectCompletion": false
|
|
164
166
|
}
|
|
165
167
|
}
|
|
166
168
|
```
|
|
167
169
|
|
|
170
|
+
### Documentation path resolution
|
|
171
|
+
|
|
172
|
+
`documentation.root` canonically names the docs **app root** — exactly the directory `oat docs init` scaffolds. Authored Markdown lives under `<root>/docs`, and the generated Fumadocs manifest is written to `<root>/index.md`, outside the tree it indexes.
|
|
173
|
+
|
|
174
|
+
`oat docs generate-index` resolves omitted options from this configuration:
|
|
175
|
+
|
|
176
|
+
| Option | Omitted | Supplied |
|
|
177
|
+
| ------------ | ---------------------------------------------------------------------------------------- | ----------------------------------- |
|
|
178
|
+
| `--docs-dir` | `<documentation.root>/docs` when that directory exists, otherwise `<documentation.root>` | Resolved from the current directory |
|
|
179
|
+
| `--output` | `<documentation.root>/index.md` | Resolved from the current directory |
|
|
180
|
+
|
|
181
|
+
If an omitted option has no non-empty `documentation.root` to resolve against, or the configured root is not a directory, the command fails with exit code `2` before generating or writing anything.
|
|
182
|
+
|
|
183
|
+
**Compatibility behavior.** Preferring `<root>/docs` exists for legacy configs whose `root` names a docs _source_ directory rather than an app root; it is not a second meaning of the key. Because the rule is a plain directory-existence test, a source root that happens to contain its own `docs` subdirectory is narrowed to it. The command prints the directory it derived in both human and JSON output (`docsDir`, `docsDirSource`), and `--docs-dir` is the escape hatch when the derivation is wrong.
|
|
184
|
+
|
|
185
|
+
A legacy source root with no `docs` child resolves `--docs-dir` and `--output` to the same tree, so a bare run is refused rather than allowed to overwrite an authored page. Pass an explicit `--output` outside the source tree, or repoint `documentation.root` at the app root.
|
|
186
|
+
|
|
187
|
+
**Refused output targets.** Checked before generation for derived and explicit paths alike, the command refuses to write an output that is inside the docs directory it indexes, equals `documentation.config`, or ends in `.yml` / `.yaml`. A derived output whose existing file lacks the `AUTOGENERATED by oat docs generate-index` header is also refused; naming that same path with `--output` overwrites it explicitly.
|
|
188
|
+
|
|
189
|
+
**Configuration writes.** The only configuration write generation performs is the Fumadocs manifest transition: when the written manifest lies inside `documentation.root` and `documentation.tooling` is `fumadocs`, the repo-relative output path is recorded in `documentation.index`. A config that declares neither `documentation.tooling` nor `documentation.config` is treated as the same Fumadocs-shaped case. Any other declared tooling — `mkdocs` included — is never written, and neither are `documentation.root` and `documentation.config`. The value is always repo-relative: when `documentation.root` points outside the repository, the manifest is still generated but the key is left untouched rather than recording a machine-specific path.
|
|
190
|
+
|
|
191
|
+
### Excluding pages from the generated index
|
|
192
|
+
|
|
193
|
+
`documentation.excludes` and the repeatable `--exclude <glob>` flag feed one matcher. Flags **extend** the configured list rather than replacing it, so a one-off exclusion cannot silently republish pages the repository deliberately excluded. A directory left empty by exclusion emits no heading, and an empty list produces byte-identical output to no exclusions at all.
|
|
194
|
+
|
|
195
|
+
Patterns are matched against each candidate's path **relative to the docs directory being indexed** — the directory `--docs-dir` resolved to, not `documentation.root` and not the repository root.
|
|
196
|
+
|
|
197
|
+
| Pattern | Matches |
|
|
198
|
+
| -------------- | -------------------------------------------------------- |
|
|
199
|
+
| `CLAUDE.md` | only the root-level `CLAUDE.md`; patterns are anchored |
|
|
200
|
+
| `**/CLAUDE.md` | `CLAUDE.md` at any depth, including the docs root |
|
|
201
|
+
| `*.md` | root-level Markdown only; `*` never crosses `/` |
|
|
202
|
+
| `drafts/` | the `drafts` directory and everything beneath it |
|
|
203
|
+
| `api/**/*.md` | Markdown at any depth under `api/`, including `api/x.md` |
|
|
204
|
+
|
|
205
|
+
A trailing `/` restricts a pattern to directories and never matches a file; without it, a pattern that matches a directory path still prunes that directory. `**` spans `/` only as a whole path segment — inside a segment (`a**b`) it is an ordinary single-segment wildcard. Matching is case-sensitive, `/` is the separator on every platform, and only `*` and `**` are metacharacters — every other character, `.` included, is literal. A leading `./` or `/` is stripped, so both spellings anchor at the docs root. Entries are trimmed and de-duplicated; a malformed `documentation.excludes` (a non-array, or an empty or non-string entry) is rejected rather than silently ignored. `oat docs generate-index` reports it with exit code `2`; `oat config` surfaces the same error as an ordinary exit-`1` user error. `oat config set documentation.excludes` can still replace or clear a malformed value, so the repair the error names always works.
|
|
206
|
+
|
|
207
|
+
Set the list with the comma-separated grammar, and clear it with an empty value:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
oat config set documentation.excludes "**/CLAUDE.md,**/AGENTS.md"
|
|
211
|
+
oat config set documentation.excludes ""
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
This repository pins the invocation explicitly, which is the portable fallback anywhere the configured defaults are not what you want:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
oat docs generate-index --docs-dir apps/oat-docs/docs --output apps/oat-docs/index.md
|
|
218
|
+
```
|
|
219
|
+
|
|
168
220
|
### Worktree root precedence
|
|
169
221
|
|
|
170
222
|
When resolving the worktree root directory, `oat-worktree-bootstrap` uses this strict precedence (stops at the first match):
|
|
@@ -10,6 +10,7 @@ interface ConfigCommandDependencies {
|
|
|
10
10
|
resolveProjectRoot: (cwd: string) => Promise<string>;
|
|
11
11
|
readOatConfig: (repoRoot: string) => Promise<OatConfig>;
|
|
12
12
|
readOatConfigForDefaultScopeRepair: (repoRoot: string) => Promise<OatConfig>;
|
|
13
|
+
readOatConfigForDocumentationExcludesRepair: (repoRoot: string) => Promise<OatConfig>;
|
|
13
14
|
writeOatConfig: (repoRoot: string, config: OatConfig) => Promise<void>;
|
|
14
15
|
readOatLocalConfig: (repoRoot: string) => Promise<OatLocalConfig>;
|
|
15
16
|
writeOatLocalConfig: (repoRoot: string, config: OatLocalConfig) => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/config/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGhF,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,iCAAiC,CAAC;AAmBzC,OAAO,EAML,KAAK,SAAS,EACd,KAAK,cAAc,EAGnB,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/config/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGhF,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,iCAAiC,CAAC;AAmBzC,OAAO,EAML,KAAK,SAAS,EACd,KAAK,cAAc,EAGnB,KAAK,UAAU,EAiBhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAGL,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAC/B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,mCAAmC,EACnC,0BAA0B,EAC3B,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2GpC,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,kCAAkC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IAC7E,2CAA2C,EAAE,CAC3C,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,SAAS,CAAC,CAAC;IACxB,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,cAAc,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/D,eAAe,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzE,kBAAkB,EAAE,CAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC7C,mCAAmC,EAAE,OAAO,mCAAmC,CAAC;IAChF,0BAA0B,EAAE,OAAO,0BAA0B,CAAC;IAC9D,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAunFD,wBAAgB,mBAAmB,CACjC,SAAS,GAAE,OAAO,CAAC,yBAAyB,CAAM,GACjD,OAAO,CAsKT"}
|
|
@@ -9,7 +9,7 @@ import { compileDispatchCeilingPreset } from '../../config/dispatch-ceiling-pres
|
|
|
9
9
|
import { normalizeDispatchMatrix, validateDispatchRouteTarget, walkDispatchMatrix, } from '../../config/dispatch-matrix.js';
|
|
10
10
|
import { formatDispatchNotices, terminalReviewerNoticesForMatrix, } from '../../config/dispatch-notices.js';
|
|
11
11
|
import { dispatchPolicyModeDescription, dispatchPolicyPolicyDescription, managedDispatchPolicyValueList, } from '../../config/dispatch-policy-options.js';
|
|
12
|
-
import { VALID_DISPATCH_POLICY_MODES, VALID_MANAGED_DISPATCH_POLICIES, MAX_GATE_TIMEOUT_MS, MIN_GATE_TIMEOUT_MS, isValidGateTimeoutMs, normalizeWorkflowPostImplementSequence, readOatConfig, readOatConfigForDefaultScopeRepair, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
|
|
12
|
+
import { VALID_DISPATCH_POLICY_MODES, VALID_MANAGED_DISPATCH_POLICIES, MAX_GATE_TIMEOUT_MS, MIN_GATE_TIMEOUT_MS, isValidGateTimeoutMs, normalizeWorkflowPostImplementSequence, readOatConfig, readOatConfigForDefaultScopeRepair, readOatConfigForDocumentationExcludesRepair, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
|
|
13
13
|
import { resolveEffectiveConfig, } from '../../config/resolve.js';
|
|
14
14
|
import { resolveAssetsRoot } from '../../fs/assets.js';
|
|
15
15
|
import { resolveProjectRoot } from '../../fs/paths.js';
|
|
@@ -38,6 +38,7 @@ const KEY_ORDER = [
|
|
|
38
38
|
'documentation.root',
|
|
39
39
|
'documentation.tooling',
|
|
40
40
|
'documentation.config',
|
|
41
|
+
'documentation.excludes',
|
|
41
42
|
'documentation.requireForProjectCompletion',
|
|
42
43
|
'explainers.defaults.style',
|
|
43
44
|
'explainers.defaults.palette',
|
|
@@ -177,6 +178,17 @@ const CONFIG_CATALOG = [
|
|
|
177
178
|
owningCommand: 'oat config set documentation.config <value>',
|
|
178
179
|
description: 'Repository-relative path to the primary documentation tool config file.',
|
|
179
180
|
},
|
|
181
|
+
{
|
|
182
|
+
key: 'documentation.excludes',
|
|
183
|
+
group: 'Shared Repo (.oat/config.json)',
|
|
184
|
+
file: '.oat/config.json',
|
|
185
|
+
scope: 'shared repo',
|
|
186
|
+
type: 'string[]',
|
|
187
|
+
defaultValue: 'unset',
|
|
188
|
+
mutability: 'read/write',
|
|
189
|
+
owningCommand: 'oat config set documentation.excludes <glob[,glob...]>',
|
|
190
|
+
description: 'Comma-separated globs, relative to the docs directory, excluded from `oat docs generate-index`. Repeated `--exclude` flags extend this list; an empty value clears the key.',
|
|
191
|
+
},
|
|
180
192
|
{
|
|
181
193
|
key: 'documentation.requireForProjectCompletion',
|
|
182
194
|
group: 'Shared Repo (.oat/config.json)',
|
|
@@ -854,6 +866,7 @@ const DEFAULT_DEPENDENCIES = {
|
|
|
854
866
|
resolveProjectRoot,
|
|
855
867
|
readOatConfig,
|
|
856
868
|
readOatConfigForDefaultScopeRepair,
|
|
869
|
+
readOatConfigForDocumentationExcludesRepair,
|
|
857
870
|
writeOatConfig,
|
|
858
871
|
readOatLocalConfig,
|
|
859
872
|
writeOatLocalConfig,
|
|
@@ -916,6 +929,20 @@ function normalizeSharedRoot(value) {
|
|
|
916
929
|
}
|
|
917
930
|
return trimmed.replace(/\/+$/, '');
|
|
918
931
|
}
|
|
932
|
+
/**
|
|
933
|
+
* Split the one array-valued config key's comma-separated grammar.
|
|
934
|
+
*
|
|
935
|
+
* Entries are trimmed and de-duplicated in order; blank entries are dropped, so
|
|
936
|
+
* an empty (or all-blank) value yields an empty list, which the caller treats
|
|
937
|
+
* as "clear the key" rather than as an error.
|
|
938
|
+
*/
|
|
939
|
+
function parseDocumentationExcludes(rawValue) {
|
|
940
|
+
const parsed = rawValue
|
|
941
|
+
.split(',')
|
|
942
|
+
.map((entry) => entry.trim())
|
|
943
|
+
.filter((entry) => entry.length > 0);
|
|
944
|
+
return [...new Set(parsed)];
|
|
945
|
+
}
|
|
919
946
|
function parseExplainerValue(key, rawValue, surface) {
|
|
920
947
|
const value = rawValue.trim();
|
|
921
948
|
if (!value) {
|
|
@@ -1633,7 +1660,12 @@ async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, d
|
|
|
1633
1660
|
});
|
|
1634
1661
|
return { key, value: defaultScope, source: 'shared' };
|
|
1635
1662
|
}
|
|
1636
|
-
|
|
1663
|
+
// `documentation.excludes` is the one shared key whose own validation error
|
|
1664
|
+
// names `oat config set` as the repair, so it reads leniently: a strict read
|
|
1665
|
+
// would reject the very value the operator is trying to replace.
|
|
1666
|
+
const config = key === 'documentation.excludes'
|
|
1667
|
+
? await dependencies.readOatConfigForDocumentationExcludesRepair(repoRoot)
|
|
1668
|
+
: await dependencies.readOatConfig(repoRoot);
|
|
1637
1669
|
if (key.startsWith('documentation.')) {
|
|
1638
1670
|
const doc = { ...config.documentation };
|
|
1639
1671
|
if (key === 'documentation.root') {
|
|
@@ -1645,6 +1677,17 @@ async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, d
|
|
|
1645
1677
|
else if (key === 'documentation.config') {
|
|
1646
1678
|
doc.config = normalizeSharedRoot(rawValue);
|
|
1647
1679
|
}
|
|
1680
|
+
else if (key === 'documentation.excludes') {
|
|
1681
|
+
const excludes = parseDocumentationExcludes(rawValue);
|
|
1682
|
+
if (excludes.length === 0) {
|
|
1683
|
+
// Unlike the scalar shared keys, an empty value is the documented way
|
|
1684
|
+
// to clear this list rather than an error.
|
|
1685
|
+
delete doc.excludes;
|
|
1686
|
+
}
|
|
1687
|
+
else {
|
|
1688
|
+
doc.excludes = excludes;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1648
1691
|
else if (key === 'documentation.requireForProjectCompletion') {
|
|
1649
1692
|
doc.requireForProjectCompletion =
|
|
1650
1693
|
rawValue.trim().toLowerCase() === 'true';
|
|
@@ -1655,7 +1698,9 @@ async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, d
|
|
|
1655
1698
|
});
|
|
1656
1699
|
const resultValue = key === 'documentation.requireForProjectCompletion'
|
|
1657
1700
|
? String(doc.requireForProjectCompletion ?? false)
|
|
1658
|
-
:
|
|
1701
|
+
: key === 'documentation.excludes'
|
|
1702
|
+
? (doc.excludes ?? null)
|
|
1703
|
+
: (doc[key.replace('documentation.', '')] ?? null);
|
|
1659
1704
|
return {
|
|
1660
1705
|
key,
|
|
1661
1706
|
value: resultValue,
|
|
@@ -1990,7 +2035,9 @@ async function runSet(keyArg, rawValue, surface, context, dependencies) {
|
|
|
1990
2035
|
});
|
|
1991
2036
|
}
|
|
1992
2037
|
else {
|
|
1993
|
-
|
|
2038
|
+
// `formatResolvedValue` renders an array-valued key the same way `get`
|
|
2039
|
+
// does, rather than leaning on implicit `Array.prototype.toString`.
|
|
2040
|
+
context.logger.info(`${result.key}=${formatResolvedValue(result.value) ?? ''}`);
|
|
1994
2041
|
}
|
|
1995
2042
|
process.exitCode = 0;
|
|
1996
2043
|
}
|
|
@@ -4,6 +4,48 @@ export interface IndexEntry {
|
|
|
4
4
|
path: string;
|
|
5
5
|
children?: IndexEntry[];
|
|
6
6
|
}
|
|
7
|
-
export
|
|
7
|
+
export interface GenerateIndexOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Glob patterns whose matches are left out of the index. See
|
|
10
|
+
* {@link createExclusionMatcher} for the grammar; an empty or omitted list
|
|
11
|
+
* indexes everything, byte-identically to the pre-exclusion behavior.
|
|
12
|
+
*/
|
|
13
|
+
excludes?: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A compiled exclusion list.
|
|
17
|
+
*
|
|
18
|
+
* Files and directories are asked separately so a trailing-slash pattern can
|
|
19
|
+
* mean "directories only" while every other pattern applies to both.
|
|
20
|
+
*/
|
|
21
|
+
export interface ExclusionMatcher {
|
|
22
|
+
excludesFile: (relativePath: string) => boolean;
|
|
23
|
+
excludesDirectory: (relativePath: string) => boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Compile exclusion globs matched against **docs-root-relative POSIX paths**
|
|
27
|
+
* (`api/auth.md`, `api/nested`), never against absolute or CWD-relative ones.
|
|
28
|
+
*
|
|
29
|
+
* Grammar, defined once and documented in
|
|
30
|
+
* `apps/oat-docs/docs/reference/oat-directory-structure.md`:
|
|
31
|
+
*
|
|
32
|
+
* - `*` matches any run of characters inside a single path segment.
|
|
33
|
+
* - `**` spans `/` only as a whole segment, where it covers zero or more
|
|
34
|
+
* segments: `**\/CLAUDE.md` matches `CLAUDE.md` and `api/CLAUDE.md`. Inside a
|
|
35
|
+
* segment (`a**b`) it is an ordinary single-segment wildcard.
|
|
36
|
+
* - Patterns are anchored at both ends against the whole relative path, so a
|
|
37
|
+
* bare `CLAUDE.md` matches only the root-level file. Use `**\/CLAUDE.md` for
|
|
38
|
+
* every depth.
|
|
39
|
+
* - A trailing `/` restricts a pattern to directories: `drafts/` prunes the
|
|
40
|
+
* `drafts` directory and everything beneath it, and never matches a file.
|
|
41
|
+
* Without it, a pattern that matches a directory path prunes it too.
|
|
42
|
+
* - Matching is case-sensitive, `/` is the separator on every platform, and
|
|
43
|
+
* `*` is the only metacharacter — `.`, `+`, and friends are literal.
|
|
44
|
+
*
|
|
45
|
+
* Entries are trimmed; a leading `./` or `/` is stripped so both spellings
|
|
46
|
+
* anchor at the docs root. Blank entries are ignored.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createExclusionMatcher(patterns: readonly string[]): ExclusionMatcher;
|
|
49
|
+
export declare function generateIndex(docsDir: string, options?: GenerateIndexOptions): Promise<IndexEntry[]>;
|
|
8
50
|
export declare function renderIndex(entries: IndexEntry[]): string;
|
|
9
51
|
//# sourceMappingURL=generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/index-generate/generator.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;CACzB;
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/index-generate/generator.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;IAChD,iBAAiB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;CACtD;AAmHD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,gBAAgB,CAwClB;AAqID,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,UAAU,EAAE,CAAC,CAMvB;AAmBD,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAMzD"}
|
|
@@ -1,5 +1,155 @@
|
|
|
1
1
|
import { readdir, readFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
const NO_EXCLUSIONS = {
|
|
4
|
+
excludesFile: () => false,
|
|
5
|
+
excludesDirectory: () => false,
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Match one path segment against one pattern segment, where `*` is the only
|
|
9
|
+
* metacharacter and never crosses `/`.
|
|
10
|
+
*
|
|
11
|
+
* This is the classic greedy two-pointer wildcard match, deliberately not a
|
|
12
|
+
* compiled regular expression: a pattern like `*a*a*a...` translates to
|
|
13
|
+
* nested-quantifier alternatives that backtrack exponentially, and exclusion
|
|
14
|
+
* patterns come from config and flags where a typo should not hang generation.
|
|
15
|
+
* Restarting from the last `*` instead bounds the work at O(pattern x text).
|
|
16
|
+
*/
|
|
17
|
+
function matchSegment(pattern, text) {
|
|
18
|
+
let patternIndex = 0;
|
|
19
|
+
let textIndex = 0;
|
|
20
|
+
let starPatternIndex = -1;
|
|
21
|
+
let starTextIndex = 0;
|
|
22
|
+
while (textIndex < text.length) {
|
|
23
|
+
if (pattern[patternIndex] === '*') {
|
|
24
|
+
// Collapse a run of stars: within a segment they are all equivalent.
|
|
25
|
+
while (pattern[patternIndex] === '*') {
|
|
26
|
+
patternIndex += 1;
|
|
27
|
+
}
|
|
28
|
+
if (patternIndex === pattern.length) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
starPatternIndex = patternIndex;
|
|
32
|
+
starTextIndex = textIndex;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (patternIndex < pattern.length &&
|
|
36
|
+
pattern[patternIndex] === text[textIndex]) {
|
|
37
|
+
patternIndex += 1;
|
|
38
|
+
textIndex += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (starPatternIndex === -1) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
// Give the last star one more character and retry from just after it.
|
|
45
|
+
starTextIndex += 1;
|
|
46
|
+
patternIndex = starPatternIndex;
|
|
47
|
+
textIndex = starTextIndex;
|
|
48
|
+
}
|
|
49
|
+
while (pattern[patternIndex] === '*') {
|
|
50
|
+
patternIndex += 1;
|
|
51
|
+
}
|
|
52
|
+
return patternIndex === pattern.length;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Match a docs-root-relative path against a pattern, segment by segment, where
|
|
56
|
+
* a whole-segment `**` spans zero or more path segments.
|
|
57
|
+
*
|
|
58
|
+
* Same greedy-restart shape as {@link matchSegment}, one level up, so a pattern
|
|
59
|
+
* full of `**` segments stays bounded too.
|
|
60
|
+
*/
|
|
61
|
+
function matchPath(patternSegments, pathSegments) {
|
|
62
|
+
let patternIndex = 0;
|
|
63
|
+
let pathIndex = 0;
|
|
64
|
+
let starPatternIndex = -1;
|
|
65
|
+
let starPathIndex = 0;
|
|
66
|
+
while (pathIndex < pathSegments.length) {
|
|
67
|
+
if (patternSegments[patternIndex] === '**') {
|
|
68
|
+
while (patternSegments[patternIndex] === '**') {
|
|
69
|
+
patternIndex += 1;
|
|
70
|
+
}
|
|
71
|
+
if (patternIndex === patternSegments.length) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
starPatternIndex = patternIndex;
|
|
75
|
+
starPathIndex = pathIndex;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (patternIndex < patternSegments.length &&
|
|
79
|
+
matchSegment(patternSegments[patternIndex], pathSegments[pathIndex])) {
|
|
80
|
+
patternIndex += 1;
|
|
81
|
+
pathIndex += 1;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (starPatternIndex === -1) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
starPathIndex += 1;
|
|
88
|
+
patternIndex = starPatternIndex;
|
|
89
|
+
pathIndex = starPathIndex;
|
|
90
|
+
}
|
|
91
|
+
while (patternSegments[patternIndex] === '**') {
|
|
92
|
+
patternIndex += 1;
|
|
93
|
+
}
|
|
94
|
+
return patternIndex === patternSegments.length;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Compile exclusion globs matched against **docs-root-relative POSIX paths**
|
|
98
|
+
* (`api/auth.md`, `api/nested`), never against absolute or CWD-relative ones.
|
|
99
|
+
*
|
|
100
|
+
* Grammar, defined once and documented in
|
|
101
|
+
* `apps/oat-docs/docs/reference/oat-directory-structure.md`:
|
|
102
|
+
*
|
|
103
|
+
* - `*` matches any run of characters inside a single path segment.
|
|
104
|
+
* - `**` spans `/` only as a whole segment, where it covers zero or more
|
|
105
|
+
* segments: `**\/CLAUDE.md` matches `CLAUDE.md` and `api/CLAUDE.md`. Inside a
|
|
106
|
+
* segment (`a**b`) it is an ordinary single-segment wildcard.
|
|
107
|
+
* - Patterns are anchored at both ends against the whole relative path, so a
|
|
108
|
+
* bare `CLAUDE.md` matches only the root-level file. Use `**\/CLAUDE.md` for
|
|
109
|
+
* every depth.
|
|
110
|
+
* - A trailing `/` restricts a pattern to directories: `drafts/` prunes the
|
|
111
|
+
* `drafts` directory and everything beneath it, and never matches a file.
|
|
112
|
+
* Without it, a pattern that matches a directory path prunes it too.
|
|
113
|
+
* - Matching is case-sensitive, `/` is the separator on every platform, and
|
|
114
|
+
* `*` is the only metacharacter — `.`, `+`, and friends are literal.
|
|
115
|
+
*
|
|
116
|
+
* Entries are trimmed; a leading `./` or `/` is stripped so both spellings
|
|
117
|
+
* anchor at the docs root. Blank entries are ignored.
|
|
118
|
+
*/
|
|
119
|
+
export function createExclusionMatcher(patterns) {
|
|
120
|
+
const filePatterns = [];
|
|
121
|
+
const directoryPatterns = [];
|
|
122
|
+
for (const rawPattern of patterns) {
|
|
123
|
+
const trimmedPattern = rawPattern.trim();
|
|
124
|
+
if (!trimmedPattern) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const directoryOnly = trimmedPattern.endsWith('/');
|
|
128
|
+
const normalized = trimmedPattern
|
|
129
|
+
.replace(/\/+$/, '')
|
|
130
|
+
.replace(/^\.\//, '')
|
|
131
|
+
.replace(/^\/+/, '');
|
|
132
|
+
if (!normalized) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const segments = normalized.split('/');
|
|
136
|
+
directoryPatterns.push(segments);
|
|
137
|
+
if (!directoryOnly) {
|
|
138
|
+
filePatterns.push(segments);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (directoryPatterns.length === 0) {
|
|
142
|
+
return NO_EXCLUSIONS;
|
|
143
|
+
}
|
|
144
|
+
const matches = (compiled, relativePath) => {
|
|
145
|
+
const pathSegments = relativePath.split('/');
|
|
146
|
+
return compiled.some((segments) => matchPath(segments, pathSegments));
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
excludesFile: (relativePath) => matches(filePatterns, relativePath),
|
|
150
|
+
excludesDirectory: (relativePath) => matches(directoryPatterns, relativePath),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
3
153
|
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---/;
|
|
4
154
|
const HEADING_RE = /^#\s+(.+)$/m;
|
|
5
155
|
function fileNameToTitle(fileName) {
|
|
@@ -60,15 +210,25 @@ function prefixEntryPath(entry, prefix) {
|
|
|
60
210
|
children: entry.children?.map((child) => prefixEntryPath(child, prefix)),
|
|
61
211
|
};
|
|
62
212
|
}
|
|
63
|
-
|
|
213
|
+
/** Docs-root-relative POSIX path used for matching, never for reading files. */
|
|
214
|
+
function relativeKey(base, name) {
|
|
215
|
+
return base ? `${base}/${name}` : name;
|
|
216
|
+
}
|
|
217
|
+
async function collectEntries(docsDir, relativeBase, matcher) {
|
|
64
218
|
const entries = [];
|
|
65
219
|
const subdirs = [];
|
|
66
220
|
const dirEntries = await readdir(docsDir, { withFileTypes: true });
|
|
67
221
|
for (const entry of dirEntries) {
|
|
222
|
+
const relativePath = relativeKey(relativeBase, entry.name);
|
|
68
223
|
if (entry.isDirectory()) {
|
|
69
|
-
|
|
224
|
+
if (!matcher.excludesDirectory(relativePath)) {
|
|
225
|
+
subdirs.push(entry.name);
|
|
226
|
+
}
|
|
70
227
|
}
|
|
71
228
|
else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
229
|
+
if (matcher.excludesFile(relativePath)) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
72
232
|
const filePath = join(docsDir, entry.name);
|
|
73
233
|
const content = await readFile(filePath, 'utf8');
|
|
74
234
|
const { title, description } = resolveTitle(content, entry.name);
|
|
@@ -80,7 +240,10 @@ export async function generateIndex(docsDir) {
|
|
|
80
240
|
}
|
|
81
241
|
}
|
|
82
242
|
for (const subdir of subdirs.sort()) {
|
|
83
|
-
const children = await
|
|
243
|
+
const children = await collectEntries(join(docsDir, subdir), relativeKey(relativeBase, subdir), matcher);
|
|
244
|
+
// A directory with no surviving children emits no heading. This already
|
|
245
|
+
// pruned genuinely empty directories; exclusion simply empties more of
|
|
246
|
+
// them, and it composes upward through the recursion.
|
|
84
247
|
if (children.length > 0) {
|
|
85
248
|
entries.push({
|
|
86
249
|
title: fileNameToTitle(subdir),
|
|
@@ -91,6 +254,9 @@ export async function generateIndex(docsDir) {
|
|
|
91
254
|
}
|
|
92
255
|
return sortEntries(entries);
|
|
93
256
|
}
|
|
257
|
+
export async function generateIndex(docsDir, options = {}) {
|
|
258
|
+
return collectEntries(docsDir, '', createExclusionMatcher(options.excludes ?? []));
|
|
259
|
+
}
|
|
94
260
|
function renderEntries(entries, indent) {
|
|
95
261
|
const lines = [];
|
|
96
262
|
const prefix = ' '.repeat(indent);
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
|
|
2
|
+
import { type OatConfig } from '../../../config/oat-config.js';
|
|
2
3
|
import { Command } from 'commander';
|
|
3
4
|
export declare const GENERATED_INDEX_WARNING = "<!-- AUTOGENERATED by `oat docs generate-index`. Do not hand-edit; changes are clobbered on every `predev` / `prebuild`. -->";
|
|
4
5
|
interface IndexGenerateFileDependencies {
|
|
5
|
-
generateIndex: (docsDir: string) => Promise<import('./generator.js').IndexEntry[]>;
|
|
6
|
+
generateIndex: (docsDir: string, options: import('./generator.js').GenerateIndexOptions) => Promise<import('./generator.js').IndexEntry[]>;
|
|
6
7
|
renderIndex: (entries: import('./generator.js').IndexEntry[]) => string;
|
|
7
8
|
writeFile: (path: string, content: string, encoding: BufferEncoding) => Promise<void>;
|
|
8
|
-
readOatConfig: (repoRoot: string) => Promise<
|
|
9
|
-
writeOatConfig: (repoRoot: string, config:
|
|
9
|
+
readOatConfig: (repoRoot: string) => Promise<OatConfig>;
|
|
10
|
+
writeOatConfig: (repoRoot: string, config: OatConfig) => Promise<void>;
|
|
10
11
|
resolveRepoRoot: (cwd: string) => Promise<string>;
|
|
12
|
+
dirExists: (path: string) => Promise<boolean>;
|
|
13
|
+
readFileIfPresent: (path: string) => Promise<string | null>;
|
|
14
|
+
realpath: (path: string) => Promise<string>;
|
|
15
|
+
readLinkIfSymlink: (path: string) => Promise<string | null>;
|
|
11
16
|
}
|
|
12
17
|
interface IndexGenerateDependencies {
|
|
13
18
|
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/index-generate/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/index-generate/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,KAAK,SAAS,EAGf,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAI5C,eAAO,MAAM,uBAAuB,iIAC4F,CAAC;AAuBjI,UAAU,6BAA6B;IACrC,aAAa,EAAE,CACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,aAAa,EAAE,oBAAoB,KAChD,OAAO,CAAC,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC,CAAC;IACjD,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,KAAK,MAAM,CAAC;IACrE,SAAS,EAAE,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,cAAc,KACrB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5D,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC7D;AAED,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,QAAQ,EAAE,6BAA6B,CAAC;CACzC;AAggBD,wBAAgB,8BAA8B,CAC5C,SAAS,GAAE,OAAO,CAAC,yBAAyB,CAAM,GACjD,OAAO,CAgCT"}
|