@nzpr/kb 0.1.8 → 0.1.10
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 +4 -1
- package/lib/cli.js +63 -25
- package/lib/init-repo-interactive.js +1 -1
- package/lib/repo-init.js +50 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -176,15 +176,18 @@ export KB_GITHUB_REPO=owner/repo
|
|
|
176
176
|
export GITHUB_TOKEN=...
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
-
Publishing is allowed when the provided `GITHUB_TOKEN`
|
|
179
|
+
Publishing is allowed when the provided `GITHUB_TOKEN` can read `KB_GITHUB_REPO`. Normal readers do not need GitHub credentials.
|
|
180
180
|
|
|
181
181
|
When `kb init-repo` is given `--repo`, it uses the same token to:
|
|
182
182
|
|
|
183
183
|
- enable issues in the target repo
|
|
184
|
+
- set GitHub Actions workflow permissions to write and allow Actions to create PRs
|
|
184
185
|
- create or update the `kb-entry` and `kb-approved` labels
|
|
185
186
|
- write repository secrets and variables
|
|
186
187
|
- verify and initialize the target database schema if `--database-url` is provided
|
|
187
188
|
|
|
189
|
+
For the GitHub setup step, that token must have repository admin access. A plain write token is not enough for Actions settings and repo secrets.
|
|
190
|
+
|
|
188
191
|
If you run `kb init-repo` without the repo or database inputs, it still scaffolds the knowledge repo and prints exactly which remote bootstrap inputs are still pending.
|
|
189
192
|
|
|
190
193
|
## Quick Start
|
package/lib/cli.js
CHANGED
|
@@ -322,10 +322,10 @@ async function requirePublishAccess({ repo, token, apiBaseUrl }) {
|
|
|
322
322
|
if (!permissions) {
|
|
323
323
|
return;
|
|
324
324
|
}
|
|
325
|
-
if (permissions.admin || permissions.maintain || permissions.push) {
|
|
325
|
+
if (permissions.admin || permissions.maintain || permissions.push || permissions.triage || permissions.pull) {
|
|
326
326
|
return;
|
|
327
327
|
}
|
|
328
|
-
throw new Error(`GITHUB_TOKEN does not have
|
|
328
|
+
throw new Error(`GITHUB_TOKEN does not have repository access to ${repo}`);
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
function printRepoConfiguration(configuration) {
|
|
@@ -349,19 +349,31 @@ function printRepoConfiguration(configuration) {
|
|
|
349
349
|
console.log("");
|
|
350
350
|
console.log("publish workflow auth:");
|
|
351
351
|
console.log(" KB_GITHUB_REPO is set automatically to github.repository in the scaffolded workflow");
|
|
352
|
-
console.log(" GITHUB_TOKEN is provided automatically by GitHub Actions and
|
|
352
|
+
console.log(" GITHUB_TOKEN is provided automatically by GitHub Actions and only needs repository read access for publish");
|
|
353
|
+
console.log(" the token used for kb init-repo itself must have admin access if you want the CLI to configure repo Actions settings")
|
|
353
354
|
}
|
|
354
355
|
|
|
355
356
|
function printInitRepoStatus(result) {
|
|
356
357
|
console.log("");
|
|
357
|
-
console.log("
|
|
358
|
-
|
|
359
|
-
console.log(` files created: ${result.created.length}`);
|
|
360
|
-
console.log(` files kept: ${result.skipped.length}`);
|
|
358
|
+
console.log("what happened:");
|
|
359
|
+
printInitRepoScaffoldStatus(result);
|
|
361
360
|
printInitRepoDatabaseStatus(result.database);
|
|
362
361
|
printInitRepoGitHubStatus(result.github);
|
|
363
362
|
}
|
|
364
363
|
|
|
364
|
+
function printInitRepoScaffoldStatus(result) {
|
|
365
|
+
const reusedOnly = !result.created.length && result.skipped.length > 0;
|
|
366
|
+
if (reusedOnly) {
|
|
367
|
+
console.log(` scaffold: reused the existing knowledge repo files in ${result.root}`);
|
|
368
|
+
} else if (result.created.length) {
|
|
369
|
+
console.log(` scaffold: created or updated the knowledge repo scaffold in ${result.root}`);
|
|
370
|
+
} else {
|
|
371
|
+
console.log(` scaffold: nothing changed in ${result.root}`);
|
|
372
|
+
}
|
|
373
|
+
console.log(` docs: documents live in ${result.docsRootRelative}/`);
|
|
374
|
+
console.log(` files: created=${result.created.length} kept=${result.skipped.length}`);
|
|
375
|
+
}
|
|
376
|
+
|
|
365
377
|
function printInitRepoDatabaseStatus(database) {
|
|
366
378
|
if (database.status === "verified") {
|
|
367
379
|
console.log(
|
|
@@ -370,10 +382,16 @@ function printInitRepoDatabaseStatus(database) {
|
|
|
370
382
|
return;
|
|
371
383
|
}
|
|
372
384
|
if (database.status === "failed") {
|
|
373
|
-
|
|
385
|
+
const target = database.target ? ` ${database.target}` : "";
|
|
386
|
+
console.log(` database: attempted verification${target}`);
|
|
387
|
+
console.log(` database result: failed - ${formatCliError(new Error(database.error))}`);
|
|
388
|
+
console.log(" database effect: no schema changes were confirmed");
|
|
374
389
|
return;
|
|
375
390
|
}
|
|
376
|
-
|
|
391
|
+
if (database.target) {
|
|
392
|
+
console.log(` database: pending verification for ${database.target}`);
|
|
393
|
+
}
|
|
394
|
+
console.log(` database result: pending - ${database.message}`);
|
|
377
395
|
}
|
|
378
396
|
|
|
379
397
|
function printInitRepoGitHubStatus(github) {
|
|
@@ -381,36 +399,52 @@ function printInitRepoGitHubStatus(github) {
|
|
|
381
399
|
console.log(` github: configured ${github.repo}`);
|
|
382
400
|
if (github.actions) {
|
|
383
401
|
console.log(
|
|
384
|
-
` actions: enabled=${github.actions.enabled} workflow_permissions=${github.actions.defaultWorkflowPermissions} pr_creation=${github.actions.canApprovePullRequestReviews}`
|
|
402
|
+
` github actions: enabled=${github.actions.enabled} workflow_permissions=${github.actions.defaultWorkflowPermissions} pr_creation=${github.actions.canApprovePullRequestReviews}`
|
|
385
403
|
);
|
|
386
404
|
}
|
|
387
|
-
console.log(` labels: ${github.labels.join(", ")}`);
|
|
405
|
+
console.log(` github labels: ${github.labels.join(", ")}`);
|
|
388
406
|
if (github.secrets.length) {
|
|
389
|
-
console.log(` secrets: ${github.secrets.join(", ")}`);
|
|
407
|
+
console.log(` github secrets: ${github.secrets.join(", ")}`);
|
|
390
408
|
}
|
|
391
409
|
if (github.variables.length) {
|
|
392
|
-
console.log(` variables: ${github.variables.join(", ")}`);
|
|
410
|
+
console.log(` github variables: ${github.variables.join(", ")}`);
|
|
393
411
|
}
|
|
394
412
|
return;
|
|
395
413
|
}
|
|
396
414
|
if (github.status === "failed") {
|
|
397
|
-
|
|
415
|
+
const repo = github.repo ? ` ${github.repo}` : "";
|
|
416
|
+
console.log(` github: attempted repo bootstrap${repo}`);
|
|
417
|
+
console.log(` github result: failed - ${github.error}`);
|
|
398
418
|
return;
|
|
399
419
|
}
|
|
400
|
-
|
|
420
|
+
if (github.repo) {
|
|
421
|
+
console.log(` github: pending bootstrap for ${github.repo}`);
|
|
422
|
+
}
|
|
423
|
+
console.log(` github result: ${github.status} - ${github.message}`);
|
|
401
424
|
}
|
|
402
425
|
|
|
403
426
|
function printInitRepoNextStep(result) {
|
|
427
|
+
const failures = [];
|
|
428
|
+
if (result.database.status === "failed") {
|
|
429
|
+
failures.push("database verification failed");
|
|
430
|
+
}
|
|
431
|
+
if (result.github.status === "failed") {
|
|
432
|
+
failures.push("GitHub bootstrap failed");
|
|
433
|
+
}
|
|
434
|
+
|
|
404
435
|
if (!result.ok) {
|
|
436
|
+
console.log("");
|
|
405
437
|
console.log(
|
|
406
|
-
|
|
438
|
+
`next: fix ${failures.join(" and ")} and rerun kb init-repo; rerunning is safe because the scaffold is idempotent`
|
|
407
439
|
);
|
|
408
440
|
return;
|
|
409
441
|
}
|
|
410
442
|
if (result.database.status === "verified" && result.github.status === "configured") {
|
|
443
|
+
console.log("");
|
|
411
444
|
console.log("next: commit the scaffold in the knowledge repo, push it, and let that repo own KB publishing");
|
|
412
445
|
return;
|
|
413
446
|
}
|
|
447
|
+
console.log("");
|
|
414
448
|
console.log(
|
|
415
449
|
"next: rerun kb init-repo with the missing repo or database inputs when you are ready, or commit the scaffold now and finish remote setup later"
|
|
416
450
|
);
|
|
@@ -419,25 +453,29 @@ function printInitRepoNextStep(result) {
|
|
|
419
453
|
function printInitRepoChecklist(result) {
|
|
420
454
|
console.log("");
|
|
421
455
|
console.log("next steps:");
|
|
456
|
+
const steps = [];
|
|
422
457
|
if (result.created.length || result.skipped.length) {
|
|
423
|
-
|
|
458
|
+
steps.push(`commit and push the scaffolded knowledge repo files, including ${result.docsRootRelative}/`);
|
|
424
459
|
}
|
|
425
460
|
if (result.github.status !== "configured") {
|
|
426
|
-
|
|
461
|
+
steps.push("make sure the target repo exists and rerun with --repo once GitHub setup is ready");
|
|
427
462
|
}
|
|
428
463
|
if (result.database.status !== "verified") {
|
|
429
|
-
|
|
464
|
+
steps.push("rerun with --database-url once the target database is reachable");
|
|
430
465
|
}
|
|
431
|
-
|
|
432
|
-
|
|
466
|
+
steps.push("use kb create to open a proposal issue for the first knowledge entry");
|
|
467
|
+
steps.push("review the issue, add kb-approved, merge the generated PR, and let publish sync the live KB");
|
|
468
|
+
steps.forEach((step, index) => {
|
|
469
|
+
console.log(` ${index + 1}. ${step}`);
|
|
470
|
+
});
|
|
433
471
|
console.log("");
|
|
434
|
-
console.log("
|
|
472
|
+
console.log("changes in this run:");
|
|
435
473
|
if (result.created.length) {
|
|
436
|
-
console.log(` created: ${result.created.join(", ")}`);
|
|
474
|
+
console.log(` created files: ${result.created.join(", ")}`);
|
|
437
475
|
} else {
|
|
438
|
-
console.log(" created:
|
|
476
|
+
console.log(" created files: none");
|
|
439
477
|
}
|
|
440
478
|
if (result.skipped.length) {
|
|
441
|
-
console.log(` kept: ${result.skipped.join(", ")}`);
|
|
479
|
+
console.log(` kept existing files: ${result.skipped.join(", ")}`);
|
|
442
480
|
}
|
|
443
481
|
}
|
|
@@ -126,7 +126,7 @@ export async function collectInitRepoInteractiveOptions({
|
|
|
126
126
|
` embeddings config: ${embeddingMode === "bge-m3-openai" ? `${embeddingMode}/${embeddingModel}` : "skip for now"}\n`
|
|
127
127
|
);
|
|
128
128
|
|
|
129
|
-
const proceed = await prompt.askConfirm("
|
|
129
|
+
const proceed = await prompt.askConfirm("proceed with initialization", true);
|
|
130
130
|
if (!proceed) {
|
|
131
131
|
throw new Error("interactive init-repo cancelled");
|
|
132
132
|
}
|
package/lib/repo-init.js
CHANGED
|
@@ -79,11 +79,13 @@ export async function bootstrapKnowledgeRepo({
|
|
|
79
79
|
...scaffold,
|
|
80
80
|
ok: true,
|
|
81
81
|
database: {
|
|
82
|
+
target: databaseUrl ? maskConnection(databaseUrl) : null,
|
|
82
83
|
status: "pending",
|
|
83
84
|
message:
|
|
84
85
|
"rerun with --database-url URL or KB_DATABASE_URL to verify the target database and initialize the schema"
|
|
85
86
|
},
|
|
86
87
|
github: {
|
|
88
|
+
repo,
|
|
87
89
|
status: "pending",
|
|
88
90
|
message:
|
|
89
91
|
"rerun with --repo OWNER/REPO and GITHUB_TOKEN to configure labels, repo settings, and GitHub secrets or variables"
|
|
@@ -94,12 +96,14 @@ export async function bootstrapKnowledgeRepo({
|
|
|
94
96
|
try {
|
|
95
97
|
const database = await verifyDatabaseReady({ databaseUrl });
|
|
96
98
|
result.database = {
|
|
99
|
+
target: maskConnection(databaseUrl),
|
|
97
100
|
status: "verified",
|
|
98
101
|
...database
|
|
99
102
|
};
|
|
100
103
|
} catch (error) {
|
|
101
104
|
result.ok = false;
|
|
102
105
|
result.database = {
|
|
106
|
+
target: maskConnection(databaseUrl),
|
|
103
107
|
status: "failed",
|
|
104
108
|
error: String(error?.message ?? error)
|
|
105
109
|
};
|
|
@@ -117,15 +121,6 @@ export async function bootstrapKnowledgeRepo({
|
|
|
117
121
|
};
|
|
118
122
|
return result;
|
|
119
123
|
}
|
|
120
|
-
|
|
121
|
-
if (result.database.status === "failed") {
|
|
122
|
-
result.github = {
|
|
123
|
-
status: "skipped",
|
|
124
|
-
repo,
|
|
125
|
-
message: "database preflight failed, so repo secrets and variables were not changed"
|
|
126
|
-
};
|
|
127
|
-
return result;
|
|
128
|
-
}
|
|
129
124
|
const secrets = new Map();
|
|
130
125
|
const variables = new Map();
|
|
131
126
|
|
|
@@ -226,6 +221,19 @@ async function configureKnowledgeRepo({
|
|
|
226
221
|
variables,
|
|
227
222
|
runGitHubCommand
|
|
228
223
|
}) {
|
|
224
|
+
const repository = await fetchGitHubRepositoryMetadata({
|
|
225
|
+
repo,
|
|
226
|
+
githubToken,
|
|
227
|
+
runGitHubCommand
|
|
228
|
+
});
|
|
229
|
+
const permissions = repository.permissions ?? null;
|
|
230
|
+
|
|
231
|
+
if (!permissions?.admin) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`GITHUB_TOKEN must have admin access to ${repo} so init-repo can configure Actions workflow permissions, PR creation, labels, and repo secrets`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
229
237
|
await runGitHubCommand(["repo", "edit", repo, "--enable-issues"], { githubToken });
|
|
230
238
|
const actions = await ensureGitHubActionsPermissions({
|
|
231
239
|
repo,
|
|
@@ -275,7 +283,11 @@ async function configureKnowledgeRepo({
|
|
|
275
283
|
async function ensureGitHubActionsPermissions({ repo, githubToken, runGitHubCommand }) {
|
|
276
284
|
const actionsPermissions = await runGitHubJson(
|
|
277
285
|
["api", `repos/${repo}/actions/permissions`],
|
|
278
|
-
{
|
|
286
|
+
{
|
|
287
|
+
githubToken,
|
|
288
|
+
runGitHubCommand,
|
|
289
|
+
notFoundMessage: buildActionsAdminError(repo)
|
|
290
|
+
}
|
|
279
291
|
);
|
|
280
292
|
|
|
281
293
|
if (actionsPermissions.enabled === false) {
|
|
@@ -296,7 +308,11 @@ async function ensureGitHubActionsPermissions({ repo, githubToken, runGitHubComm
|
|
|
296
308
|
|
|
297
309
|
const workflowPermissions = await runGitHubJson(
|
|
298
310
|
["api", `repos/${repo}/actions/permissions/workflow`],
|
|
299
|
-
{
|
|
311
|
+
{
|
|
312
|
+
githubToken,
|
|
313
|
+
runGitHubCommand,
|
|
314
|
+
notFoundMessage: buildActionsAdminError(repo)
|
|
315
|
+
}
|
|
300
316
|
);
|
|
301
317
|
|
|
302
318
|
if (
|
|
@@ -326,9 +342,28 @@ async function ensureGitHubActionsPermissions({ repo, githubToken, runGitHubComm
|
|
|
326
342
|
};
|
|
327
343
|
}
|
|
328
344
|
|
|
329
|
-
async function
|
|
330
|
-
|
|
331
|
-
|
|
345
|
+
async function fetchGitHubRepositoryMetadata({ repo, githubToken, runGitHubCommand }) {
|
|
346
|
+
return runGitHubJson(["api", `repos/${repo}`], {
|
|
347
|
+
githubToken,
|
|
348
|
+
runGitHubCommand
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function buildActionsAdminError(repo) {
|
|
353
|
+
return `GitHub token could not read or update Actions settings for ${repo}; use a token with repository admin access and rerun init-repo`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function runGitHubJson(args, { githubToken, runGitHubCommand, notFoundMessage = null }) {
|
|
357
|
+
try {
|
|
358
|
+
const output = await runGitHubCommand(args, { githubToken });
|
|
359
|
+
return JSON.parse(output);
|
|
360
|
+
} catch (error) {
|
|
361
|
+
const message = String(error?.message ?? error);
|
|
362
|
+
if (notFoundMessage && /404|Not Found/i.test(message)) {
|
|
363
|
+
throw new Error(notFoundMessage);
|
|
364
|
+
}
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
332
367
|
}
|
|
333
368
|
|
|
334
369
|
async function defaultVerifyDatabaseReady({ databaseUrl }) {
|
|
@@ -475,7 +510,7 @@ function renderPublishWorkflow({ docsRootRelative }) {
|
|
|
475
510
|
" publish:",
|
|
476
511
|
" runs-on: ubuntu-latest",
|
|
477
512
|
" permissions:",
|
|
478
|
-
" contents:
|
|
513
|
+
" contents: read",
|
|
479
514
|
" concurrency:",
|
|
480
515
|
" group: kb-publish",
|
|
481
516
|
" cancel-in-progress: false",
|