@nzpr/kb 0.1.2 → 0.1.4
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/README.md +14 -5
- package/lib/cli.js +28 -7
- package/lib/init-repo-interactive.js +93 -7
- package/lib/repo-init.js +101 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ The public surface of `@nzpr/kb` is the `kb` CLI.
|
|
|
24
24
|
- `kb init-repo`
|
|
25
25
|
Initialize a knowledge base workspace.
|
|
26
26
|
Use `kb init-repo --interactive` for a guided setup flow.
|
|
27
|
+
Use `--layout repo-root` when this repository is itself the KB.
|
|
27
28
|
|
|
28
29
|
- `kb create --title TEXT --text TEXT`
|
|
29
30
|
Propose a new knowledge document or a substantial update.
|
|
@@ -102,6 +103,13 @@ If you want the CLI to walk you through setup and tell you what to do next:
|
|
|
102
103
|
kb init-repo --interactive
|
|
103
104
|
```
|
|
104
105
|
|
|
106
|
+
Layout options:
|
|
107
|
+
|
|
108
|
+
- `--layout repo-root` puts documents in `docs/`
|
|
109
|
+
- `--layout nested-kb` puts documents in `kb/docs/`
|
|
110
|
+
|
|
111
|
+
For a dedicated knowledge repository, prefer `--layout repo-root`.
|
|
112
|
+
|
|
105
113
|
Or bootstrap and configure it in one step:
|
|
106
114
|
|
|
107
115
|
```bash
|
|
@@ -120,12 +128,13 @@ Inside that knowledge repo, the normal flow is:
|
|
|
120
128
|
1. Run `kb init-repo` once to scaffold the repo. If you provide `--repo`, `--database-url`, and `GITHUB_TOKEN`, it also configures the target repo and preflights the database.
|
|
121
129
|
2. Open or update a knowledge proposal issue.
|
|
122
130
|
3. Review and approve it there.
|
|
123
|
-
4. Materialize it into
|
|
124
|
-
5. After merge, that repo's CI runs `kb publish
|
|
131
|
+
4. Materialize it into the configured docs directory.
|
|
132
|
+
5. After merge, that repo's CI runs `kb publish` against that docs directory.
|
|
125
133
|
|
|
126
134
|
`kb init-repo` is safe to rerun. It reports bootstrap status for:
|
|
127
135
|
|
|
128
136
|
- local scaffold creation
|
|
137
|
+
- knowledge document layout selection
|
|
129
138
|
- database preflight and schema initialization
|
|
130
139
|
- GitHub repo labels, variables, and secrets
|
|
131
140
|
|
|
@@ -185,7 +194,7 @@ export KB_DATABASE_URL=postgresql://kb:kb@localhost:5432/kb
|
|
|
185
194
|
export KB_GITHUB_REPO=owner/repo
|
|
186
195
|
export GITHUB_TOKEN=...
|
|
187
196
|
docker compose -f docker-compose.pgvector.yml up -d
|
|
188
|
-
kb publish --docs-root ./
|
|
197
|
+
kb publish --docs-root ./docs
|
|
189
198
|
kb catalog --json
|
|
190
199
|
kb search "deployment rule"
|
|
191
200
|
```
|
|
@@ -215,13 +224,13 @@ The package is published as `@nzpr/kb`.
|
|
|
215
224
|
|
|
216
225
|
## Using From A Knowledge Repo
|
|
217
226
|
|
|
218
|
-
The knowledge-authority repo should install this package in CI and use it to sync
|
|
227
|
+
The knowledge-authority repo should install this package in CI and use it to sync its configured docs directory into the vector database. For a dedicated KB repo with `--layout repo-root`, that directory is `docs/`:
|
|
219
228
|
|
|
220
229
|
```bash
|
|
221
230
|
npm install -g @nzpr/kb
|
|
222
231
|
export KB_GITHUB_REPO=owner/repo
|
|
223
232
|
export GITHUB_TOKEN=...
|
|
224
|
-
kb publish --docs-root ./
|
|
233
|
+
kb publish --docs-root ./docs
|
|
225
234
|
```
|
|
226
235
|
|
|
227
236
|
Or scaffold that repo first:
|
package/lib/cli.js
CHANGED
|
@@ -65,6 +65,7 @@ export async function main(argv) {
|
|
|
65
65
|
? await collectInitRepoInteractiveOptions({ flags })
|
|
66
66
|
: {
|
|
67
67
|
targetDir: flags.dir ?? process.cwd(),
|
|
68
|
+
layout: flags.layout ?? "nested-kb",
|
|
68
69
|
repo: flags.repo ?? resolveGitHubRepository(),
|
|
69
70
|
databaseUrl: flags["database-url"] ?? process.env.KB_DATABASE_URL ?? null,
|
|
70
71
|
embeddingMode: flags["embedding-mode"] ?? process.env.KB_EMBEDDING_MODE ?? null,
|
|
@@ -78,8 +79,9 @@ export async function main(argv) {
|
|
|
78
79
|
};
|
|
79
80
|
const result = await bootstrapKnowledgeRepo({
|
|
80
81
|
targetDir: initRepoOptions.targetDir,
|
|
82
|
+
layout: initRepoOptions.layout,
|
|
81
83
|
repo: initRepoOptions.repo,
|
|
82
|
-
githubToken: process.env.GITHUB_TOKEN ?? null,
|
|
84
|
+
githubToken: initRepoOptions.githubToken ?? process.env.GITHUB_TOKEN ?? null,
|
|
83
85
|
databaseUrl: initRepoOptions.databaseUrl,
|
|
84
86
|
embeddingMode: initRepoOptions.embeddingMode,
|
|
85
87
|
embeddingApiUrl: initRepoOptions.embeddingApiUrl,
|
|
@@ -89,6 +91,7 @@ export async function main(argv) {
|
|
|
89
91
|
repoAutomationToken: initRepoOptions.repoAutomationToken
|
|
90
92
|
});
|
|
91
93
|
console.log(`initialized knowledge repo scaffold in ${result.root}`);
|
|
94
|
+
console.log(`knowledge documents will live in ${result.docsRootRelative}/`);
|
|
92
95
|
for (const relativePath of result.created) {
|
|
93
96
|
console.log(`created ${relativePath}`);
|
|
94
97
|
}
|
|
@@ -246,7 +249,7 @@ function printHelp() {
|
|
|
246
249
|
console.log(`usage: kb <command> [options]
|
|
247
250
|
|
|
248
251
|
commands:
|
|
249
|
-
init-repo [--interactive] [--dir PATH] [--repo OWNER/REPO]
|
|
252
|
+
init-repo [--interactive] [--layout repo-root|nested-kb] [--dir PATH] [--repo OWNER/REPO]
|
|
250
253
|
create --title TEXT --text TEXT [--path RELATIVE_PATH]
|
|
251
254
|
search <query>
|
|
252
255
|
ask <question>
|
|
@@ -271,7 +274,7 @@ search options:
|
|
|
271
274
|
function printCommandHelp(command) {
|
|
272
275
|
const commandHelp = {
|
|
273
276
|
"init-repo":
|
|
274
|
-
"usage: kb init-repo [--interactive] [--dir PATH] [--repo OWNER/REPO] [--database-url URL] [--embedding-mode MODE] [--embedding-api-url URL] [--embedding-model NAME] [--embedding-api-key KEY] [--db-connect-timeout-ms N] [--repo-automation-token TOKEN]\n\nScaffold a knowledge-authority repository, optionally configure its GitHub settings, and preflight the target database. Use --interactive for a guided terminal walkthrough.",
|
|
277
|
+
"usage: kb init-repo [--interactive] [--layout repo-root|nested-kb] [--dir PATH] [--repo OWNER/REPO] [--database-url URL] [--embedding-mode MODE] [--embedding-api-url URL] [--embedding-model NAME] [--embedding-api-key KEY] [--db-connect-timeout-ms N] [--repo-automation-token TOKEN]\n\nScaffold a knowledge-authority repository, optionally configure its GitHub settings, and preflight the target database. Use --interactive for a guided terminal walkthrough.",
|
|
275
278
|
create: `usage: kb create --title TEXT --text TEXT [--path RELATIVE_PATH] [--repo OWNER/REPO]\n\n${githubCreationHelp()}`,
|
|
276
279
|
search: `usage: kb search <query> [options]\n\n${databaseHelp()}\n --limit N`,
|
|
277
280
|
ask: `usage: kb ask <question> [options]\n\n${databaseHelp()}\n --limit N`,
|
|
@@ -327,7 +330,7 @@ async function requirePublishAccess({ repo, token, apiBaseUrl }) {
|
|
|
327
330
|
|
|
328
331
|
function printRepoConfiguration(configuration) {
|
|
329
332
|
console.log("");
|
|
330
|
-
console.log("
|
|
333
|
+
console.log("configuration reference:");
|
|
331
334
|
console.log("");
|
|
332
335
|
console.log("required secrets:");
|
|
333
336
|
for (const entry of configuration.requiredSecrets) {
|
|
@@ -351,7 +354,10 @@ function printRepoConfiguration(configuration) {
|
|
|
351
354
|
|
|
352
355
|
function printInitRepoStatus(result) {
|
|
353
356
|
console.log("");
|
|
354
|
-
console.log("
|
|
357
|
+
console.log("summary:");
|
|
358
|
+
console.log(` docs layout: ${result.docsRootRelative}/`);
|
|
359
|
+
console.log(` files created: ${result.created.length}`);
|
|
360
|
+
console.log(` files kept: ${result.skipped.length}`);
|
|
355
361
|
printInitRepoDatabaseStatus(result.database);
|
|
356
362
|
printInitRepoGitHubStatus(result.github);
|
|
357
363
|
}
|
|
@@ -373,6 +379,11 @@ function printInitRepoDatabaseStatus(database) {
|
|
|
373
379
|
function printInitRepoGitHubStatus(github) {
|
|
374
380
|
if (github.status === "configured") {
|
|
375
381
|
console.log(` github: configured ${github.repo}`);
|
|
382
|
+
if (github.actions) {
|
|
383
|
+
console.log(
|
|
384
|
+
` actions: enabled=${github.actions.enabled} workflow_permissions=${github.actions.defaultWorkflowPermissions} pr_creation=${github.actions.canApprovePullRequestReviews}`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
376
387
|
console.log(` labels: ${github.labels.join(", ")}`);
|
|
377
388
|
if (github.secrets.length) {
|
|
378
389
|
console.log(` secrets: ${github.secrets.join(", ")}`);
|
|
@@ -407,9 +418,9 @@ function printInitRepoNextStep(result) {
|
|
|
407
418
|
|
|
408
419
|
function printInitRepoChecklist(result) {
|
|
409
420
|
console.log("");
|
|
410
|
-
console.log("
|
|
421
|
+
console.log("next steps:");
|
|
411
422
|
if (result.created.length || result.skipped.length) {
|
|
412
|
-
console.log(
|
|
423
|
+
console.log(` 1. commit and push the scaffolded knowledge repo files, including ${result.docsRootRelative}/`);
|
|
413
424
|
}
|
|
414
425
|
if (result.github.status !== "configured") {
|
|
415
426
|
console.log(" 2. make sure the target repo exists and rerun with --repo once GitHub setup is ready");
|
|
@@ -419,4 +430,14 @@ function printInitRepoChecklist(result) {
|
|
|
419
430
|
}
|
|
420
431
|
console.log(" 4. use kb create to open a proposal issue for the first knowledge entry");
|
|
421
432
|
console.log(" 5. review the issue, add kb-approved, merge the generated PR, and let publish sync the live KB");
|
|
433
|
+
console.log("");
|
|
434
|
+
console.log("done in this run:");
|
|
435
|
+
if (result.created.length) {
|
|
436
|
+
console.log(` created: ${result.created.join(", ")}`);
|
|
437
|
+
} else {
|
|
438
|
+
console.log(" created: no new files");
|
|
439
|
+
}
|
|
440
|
+
if (result.skipped.length) {
|
|
441
|
+
console.log(` kept: ${result.skipped.join(", ")}`);
|
|
442
|
+
}
|
|
422
443
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
2
4
|
import readline from "node:readline/promises";
|
|
3
5
|
import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
|
|
4
6
|
import { resolveGitHubRepository } from "./config.js";
|
|
@@ -9,7 +11,8 @@ export async function collectInitRepoInteractiveOptions({
|
|
|
9
11
|
cwd = process.cwd(),
|
|
10
12
|
stdin = defaultStdin,
|
|
11
13
|
stdout = defaultStdout,
|
|
12
|
-
prompter = null
|
|
14
|
+
prompter = null,
|
|
15
|
+
auth = defaultInteractiveGitHubAuth
|
|
13
16
|
}) {
|
|
14
17
|
if (!prompter && (!stdin.isTTY || !stdout.isTTY)) {
|
|
15
18
|
throw new Error("interactive init-repo requires a TTY; rerun in a terminal or pass flags directly");
|
|
@@ -20,8 +23,8 @@ export async function collectInitRepoInteractiveOptions({
|
|
|
20
23
|
try {
|
|
21
24
|
const defaults = buildInitRepoDefaults({ flags, env, cwd });
|
|
22
25
|
|
|
23
|
-
stdout.write("
|
|
24
|
-
stdout.write("
|
|
26
|
+
stdout.write("KB Init Wizard\n");
|
|
27
|
+
stdout.write("This walkthrough prepares the knowledge repo and can wire GitHub and database setup now.\n\n");
|
|
25
28
|
|
|
26
29
|
const targetDir = path.resolve(
|
|
27
30
|
cwd,
|
|
@@ -29,6 +32,7 @@ export async function collectInitRepoInteractiveOptions({
|
|
|
29
32
|
validate: requireValue
|
|
30
33
|
})
|
|
31
34
|
);
|
|
35
|
+
const layout = await askKnowledgeLayout(prompt, targetDir, defaults.layout);
|
|
32
36
|
|
|
33
37
|
const configureRepo = await prompt.askConfirm(
|
|
34
38
|
"configure the GitHub repo now",
|
|
@@ -40,6 +44,15 @@ export async function collectInitRepoInteractiveOptions({
|
|
|
40
44
|
repo = await prompt.askText("target GitHub repo (OWNER/REPO)", defaults.repo, {
|
|
41
45
|
validate: requireValue
|
|
42
46
|
});
|
|
47
|
+
if (!defaults.githubToken) {
|
|
48
|
+
const authorized = await ensureInteractiveGitHubAccess({ prompt, stdout, auth });
|
|
49
|
+
if (authorized) {
|
|
50
|
+
defaults.githubToken = authorized;
|
|
51
|
+
} else {
|
|
52
|
+
stdout.write("skipping GitHub repo setup for now because GitHub auth is not ready.\n");
|
|
53
|
+
repo = null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
43
56
|
}
|
|
44
57
|
|
|
45
58
|
const verifyDatabase = await prompt.askConfirm(
|
|
@@ -93,7 +106,7 @@ export async function collectInitRepoInteractiveOptions({
|
|
|
93
106
|
let repoAutomationToken = null;
|
|
94
107
|
if (configureRepo) {
|
|
95
108
|
const useAutomationToken = await prompt.askConfirm(
|
|
96
|
-
"
|
|
109
|
+
"use a custom automation token instead of the default GitHub Actions token",
|
|
97
110
|
Boolean(defaults.repoAutomationToken)
|
|
98
111
|
);
|
|
99
112
|
if (useAutomationToken) {
|
|
@@ -104,9 +117,10 @@ export async function collectInitRepoInteractiveOptions({
|
|
|
104
117
|
}
|
|
105
118
|
}
|
|
106
119
|
|
|
107
|
-
stdout.write("\
|
|
108
|
-
stdout.write(`
|
|
109
|
-
stdout.write(`
|
|
120
|
+
stdout.write("\nPlan\n");
|
|
121
|
+
stdout.write(` target directory: ${targetDir}\n`);
|
|
122
|
+
stdout.write(` document layout: ${layout === "repo-root" ? "docs/" : "kb/docs/"}\n`);
|
|
123
|
+
stdout.write(` GitHub repo setup: ${repo ? repo : "skip for now"}\n`);
|
|
110
124
|
stdout.write(` database preflight: ${databaseUrl ? "yes" : "skip for now"}\n`);
|
|
111
125
|
stdout.write(
|
|
112
126
|
` embeddings config: ${embeddingMode === "bge-m3-openai" ? `${embeddingMode}/${embeddingModel}` : "skip for now"}\n`
|
|
@@ -119,7 +133,9 @@ export async function collectInitRepoInteractiveOptions({
|
|
|
119
133
|
|
|
120
134
|
return {
|
|
121
135
|
targetDir,
|
|
136
|
+
layout,
|
|
122
137
|
repo,
|
|
138
|
+
githubToken: defaults.githubToken || null,
|
|
123
139
|
databaseUrl,
|
|
124
140
|
embeddingMode,
|
|
125
141
|
embeddingApiUrl,
|
|
@@ -136,6 +152,9 @@ export async function collectInitRepoInteractiveOptions({
|
|
|
136
152
|
export function buildInitRepoDefaults({ flags = {}, env = process.env, cwd = process.cwd() }) {
|
|
137
153
|
return {
|
|
138
154
|
targetDir: flags.dir ?? cwd,
|
|
155
|
+
layout:
|
|
156
|
+
flags.layout ?? inferDefaultKnowledgeLayout(flags.dir ? path.resolve(cwd, flags.dir) : cwd),
|
|
157
|
+
githubToken: env.GITHUB_TOKEN ?? "",
|
|
139
158
|
repo: flags.repo ?? resolveGitHubRepository(env) ?? "",
|
|
140
159
|
databaseUrl: flags["database-url"] ?? env.KB_DATABASE_URL ?? "",
|
|
141
160
|
embeddingMode: flags["embedding-mode"] ?? env.KB_EMBEDDING_MODE ?? "",
|
|
@@ -149,6 +168,31 @@ export function buildInitRepoDefaults({ flags = {}, env = process.env, cwd = pro
|
|
|
149
168
|
};
|
|
150
169
|
}
|
|
151
170
|
|
|
171
|
+
async function askKnowledgeLayout(prompt, targetDir, defaultLayout) {
|
|
172
|
+
const useRepoRoot = await prompt.askConfirm(
|
|
173
|
+
"store documents at repo root docs/ (recommended when this repo is the knowledge base)",
|
|
174
|
+
defaultLayout === "repo-root"
|
|
175
|
+
);
|
|
176
|
+
if (useRepoRoot) {
|
|
177
|
+
return "repo-root";
|
|
178
|
+
}
|
|
179
|
+
if (!fs.existsSync(targetDir) || fs.readdirSync(targetDir).length === 0) {
|
|
180
|
+
return "nested-kb";
|
|
181
|
+
}
|
|
182
|
+
return "nested-kb";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function inferDefaultKnowledgeLayout(targetDir) {
|
|
186
|
+
const root = path.resolve(targetDir);
|
|
187
|
+
if (fs.existsSync(path.join(root, "docs"))) {
|
|
188
|
+
return "repo-root";
|
|
189
|
+
}
|
|
190
|
+
if (fs.existsSync(path.join(root, "kb", "docs"))) {
|
|
191
|
+
return "nested-kb";
|
|
192
|
+
}
|
|
193
|
+
return "repo-root";
|
|
194
|
+
}
|
|
195
|
+
|
|
152
196
|
function createReadlinePrompter({ stdin, stdout }) {
|
|
153
197
|
const rl = readline.createInterface({
|
|
154
198
|
input: stdin,
|
|
@@ -220,3 +264,45 @@ function emptyToNull(value) {
|
|
|
220
264
|
const normalized = String(value ?? "").trim();
|
|
221
265
|
return normalized ? normalized : null;
|
|
222
266
|
}
|
|
267
|
+
|
|
268
|
+
async function ensureInteractiveGitHubAccess({ prompt, stdout, auth }) {
|
|
269
|
+
const existingToken = auth.getToken();
|
|
270
|
+
if (existingToken) {
|
|
271
|
+
stdout.write("using existing GitHub CLI authentication for repo setup.\n");
|
|
272
|
+
return existingToken;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
stdout.write("\nGitHub repo setup needs GitHub authentication.\n");
|
|
276
|
+
stdout.write("The wizard can open GitHub CLI login in your browser and then continue.\n");
|
|
277
|
+
const loginNow = await prompt.askConfirm("authorize GitHub CLI now", true);
|
|
278
|
+
if (!loginNow) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
auth.login();
|
|
283
|
+
const token = auth.getToken();
|
|
284
|
+
if (token) {
|
|
285
|
+
stdout.write("GitHub CLI authentication is ready.\n");
|
|
286
|
+
return token;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
throw new Error("GitHub authentication did not complete; rerun gh auth login or set GITHUB_TOKEN");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const defaultInteractiveGitHubAuth = {
|
|
293
|
+
getToken() {
|
|
294
|
+
try {
|
|
295
|
+
return execFileSync("gh", ["auth", "token"], {
|
|
296
|
+
encoding: "utf8",
|
|
297
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
298
|
+
}).trim();
|
|
299
|
+
} catch {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
login() {
|
|
304
|
+
execFileSync("gh", ["auth", "login", "--web", "--git-protocol", "https"], {
|
|
305
|
+
stdio: "inherit"
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
};
|
package/lib/repo-init.js
CHANGED
|
@@ -5,6 +5,8 @@ import { connect, initDb } from "./db.js";
|
|
|
5
5
|
import { maskConnection } from "./cli-common.js";
|
|
6
6
|
|
|
7
7
|
const PACKAGE_NAME = "@nzpr/kb";
|
|
8
|
+
const REPO_ROOT_LAYOUT = "repo-root";
|
|
9
|
+
const NESTED_KB_LAYOUT = "nested-kb";
|
|
8
10
|
const LABELS = Object.freeze([
|
|
9
11
|
{
|
|
10
12
|
name: "kb-entry",
|
|
@@ -18,14 +20,19 @@ const LABELS = Object.freeze([
|
|
|
18
20
|
}
|
|
19
21
|
]);
|
|
20
22
|
|
|
21
|
-
export function initializeKnowledgeRepo({
|
|
23
|
+
export function initializeKnowledgeRepo({
|
|
24
|
+
targetDir = process.cwd(),
|
|
25
|
+
layout = NESTED_KB_LAYOUT
|
|
26
|
+
} = {}) {
|
|
22
27
|
const root = path.resolve(targetDir);
|
|
28
|
+
const normalizedLayout = normalizeKnowledgeLayout(layout);
|
|
29
|
+
const docsRootRelative = docsRootForLayout(normalizedLayout);
|
|
23
30
|
const files = new Map([
|
|
24
31
|
[".github/ISSUE_TEMPLATE/config.yml", renderIssueConfig()],
|
|
25
|
-
[".github/ISSUE_TEMPLATE/knowledge-document.md", renderIssueTemplate()],
|
|
26
|
-
[".github/workflows/kb-issue-to-pr.yml", renderIssueToPrWorkflow()],
|
|
27
|
-
[".github/workflows/kb-publish.yml", renderPublishWorkflow()],
|
|
28
|
-
[
|
|
32
|
+
[".github/ISSUE_TEMPLATE/knowledge-document.md", renderIssueTemplate({ docsRootRelative })],
|
|
33
|
+
[".github/workflows/kb-issue-to-pr.yml", renderIssueToPrWorkflow({ docsRootRelative })],
|
|
34
|
+
[".github/workflows/kb-publish.yml", renderPublishWorkflow({ docsRootRelative })],
|
|
35
|
+
[`${docsRootRelative}/.gitkeep`, ""]
|
|
29
36
|
]);
|
|
30
37
|
|
|
31
38
|
const created = [];
|
|
@@ -44,6 +51,8 @@ export function initializeKnowledgeRepo({ targetDir = process.cwd() } = {}) {
|
|
|
44
51
|
|
|
45
52
|
return {
|
|
46
53
|
root,
|
|
54
|
+
layout: normalizedLayout,
|
|
55
|
+
docsRootRelative,
|
|
47
56
|
created,
|
|
48
57
|
skipped,
|
|
49
58
|
configuration: buildConfigurationGuide()
|
|
@@ -52,6 +61,7 @@ export function initializeKnowledgeRepo({ targetDir = process.cwd() } = {}) {
|
|
|
52
61
|
|
|
53
62
|
export async function bootstrapKnowledgeRepo({
|
|
54
63
|
targetDir = process.cwd(),
|
|
64
|
+
layout = NESTED_KB_LAYOUT,
|
|
55
65
|
repo = null,
|
|
56
66
|
githubToken = null,
|
|
57
67
|
databaseUrl = null,
|
|
@@ -64,7 +74,7 @@ export async function bootstrapKnowledgeRepo({
|
|
|
64
74
|
runGitHubCommand = defaultRunGitHubCommand,
|
|
65
75
|
verifyDatabaseReady = defaultVerifyDatabaseReady
|
|
66
76
|
} = {}) {
|
|
67
|
-
const scaffold = initializeKnowledgeRepo({ targetDir });
|
|
77
|
+
const scaffold = initializeKnowledgeRepo({ targetDir, layout });
|
|
68
78
|
const result = {
|
|
69
79
|
...scaffold,
|
|
70
80
|
ok: true,
|
|
@@ -102,7 +112,8 @@ export async function bootstrapKnowledgeRepo({
|
|
|
102
112
|
result.github = {
|
|
103
113
|
status: "failed",
|
|
104
114
|
repo,
|
|
105
|
-
error:
|
|
115
|
+
error:
|
|
116
|
+
"GITHUB_TOKEN is required when configuring a knowledge repo; set GITHUB_TOKEN or run gh auth login before rerunning"
|
|
106
117
|
};
|
|
107
118
|
return result;
|
|
108
119
|
}
|
|
@@ -216,6 +227,11 @@ async function configureKnowledgeRepo({
|
|
|
216
227
|
runGitHubCommand
|
|
217
228
|
}) {
|
|
218
229
|
await runGitHubCommand(["repo", "edit", repo, "--enable-issues"], { githubToken });
|
|
230
|
+
const actions = await ensureGitHubActionsPermissions({
|
|
231
|
+
repo,
|
|
232
|
+
githubToken,
|
|
233
|
+
runGitHubCommand
|
|
234
|
+
});
|
|
219
235
|
|
|
220
236
|
for (const label of LABELS) {
|
|
221
237
|
await runGitHubCommand(
|
|
@@ -249,12 +265,72 @@ async function configureKnowledgeRepo({
|
|
|
249
265
|
|
|
250
266
|
return {
|
|
251
267
|
repo,
|
|
268
|
+
actions,
|
|
252
269
|
labels: LABELS.map((label) => label.name),
|
|
253
270
|
secrets: [...secrets.keys()],
|
|
254
271
|
variables: [...variables.keys()]
|
|
255
272
|
};
|
|
256
273
|
}
|
|
257
274
|
|
|
275
|
+
async function ensureGitHubActionsPermissions({ repo, githubToken, runGitHubCommand }) {
|
|
276
|
+
const actionsPermissions = await runGitHubJson(
|
|
277
|
+
["api", `repos/${repo}/actions/permissions`],
|
|
278
|
+
{ githubToken, runGitHubCommand }
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
if (actionsPermissions.enabled === false) {
|
|
282
|
+
await runGitHubCommand(
|
|
283
|
+
[
|
|
284
|
+
"api",
|
|
285
|
+
"--method",
|
|
286
|
+
"PUT",
|
|
287
|
+
`repos/${repo}/actions/permissions`,
|
|
288
|
+
"-F",
|
|
289
|
+
"enabled=true",
|
|
290
|
+
"-f",
|
|
291
|
+
`allowed_actions=${actionsPermissions.allowed_actions ?? "all"}`
|
|
292
|
+
],
|
|
293
|
+
{ githubToken }
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const workflowPermissions = await runGitHubJson(
|
|
298
|
+
["api", `repos/${repo}/actions/permissions/workflow`],
|
|
299
|
+
{ githubToken, runGitHubCommand }
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
if (
|
|
303
|
+
workflowPermissions.default_workflow_permissions !== "write" ||
|
|
304
|
+
workflowPermissions.can_approve_pull_request_reviews !== true
|
|
305
|
+
) {
|
|
306
|
+
await runGitHubCommand(
|
|
307
|
+
[
|
|
308
|
+
"api",
|
|
309
|
+
"--method",
|
|
310
|
+
"PUT",
|
|
311
|
+
`repos/${repo}/actions/permissions/workflow`,
|
|
312
|
+
"-f",
|
|
313
|
+
"default_workflow_permissions=write",
|
|
314
|
+
"-F",
|
|
315
|
+
"can_approve_pull_request_reviews=true"
|
|
316
|
+
],
|
|
317
|
+
{ githubToken }
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
enabled: true,
|
|
323
|
+
allowedActions: actionsPermissions.allowed_actions ?? "all",
|
|
324
|
+
defaultWorkflowPermissions: "write",
|
|
325
|
+
canApprovePullRequestReviews: true
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function runGitHubJson(args, { githubToken, runGitHubCommand }) {
|
|
330
|
+
const output = await runGitHubCommand(args, { githubToken });
|
|
331
|
+
return JSON.parse(output);
|
|
332
|
+
}
|
|
333
|
+
|
|
258
334
|
async function defaultVerifyDatabaseReady({ databaseUrl }) {
|
|
259
335
|
const client = await connect(databaseUrl);
|
|
260
336
|
try {
|
|
@@ -284,7 +360,7 @@ function renderIssueConfig() {
|
|
|
284
360
|
return ["blank_issues_enabled: false", ""].join("\n");
|
|
285
361
|
}
|
|
286
362
|
|
|
287
|
-
function renderIssueTemplate() {
|
|
363
|
+
function renderIssueTemplate({ docsRootRelative }) {
|
|
288
364
|
return [
|
|
289
365
|
"---",
|
|
290
366
|
"name: Knowledge Base Document",
|
|
@@ -313,12 +389,12 @@ function renderIssueTemplate() {
|
|
|
313
389
|
"",
|
|
314
390
|
"1. Open the issue with this template.",
|
|
315
391
|
"2. Review and edit the issue until the title and text are ready.",
|
|
316
|
-
|
|
392
|
+
`3. Add the \`kb-approved\` label to generate a PR that writes the Markdown file into \`${docsRootRelative}/\`.`,
|
|
317
393
|
""
|
|
318
394
|
].join("\n");
|
|
319
395
|
}
|
|
320
396
|
|
|
321
|
-
function renderIssueToPrWorkflow() {
|
|
397
|
+
function renderIssueToPrWorkflow({ docsRootRelative }) {
|
|
322
398
|
return [
|
|
323
399
|
"name: kb-issue-to-pr",
|
|
324
400
|
"",
|
|
@@ -353,7 +429,7 @@ function renderIssueToPrWorkflow() {
|
|
|
353
429
|
"",
|
|
354
430
|
" - name: Materialize approved issue",
|
|
355
431
|
" id: materialize",
|
|
356
|
-
|
|
432
|
+
` run: kb-admin issue-to-doc --issue-event "$GITHUB_EVENT_PATH" --docs-root ./${docsRootRelative}`,
|
|
357
433
|
"",
|
|
358
434
|
" - name: Create pull request",
|
|
359
435
|
" id: cpr",
|
|
@@ -382,7 +458,7 @@ function renderIssueToPrWorkflow() {
|
|
|
382
458
|
].join("\n");
|
|
383
459
|
}
|
|
384
460
|
|
|
385
|
-
function renderPublishWorkflow() {
|
|
461
|
+
function renderPublishWorkflow({ docsRootRelative }) {
|
|
386
462
|
return [
|
|
387
463
|
"name: kb-publish",
|
|
388
464
|
"",
|
|
@@ -391,7 +467,7 @@ function renderPublishWorkflow() {
|
|
|
391
467
|
" branches:",
|
|
392
468
|
" - main",
|
|
393
469
|
" paths:",
|
|
394
|
-
|
|
470
|
+
` - "${docsRootRelative}/**"`,
|
|
395
471
|
' - ".github/workflows/**"',
|
|
396
472
|
" workflow_dispatch:",
|
|
397
473
|
"",
|
|
@@ -432,7 +508,18 @@ function renderPublishWorkflow() {
|
|
|
432
508
|
` run: npm install -g ${PACKAGE_NAME}`,
|
|
433
509
|
"",
|
|
434
510
|
" - name: Publish knowledge",
|
|
435
|
-
|
|
511
|
+
` run: kb publish --docs-root ./${docsRootRelative}`,
|
|
436
512
|
""
|
|
437
513
|
].join("\n");
|
|
438
514
|
}
|
|
515
|
+
|
|
516
|
+
function normalizeKnowledgeLayout(layout) {
|
|
517
|
+
if (layout === REPO_ROOT_LAYOUT || layout === NESTED_KB_LAYOUT) {
|
|
518
|
+
return layout;
|
|
519
|
+
}
|
|
520
|
+
throw new Error(`unsupported init-repo layout: ${layout}`);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function docsRootForLayout(layout) {
|
|
524
|
+
return layout === REPO_ROOT_LAYOUT ? "docs" : "kb/docs";
|
|
525
|
+
}
|