code-provenance 0.1.0
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 +99 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +72 -0
- package/dist/cli.js.map +1 -0
- package/dist/composeParser.d.ts +9 -0
- package/dist/composeParser.js +72 -0
- package/dist/composeParser.js.map +1 -0
- package/dist/composeParser.test.d.ts +1 -0
- package/dist/composeParser.test.js +65 -0
- package/dist/composeParser.test.js.map +1 -0
- package/dist/github.d.ts +37 -0
- package/dist/github.js +270 -0
- package/dist/github.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +9 -0
- package/dist/output.js +39 -0
- package/dist/output.js.map +1 -0
- package/dist/registry.d.ts +9 -0
- package/dist/registry.js +123 -0
- package/dist/registry.js.map +1 -0
- package/dist/resolver.d.ts +5 -0
- package/dist/resolver.js +165 -0
- package/dist/resolver.js.map +1 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +37 -0
- package/run.sh +25 -0
- package/src/cli.ts +87 -0
- package/src/composeParser.test.ts +76 -0
- package/src/composeParser.ts +79 -0
- package/src/github.ts +339 -0
- package/src/index.ts +4 -0
- package/src/output.ts +57 -0
- package/src/registry.ts +153 -0
- package/src/resolver.ts +192 -0
- package/src/types.ts +21 -0
- package/tsconfig.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# code-provenance
|
|
2
|
+
|
|
3
|
+
Resolve Docker images in a docker-compose file to their exact source code commits on GitHub.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install code-provenance
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 20+.
|
|
12
|
+
|
|
13
|
+
## CLI Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx code-provenance [compose-file] [--json] [--verbose]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `compose-file` -- path to a docker-compose file (default: `docker-compose.yml`)
|
|
20
|
+
- `--json` -- output results as JSON
|
|
21
|
+
- `--verbose`, `-v` -- show resolution steps for each image
|
|
22
|
+
|
|
23
|
+
### Example
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx code-provenance docker-compose.yml
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
┌─────────┬────────────────┬────────────────────────────┬──────────────┬──────────┬────────────┐
|
|
31
|
+
│ SERVICE │ IMAGE │ REPO │ COMMIT │ STATUS │ CONFIDENCE │
|
|
32
|
+
├─────────┼────────────────┼────────────────────────────┼──────────────┼──────────┼────────────┤
|
|
33
|
+
│ web │ traefik:v3.6.0 │ github.com/traefik/traefik │ 06db5168c0d9 │ resolved │ exact │
|
|
34
|
+
└─────────┴────────────────┴────────────────────────────┴──────────────┴──────────┴────────────┘
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Library Usage
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { readFileSync } from "node:fs";
|
|
41
|
+
import { parseCompose, parseImageRef, resolveImage } from "code-provenance";
|
|
42
|
+
|
|
43
|
+
const yaml = readFileSync("docker-compose.yml", "utf-8");
|
|
44
|
+
for (const [service, image] of parseCompose(yaml)) {
|
|
45
|
+
const ref = parseImageRef(image);
|
|
46
|
+
const result = await resolveImage(service, ref);
|
|
47
|
+
console.log(`${result.service}: ${result.commit} (${result.confidence})`);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API Reference
|
|
52
|
+
|
|
53
|
+
### Exports
|
|
54
|
+
|
|
55
|
+
- `parseCompose(yaml: string): [string, string][]` -- parse a docker-compose YAML string and return `[serviceName, imageString]` pairs
|
|
56
|
+
- `parseImageRef(image: string): ImageRef` -- parse a Docker image string into its components
|
|
57
|
+
- `resolveImage(service: string, ref: ImageRef): Promise<ImageResult>` -- resolve an image reference to its source code commit
|
|
58
|
+
- `formatTable(results: ImageResult[]): string` -- format results as a table
|
|
59
|
+
- `formatJson(results: ImageResult[]): string` -- format results as JSON
|
|
60
|
+
|
|
61
|
+
### ImageRef
|
|
62
|
+
|
|
63
|
+
| Field | Type | Description |
|
|
64
|
+
|-------|------|-------------|
|
|
65
|
+
| `registry` | `string` | e.g. `"ghcr.io"`, `"docker.io"` |
|
|
66
|
+
| `namespace` | `string` | e.g. `"myorg"`, `"library"` |
|
|
67
|
+
| `name` | `string` | e.g. `"traefik"`, `"postgres"` |
|
|
68
|
+
| `tag` | `string` | e.g. `"v3.6.0"`, `"latest"` |
|
|
69
|
+
| `raw` | `string` | original image string from docker-compose |
|
|
70
|
+
|
|
71
|
+
### ImageResult
|
|
72
|
+
|
|
73
|
+
| Field | Type | Description |
|
|
74
|
+
|-------|------|-------------|
|
|
75
|
+
| `service` | `string` | service name from docker-compose |
|
|
76
|
+
| `image` | `string` | original image string |
|
|
77
|
+
| `registry` | `string` | image registry |
|
|
78
|
+
| `repo` | `string \| null` | GitHub repository URL |
|
|
79
|
+
| `tag` | `string` | image tag |
|
|
80
|
+
| `commit` | `string \| null` | resolved commit SHA |
|
|
81
|
+
| `commit_url` | `string \| null` | URL to the commit on GitHub |
|
|
82
|
+
| `status` | `string` | `"resolved"`, `"repo_not_found"`, `"repo_found_tag_not_matched"`, or `"no_tag"` |
|
|
83
|
+
| `resolution_method` | `string \| null` | how the commit was resolved (e.g. `"oci_labels"`, `"tag_match"`) |
|
|
84
|
+
| `confidence` | `string \| null` | `"exact"` or `"approximate"` |
|
|
85
|
+
| `steps` | `string[]` | resolution steps taken (useful with `--verbose`) |
|
|
86
|
+
|
|
87
|
+
## Authentication
|
|
88
|
+
|
|
89
|
+
Set `GITHUB_TOKEN` for full functionality (digest resolution, `:latest` on GHCR, higher rate limits):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
export GITHUB_TOKEN=ghp_your_token_here
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Create a classic token at https://github.com/settings/tokens with `read:packages` scope.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { parseCompose, parseImageRef } from "./composeParser.js";
|
|
5
|
+
import { resolveImage } from "./resolver.js";
|
|
6
|
+
import { formatJson, formatTable } from "./output.js";
|
|
7
|
+
function printHelp() {
|
|
8
|
+
console.log(`usage: code-provenance [-h] [--json] [compose_file]
|
|
9
|
+
|
|
10
|
+
Resolve Docker images to their source code commits on GitHub.
|
|
11
|
+
|
|
12
|
+
positional arguments:
|
|
13
|
+
compose_file Path to docker-compose file (default: docker-compose.yml)
|
|
14
|
+
|
|
15
|
+
options:
|
|
16
|
+
-h, --help show this help message and exit
|
|
17
|
+
--json Output results as JSON
|
|
18
|
+
-v, --verbose Show resolution steps`);
|
|
19
|
+
}
|
|
20
|
+
async function main() {
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
23
|
+
printHelp();
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
const jsonOutput = args.includes("--json");
|
|
27
|
+
const verbose = args.includes("--verbose") || args.includes("-v");
|
|
28
|
+
const positionalArgs = args.filter((a) => a !== "--json" && a !== "--verbose" && a !== "-v");
|
|
29
|
+
const composeFile = positionalArgs[0] || "docker-compose.yml";
|
|
30
|
+
if (!existsSync(composeFile)) {
|
|
31
|
+
console.error(`Error: ${composeFile} not found`);
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
const yamlContent = readFileSync(composeFile, "utf-8");
|
|
35
|
+
const services = parseCompose(yamlContent);
|
|
36
|
+
if (services.length === 0) {
|
|
37
|
+
console.error("No services with images found.");
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
// Resolve all images in parallel
|
|
41
|
+
const results = await Promise.all(services.map(([serviceName, imageString]) => {
|
|
42
|
+
const ref = parseImageRef(imageString);
|
|
43
|
+
return resolveImage(serviceName, ref);
|
|
44
|
+
}));
|
|
45
|
+
if (verbose) {
|
|
46
|
+
for (const r of results) {
|
|
47
|
+
console.error(`\nResolving ${r.image} ...`);
|
|
48
|
+
for (const step of r.steps) {
|
|
49
|
+
console.error(` ${step}`);
|
|
50
|
+
}
|
|
51
|
+
console.error(` → ${r.status}` +
|
|
52
|
+
(r.status === "resolved"
|
|
53
|
+
? ` (${r.resolution_method}, ${r.confidence})`
|
|
54
|
+
: ""));
|
|
55
|
+
}
|
|
56
|
+
console.error();
|
|
57
|
+
}
|
|
58
|
+
if (jsonOutput) {
|
|
59
|
+
console.log(formatJson(results));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
console.log(formatTable(results));
|
|
63
|
+
}
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
main()
|
|
67
|
+
.then((code) => process.exit(code))
|
|
68
|
+
.catch((err) => {
|
|
69
|
+
console.error(err);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ImageRef } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a Docker image string into an ImageRef.
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseImageRef(imageString: string): ImageRef;
|
|
6
|
+
/**
|
|
7
|
+
* Parse docker-compose YAML and return list of [serviceName, imageString] pairs.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseCompose(yamlContent: string): [string, string][];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import YAML from "yaml";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a Docker image string into an ImageRef.
|
|
4
|
+
*/
|
|
5
|
+
export function parseImageRef(imageString) {
|
|
6
|
+
const raw = imageString;
|
|
7
|
+
let tag;
|
|
8
|
+
let namePart;
|
|
9
|
+
// Handle digest references (image@sha256:...)
|
|
10
|
+
if (imageString.includes("@")) {
|
|
11
|
+
const atIdx = imageString.indexOf("@");
|
|
12
|
+
namePart = imageString.slice(0, atIdx);
|
|
13
|
+
tag = imageString.slice(atIdx + 1);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
const lastSegment = imageString.split("/").pop();
|
|
17
|
+
if (lastSegment.includes(":")) {
|
|
18
|
+
const colonPos = imageString.lastIndexOf(":");
|
|
19
|
+
tag = imageString.slice(colonPos + 1);
|
|
20
|
+
namePart = imageString.slice(0, colonPos);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
tag = "latest";
|
|
24
|
+
namePart = imageString;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Determine registry
|
|
28
|
+
const parts = namePart.split("/");
|
|
29
|
+
let registry;
|
|
30
|
+
let remaining;
|
|
31
|
+
if (parts.length >= 2 && (parts[0].includes(".") || parts[0].includes(":"))) {
|
|
32
|
+
registry = parts[0];
|
|
33
|
+
remaining = parts.slice(1);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
registry = "docker.io";
|
|
37
|
+
remaining = parts;
|
|
38
|
+
}
|
|
39
|
+
// Determine namespace and name
|
|
40
|
+
let namespace;
|
|
41
|
+
let name;
|
|
42
|
+
if (remaining.length === 1) {
|
|
43
|
+
namespace = "library";
|
|
44
|
+
name = remaining[0];
|
|
45
|
+
}
|
|
46
|
+
else if (remaining.length === 2) {
|
|
47
|
+
namespace = remaining[0];
|
|
48
|
+
name = remaining[1];
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
namespace = remaining[0];
|
|
52
|
+
name = remaining.slice(1).join("/");
|
|
53
|
+
}
|
|
54
|
+
return { registry, namespace, name, tag, raw };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Parse docker-compose YAML and return list of [serviceName, imageString] pairs.
|
|
58
|
+
*/
|
|
59
|
+
export function parseCompose(yamlContent) {
|
|
60
|
+
const data = YAML.parse(yamlContent);
|
|
61
|
+
const services = data?.services ?? {};
|
|
62
|
+
const results = [];
|
|
63
|
+
for (const [serviceName, serviceConfig] of Object.entries(services)) {
|
|
64
|
+
if (serviceConfig !== null &&
|
|
65
|
+
typeof serviceConfig === "object" &&
|
|
66
|
+
"image" in serviceConfig) {
|
|
67
|
+
results.push([serviceName, serviceConfig.image]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return results;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=composeParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composeParser.js","sourceRoot":"","sources":["../src/composeParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { parseImageRef, parseCompose } from "./composeParser.js";
|
|
4
|
+
describe("parseImageRef", () => {
|
|
5
|
+
it("parses ghcr with tag", () => {
|
|
6
|
+
const ref = parseImageRef("ghcr.io/excalidraw/excalidraw:v0.17.3");
|
|
7
|
+
assert.equal(ref.registry, "ghcr.io");
|
|
8
|
+
assert.equal(ref.namespace, "excalidraw");
|
|
9
|
+
assert.equal(ref.name, "excalidraw");
|
|
10
|
+
assert.equal(ref.tag, "v0.17.3");
|
|
11
|
+
});
|
|
12
|
+
it("parses docker hub official image", () => {
|
|
13
|
+
const ref = parseImageRef("postgres:16");
|
|
14
|
+
assert.equal(ref.registry, "docker.io");
|
|
15
|
+
assert.equal(ref.namespace, "library");
|
|
16
|
+
assert.equal(ref.name, "postgres");
|
|
17
|
+
assert.equal(ref.tag, "16");
|
|
18
|
+
});
|
|
19
|
+
it("parses docker hub namespaced image", () => {
|
|
20
|
+
const ref = parseImageRef("traefik/whoami:v1.10.3");
|
|
21
|
+
assert.equal(ref.registry, "docker.io");
|
|
22
|
+
assert.equal(ref.namespace, "traefik");
|
|
23
|
+
assert.equal(ref.name, "whoami");
|
|
24
|
+
assert.equal(ref.tag, "v1.10.3");
|
|
25
|
+
});
|
|
26
|
+
it("defaults to latest when no tag", () => {
|
|
27
|
+
const ref = parseImageRef("nginx");
|
|
28
|
+
assert.equal(ref.registry, "docker.io");
|
|
29
|
+
assert.equal(ref.namespace, "library");
|
|
30
|
+
assert.equal(ref.name, "nginx");
|
|
31
|
+
assert.equal(ref.tag, "latest");
|
|
32
|
+
});
|
|
33
|
+
it("handles digest reference", () => {
|
|
34
|
+
const ref = parseImageRef("ghcr.io/org/app@sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890");
|
|
35
|
+
assert.equal(ref.registry, "ghcr.io");
|
|
36
|
+
assert.equal(ref.namespace, "org");
|
|
37
|
+
assert.equal(ref.name, "app");
|
|
38
|
+
assert.equal(ref.tag, "sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe("parseCompose", () => {
|
|
42
|
+
it("extracts services with image field", () => {
|
|
43
|
+
const yaml = `
|
|
44
|
+
services:
|
|
45
|
+
web:
|
|
46
|
+
image: nginx:latest
|
|
47
|
+
api:
|
|
48
|
+
build: .
|
|
49
|
+
db:
|
|
50
|
+
image: postgres:16
|
|
51
|
+
`;
|
|
52
|
+
const result = parseCompose(yaml);
|
|
53
|
+
assert.equal(result.length, 2);
|
|
54
|
+
assert.deepEqual(result[0], ["web", "nginx:latest"]);
|
|
55
|
+
assert.deepEqual(result[1], ["db", "postgres:16"]);
|
|
56
|
+
});
|
|
57
|
+
it("handles empty services", () => {
|
|
58
|
+
const yaml = `
|
|
59
|
+
services: {}
|
|
60
|
+
`;
|
|
61
|
+
const result = parseCompose(yaml);
|
|
62
|
+
assert.equal(result.length, 0);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=composeParser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composeParser.test.js","sourceRoot":"","sources":["../src/composeParser.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEjE,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,GAAG,GAAG,aAAa,CAAC,uCAAuC,CAAC,CAAC;QACnE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,GAAG,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACnC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,GAAG,GAAG,aAAa,CAAC,wBAAwB,CAAC,CAAC;QACpD,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,GAAG,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACnC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAChC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,GAAG,GAAG,aAAa,CACvB,yFAAyF,CAC1F,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACnC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC9B,MAAM,CAAC,KAAK,CACV,GAAG,CAAC,GAAG,EACP,yEAAyE,CAC1E,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,IAAI,GAAG;;;;;;;;CAQhB,CAAC;QACE,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,IAAI,GAAG;;CAEhB,CAAC;QACE,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/github.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve an image tag to a commit SHA by matching against git tags.
|
|
3
|
+
* Tries exact match first, then prefix match (e.g., v2.10 -> highest v2.10.x).
|
|
4
|
+
* Returns [commit_sha, is_exact_match] or null.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveTagToCommit(owner: string, repo: string, tag: string): Promise<[string, boolean] | null>;
|
|
7
|
+
/**
|
|
8
|
+
* Get the commit SHA of the latest GitHub release.
|
|
9
|
+
* Returns [commit_sha, tag_name] or null.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getLatestReleaseCommit(owner: string, repo: string): Promise<[string, string] | null>;
|
|
12
|
+
/**
|
|
13
|
+
* Get the latest commit SHA on the default branch.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getLatestCommit(owner: string, repo: string): Promise<string | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Check if a GitHub repo exists.
|
|
18
|
+
*/
|
|
19
|
+
export declare function checkGithubRepoExists(owner: string, repo: string): Promise<boolean>;
|
|
20
|
+
interface PackageVersionResult {
|
|
21
|
+
repo: string;
|
|
22
|
+
commit: string | null;
|
|
23
|
+
tags: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Find the commit for a GHCR image by its digest.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveGhcrDigestViaPackages(owner: string, packageName: string, digest: string): Promise<PackageVersionResult | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Find the commit for a GHCR image's :latest tag.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveGhcrLatestViaPackages(owner: string, packageName: string): Promise<PackageVersionResult | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Try to find the GitHub repo for a Docker Hub image.
|
|
35
|
+
*/
|
|
36
|
+
export declare function inferRepoFromDockerhub(namespace: string, name: string): Promise<[string, string] | null>;
|
|
37
|
+
export {};
|
package/dist/github.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
function githubHeaders() {
|
|
2
|
+
const headers = {
|
|
3
|
+
Accept: "application/vnd.github+json",
|
|
4
|
+
};
|
|
5
|
+
const token = process.env.GITHUB_TOKEN;
|
|
6
|
+
if (token) {
|
|
7
|
+
headers.Authorization = `Bearer ${token}`;
|
|
8
|
+
}
|
|
9
|
+
return headers;
|
|
10
|
+
}
|
|
11
|
+
function normalizeTag(tag) {
|
|
12
|
+
return tag.replace(/^v+/, "");
|
|
13
|
+
}
|
|
14
|
+
function isPrefixMatch(imageTag, gitTag) {
|
|
15
|
+
const normImage = normalizeTag(imageTag);
|
|
16
|
+
const normGit = normalizeTag(gitTag);
|
|
17
|
+
return normGit.startsWith(normImage + ".");
|
|
18
|
+
}
|
|
19
|
+
function parseVersionTuple(tag) {
|
|
20
|
+
let norm = normalizeTag(tag);
|
|
21
|
+
// Strip pre-release suffixes like -rc1, -beta2
|
|
22
|
+
norm = norm.split(/[-+]/)[0];
|
|
23
|
+
const parts = norm.split(".");
|
|
24
|
+
try {
|
|
25
|
+
const nums = parts.map((p) => {
|
|
26
|
+
const n = parseInt(p, 10);
|
|
27
|
+
if (isNaN(n))
|
|
28
|
+
throw new Error();
|
|
29
|
+
return n;
|
|
30
|
+
});
|
|
31
|
+
return nums;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function compareVersions(a, b) {
|
|
38
|
+
const len = Math.max(a.length, b.length);
|
|
39
|
+
for (let i = 0; i < len; i++) {
|
|
40
|
+
const av = a[i] ?? 0;
|
|
41
|
+
const bv = b[i] ?? 0;
|
|
42
|
+
if (av !== bv)
|
|
43
|
+
return av - bv;
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parse the Link header for pagination.
|
|
49
|
+
* Returns the "next" URL or null.
|
|
50
|
+
*/
|
|
51
|
+
function parseNextLink(linkHeader) {
|
|
52
|
+
if (!linkHeader)
|
|
53
|
+
return null;
|
|
54
|
+
const match = linkHeader.match(/<([^>]+)>;\s*rel="next"/);
|
|
55
|
+
return match ? match[1] : null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve an image tag to a commit SHA by matching against git tags.
|
|
59
|
+
* Tries exact match first, then prefix match (e.g., v2.10 -> highest v2.10.x).
|
|
60
|
+
* Returns [commit_sha, is_exact_match] or null.
|
|
61
|
+
*/
|
|
62
|
+
export async function resolveTagToCommit(owner, repo, tag) {
|
|
63
|
+
const headers = githubHeaders();
|
|
64
|
+
let url = `https://api.github.com/repos/${owner}/${repo}/tags?per_page=100`;
|
|
65
|
+
const prefixCandidates = [];
|
|
66
|
+
while (url) {
|
|
67
|
+
const resp = await fetch(url, {
|
|
68
|
+
headers,
|
|
69
|
+
signal: AbortSignal.timeout(10000),
|
|
70
|
+
});
|
|
71
|
+
if (!resp.ok)
|
|
72
|
+
return null;
|
|
73
|
+
const gitTags = await resp.json();
|
|
74
|
+
for (const gitTag of gitTags) {
|
|
75
|
+
const name = gitTag.name;
|
|
76
|
+
// Exact match (with/without v prefix)
|
|
77
|
+
if (name === tag ||
|
|
78
|
+
name === `v${tag}` ||
|
|
79
|
+
normalizeTag(name) === normalizeTag(tag)) {
|
|
80
|
+
return [gitTag.commit.sha, true];
|
|
81
|
+
}
|
|
82
|
+
// Collect prefix match candidates
|
|
83
|
+
if (isPrefixMatch(tag, name)) {
|
|
84
|
+
const version = parseVersionTuple(name);
|
|
85
|
+
if (version !== null) {
|
|
86
|
+
prefixCandidates.push([version, gitTag.commit.sha]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
url = parseNextLink(resp.headers.get("link"));
|
|
91
|
+
}
|
|
92
|
+
// Return the highest version among prefix matches
|
|
93
|
+
if (prefixCandidates.length > 0) {
|
|
94
|
+
prefixCandidates.sort((a, b) => compareVersions(b[0], a[0]));
|
|
95
|
+
return [prefixCandidates[0][1], false];
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get the commit SHA of the latest GitHub release.
|
|
101
|
+
* Returns [commit_sha, tag_name] or null.
|
|
102
|
+
*/
|
|
103
|
+
export async function getLatestReleaseCommit(owner, repo) {
|
|
104
|
+
const headers = githubHeaders();
|
|
105
|
+
try {
|
|
106
|
+
const resp = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases/latest`, { headers, signal: AbortSignal.timeout(10000) });
|
|
107
|
+
if (!resp.ok)
|
|
108
|
+
return null;
|
|
109
|
+
const data = (await resp.json());
|
|
110
|
+
const tagName = data.tag_name;
|
|
111
|
+
if (!tagName)
|
|
112
|
+
return null;
|
|
113
|
+
const tagResult = await resolveTagToCommit(owner, repo, tagName);
|
|
114
|
+
if (tagResult) {
|
|
115
|
+
return [tagResult[0], tagName];
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get the latest commit SHA on the default branch.
|
|
125
|
+
*/
|
|
126
|
+
export async function getLatestCommit(owner, repo) {
|
|
127
|
+
const headers = githubHeaders();
|
|
128
|
+
try {
|
|
129
|
+
const resp = await fetch(`https://api.github.com/repos/${owner}/${repo}/commits?per_page=1`, { headers, signal: AbortSignal.timeout(10000) });
|
|
130
|
+
if (!resp.ok)
|
|
131
|
+
return null;
|
|
132
|
+
const commits = (await resp.json());
|
|
133
|
+
if (commits.length > 0)
|
|
134
|
+
return commits[0].sha;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// ignore
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Check if a GitHub repo exists.
|
|
143
|
+
*/
|
|
144
|
+
export async function checkGithubRepoExists(owner, repo) {
|
|
145
|
+
const headers = githubHeaders();
|
|
146
|
+
try {
|
|
147
|
+
const resp = await fetch(`https://api.github.com/repos/${owner}/${repo}`, { headers, signal: AbortSignal.timeout(10000) });
|
|
148
|
+
return resp.status === 200;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Find a GHCR package version by digest or tag via the GitHub Packages API.
|
|
156
|
+
*/
|
|
157
|
+
async function findGhcrPackageVersion(owner, packageName, options) {
|
|
158
|
+
const headers = githubHeaders();
|
|
159
|
+
if (!headers.Authorization)
|
|
160
|
+
return null;
|
|
161
|
+
for (const entityType of ["orgs", "users"]) {
|
|
162
|
+
const pkgBase = `https://api.github.com/${entityType}/${owner}/packages/container/${packageName}`;
|
|
163
|
+
// Get package metadata for source repo
|
|
164
|
+
let fullName;
|
|
165
|
+
try {
|
|
166
|
+
const pkgResp = await fetch(pkgBase, {
|
|
167
|
+
headers,
|
|
168
|
+
signal: AbortSignal.timeout(10000),
|
|
169
|
+
});
|
|
170
|
+
if (pkgResp.status === 403)
|
|
171
|
+
return null;
|
|
172
|
+
if (!pkgResp.ok)
|
|
173
|
+
continue;
|
|
174
|
+
const pkgData = await pkgResp.json();
|
|
175
|
+
fullName = pkgData?.repository?.full_name;
|
|
176
|
+
if (!fullName)
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
// Search versions
|
|
183
|
+
let versionsUrl = `${pkgBase}/versions?per_page=50`;
|
|
184
|
+
try {
|
|
185
|
+
while (versionsUrl) {
|
|
186
|
+
const resp = await fetch(versionsUrl, {
|
|
187
|
+
headers,
|
|
188
|
+
signal: AbortSignal.timeout(10000),
|
|
189
|
+
});
|
|
190
|
+
if (!resp.ok)
|
|
191
|
+
break;
|
|
192
|
+
const versions = (await resp.json());
|
|
193
|
+
for (const version of versions) {
|
|
194
|
+
const name = version.name ?? "";
|
|
195
|
+
const metadata = version.metadata?.container ?? {};
|
|
196
|
+
const tags = metadata.tags ?? [];
|
|
197
|
+
// Match by digest (version name is the digest)
|
|
198
|
+
if (options.matchDigest && name !== options.matchDigest) {
|
|
199
|
+
if (!options.matchTag)
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
// Match by tag
|
|
203
|
+
if (options.matchTag && !tags.includes(options.matchTag))
|
|
204
|
+
continue;
|
|
205
|
+
// Found matching version - resolve tags to a commit
|
|
206
|
+
const [repoOwner, repoName] = fullName.split("/", 2);
|
|
207
|
+
const resolvableTags = tags.filter((t) => t !== "latest");
|
|
208
|
+
for (const t of resolvableTags) {
|
|
209
|
+
const tagResult = await resolveTagToCommit(repoOwner, repoName, t);
|
|
210
|
+
if (tagResult) {
|
|
211
|
+
return { repo: fullName, commit: tagResult[0], tags };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return { repo: fullName, commit: null, tags };
|
|
215
|
+
}
|
|
216
|
+
versionsUrl = parseNextLink(resp.headers.get("link"));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Find the commit for a GHCR image by its digest.
|
|
227
|
+
*/
|
|
228
|
+
export async function resolveGhcrDigestViaPackages(owner, packageName, digest) {
|
|
229
|
+
return findGhcrPackageVersion(owner, packageName, { matchDigest: digest });
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Find the commit for a GHCR image's :latest tag.
|
|
233
|
+
*/
|
|
234
|
+
export async function resolveGhcrLatestViaPackages(owner, packageName) {
|
|
235
|
+
return findGhcrPackageVersion(owner, packageName, { matchTag: "latest" });
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Try to find the GitHub repo for a Docker Hub image.
|
|
239
|
+
*/
|
|
240
|
+
export async function inferRepoFromDockerhub(namespace, name) {
|
|
241
|
+
// For official images (library/X), try the image name as org/repo directly
|
|
242
|
+
if (namespace === "library") {
|
|
243
|
+
if (await checkGithubRepoExists(name, name)) {
|
|
244
|
+
return [name, name];
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// For namespaced images, try namespace/name on GitHub
|
|
248
|
+
if (namespace !== "library") {
|
|
249
|
+
if (await checkGithubRepoExists(namespace, name)) {
|
|
250
|
+
return [namespace, name];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Fall back to scraping Docker Hub description for GitHub links
|
|
254
|
+
try {
|
|
255
|
+
const resp = await fetch(`https://hub.docker.com/v2/repositories/${namespace}/${name}`, { signal: AbortSignal.timeout(10000) });
|
|
256
|
+
if (!resp.ok)
|
|
257
|
+
return null;
|
|
258
|
+
const data = (await resp.json());
|
|
259
|
+
const text = (data.full_description || "") + " " + (data.description || "");
|
|
260
|
+
const match = text.match(/https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+)/);
|
|
261
|
+
if (match) {
|
|
262
|
+
return [match[1], match[2]];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// ignore
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"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"}
|