@tikoci/rosetta 0.8.13 → 0.9.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tikoci/rosetta",
3
- "version": "0.8.13",
3
+ "version": "0.9.1",
4
4
  "description": "RouterOS documentation as SQLite FTS5 — RAG search + command glossary via MCP",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,39 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+
3
+ process.env.DB_PATH = ":memory:";
4
+
5
+ const { githubApiHeaders } = await import("./extract-skills.ts");
6
+
7
+ describe("extract-skills GitHub API headers", () => {
8
+ afterEach(() => {
9
+ delete process.env.GITHUB_TOKEN;
10
+ delete process.env.GH_TOKEN;
11
+ });
12
+
13
+ test("keeps unauthenticated headers when no token is configured", () => {
14
+ delete process.env.GITHUB_TOKEN;
15
+ delete process.env.GH_TOKEN;
16
+
17
+ expect(githubApiHeaders()).toEqual({
18
+ Accept: "application/vnd.github.v3+json",
19
+ });
20
+ });
21
+
22
+ test("uses GITHUB_TOKEN for authenticated API requests", () => {
23
+ process.env.GITHUB_TOKEN = "test-token";
24
+
25
+ expect(githubApiHeaders()).toEqual({
26
+ Accept: "application/vnd.github.v3+json",
27
+ Authorization: "Bearer test-token",
28
+ });
29
+ });
30
+
31
+ test("falls back to GH_TOKEN for local authenticated runs", () => {
32
+ process.env.GH_TOKEN = "gh-token";
33
+
34
+ expect(githubApiHeaders()).toEqual({
35
+ Accept: "application/vnd.github.v3+json",
36
+ Authorization: "Bearer gh-token",
37
+ });
38
+ });
39
+ });
@@ -70,9 +70,16 @@ function countWords(text: string): number {
70
70
 
71
71
  // ── GitHub API fetching ──
72
72
 
73
+ export function githubApiHeaders(): Record<string, string> {
74
+ const headers: Record<string, string> = { Accept: "application/vnd.github.v3+json" };
75
+ const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
76
+ if (token) headers.Authorization = `Bearer ${token}`;
77
+ return headers;
78
+ }
79
+
73
80
  async function getDefaultBranchSha(): Promise<string> {
74
81
  const res = await fetch(`${GITHUB_API_BASE}/commits/HEAD`, {
75
- headers: { Accept: "application/vnd.github.v3+json" },
82
+ headers: githubApiHeaders(),
76
83
  });
77
84
  if (!res.ok) throw new Error(`Failed to get HEAD SHA: HTTP ${res.status}`);
78
85
  const data = (await res.json()) as { sha: string };
@@ -81,7 +88,7 @@ async function getDefaultBranchSha(): Promise<string> {
81
88
 
82
89
  async function listSkillDirs(sha: string): Promise<string[]> {
83
90
  const res = await fetch(`${GITHUB_API_BASE}/contents/?ref=${sha}`, {
84
- headers: { Accept: "application/vnd.github.v3+json" },
91
+ headers: githubApiHeaders(),
85
92
  });
86
93
  if (!res.ok) throw new Error(`Failed to list repo contents: HTTP ${res.status}`);
87
94
  const entries = (await res.json()) as Array<{ name: string; type: string }>;
@@ -102,7 +109,7 @@ async function fetchRawFile(sha: string, path: string): Promise<string | null> {
102
109
 
103
110
  async function listReferences(sha: string, skillName: string): Promise<string[]> {
104
111
  const res = await fetch(`${GITHUB_API_BASE}/contents/${skillName}/references?ref=${sha}`, {
105
- headers: { Accept: "application/vnd.github.v3+json" },
112
+ headers: githubApiHeaders(),
106
113
  });
107
114
  if (!res.ok) {
108
115
  if (res.status === 404) return [];
@@ -387,4 +394,6 @@ async function main() {
387
394
  populateDb(skills, sha);
388
395
  }
389
396
 
390
- await main();
397
+ if (import.meta.main) {
398
+ await main();
399
+ }
@@ -411,7 +411,13 @@ describe("release.yml", () => {
411
411
 
412
412
  test("extracts agent skills in CI", () => {
413
413
  const src = readText(".github/workflows/release.yml");
414
- expect(src).toContain("extract-skills.ts");
414
+ const skillsIdx = mustIndex(src, "Extract agent skills from GitHub");
415
+ const linkIdx = mustIndex(src, "Link commands to pages");
416
+ const skillsBlock = src.slice(skillsIdx, linkIdx);
417
+
418
+ expect(skillsBlock).toContain("extract-skills.ts");
419
+ expect(skillsBlock).toContain("GITHUB_TOKEN");
420
+ expect(skillsBlock).toContain("$" + "{{ github.token }}");
415
421
  });
416
422
 
417
423
  test("runs quality gate before release", () => {
@@ -436,6 +442,26 @@ describe("release.yml", () => {
436
442
  expect(earlyTestIdx).toBeLessThan(downloadIdx);
437
443
  });
438
444
 
445
+ test("preflights npm publish access before release side effects", () => {
446
+ const src = readText(".github/workflows/release.yml");
447
+ const preflightIdx = mustIndex(src, "Verify npm publish access");
448
+ const downloadIdx = mustIndex(src, "Download HTML export");
449
+ const ociIdx = mustIndex(src, "Build and push OCI images");
450
+ const releaseIdx = mustIndex(src, "Create or update GitHub Release");
451
+ const publishIdx = mustIndex(src, "Publish to npm");
452
+ const preflightBlock = src.slice(preflightIdx, downloadIdx);
453
+
454
+ expect(preflightIdx).toBeLessThan(downloadIdx);
455
+ expect(preflightIdx).toBeLessThan(ociIdx);
456
+ expect(preflightIdx).toBeLessThan(releaseIdx);
457
+ expect(preflightIdx).toBeLessThan(publishIdx);
458
+ expect(preflightBlock).toContain("NODE_AUTH_TOKEN");
459
+ expect(preflightBlock).toContain("npm whoami");
460
+ expect(preflightBlock).toContain("npm access list collaborators");
461
+ expect(preflightBlock).toContain("read-write access");
462
+ expect(preflightBlock).toContain("npm view");
463
+ });
464
+
439
465
  test("keeps post-extraction DB-wipe guard after extraction", () => {
440
466
  const src = readText(".github/workflows/release.yml");
441
467
  const linkIdx = mustIndex(src, "Link commands to pages");
@@ -510,6 +536,8 @@ describe("release.yml", () => {
510
536
  const clobberIdx = mustIndex(src, "gh release upload");
511
537
  expect(src.slice(clobberIdx, clobberIdx + 120)).toContain("--clobber");
512
538
  expect(republishBranchIdx).toBeLessThan(clobberIdx);
539
+ expect(src).toContain('elif gh release view "$VERSION"');
540
+ expect(src).toContain("updated before npm publish retry");
513
541
 
514
542
  expect(src).toContain("if: inputs.republish_assets != true");
515
543
  expect(src).toContain("if: inputs.republish_assets == true");
@@ -542,7 +570,7 @@ describe("release.yml", () => {
542
570
 
543
571
  test("publishes to npm", () => {
544
572
  const src = readText(".github/workflows/release.yml");
545
- expect(src).toContain("npm publish");
573
+ expect(src).toContain("npm publish --access public --registry https://registry.npmjs.org/");
546
574
  expect(src).toContain("NPM_TOKEN");
547
575
  });
548
576
 
@@ -566,7 +594,7 @@ describe("release.yml", () => {
566
594
  const validateIdx = src.indexOf("Validate DB has expected content");
567
595
  const buildIdx = src.indexOf("Build release artifacts");
568
596
  const releaseIdx = src.indexOf("gh release create");
569
- const npmIdx = src.indexOf("npm publish");
597
+ const npmIdx = src.indexOf("Publish to npm");
570
598
  expect(validateIdx).toBeGreaterThan(0);
571
599
  expect(validateIdx).toBeLessThan(buildIdx);
572
600
  expect(validateIdx).toBeLessThan(releaseIdx);