@johnhenry/sitefile 0.0.0-stage → 0.0.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/CHANGELOG.md +152 -0
- package/LICENSE +21 -0
- package/README.md +513 -2
- package/lib/src/archive-writers.d.ts +31 -0
- package/lib/src/archive-writers.js +197 -0
- package/lib/src/attest.d.ts +126 -0
- package/lib/src/attest.js +220 -0
- package/lib/src/bake.d.ts +74 -0
- package/lib/src/bake.js +372 -0
- package/lib/src/base-image.d.ts +76 -0
- package/lib/src/base-image.js +138 -0
- package/lib/src/build-args.d.ts +26 -0
- package/lib/src/build-args.js +164 -0
- package/lib/src/cache-limit.d.ts +54 -0
- package/lib/src/cache-limit.js +239 -0
- package/lib/src/casefold-data.d.ts +2 -0
- package/lib/src/casefold-data.js +217 -0
- package/lib/src/cbor.d.ts +39 -0
- package/lib/src/cbor.js +174 -0
- package/lib/src/checklinks.d.ts +42 -0
- package/lib/src/checklinks.js +120 -0
- package/lib/src/cli/bake.d.ts +2 -0
- package/lib/src/cli/bake.js +179 -0
- package/lib/src/cli/build-image.d.ts +21 -0
- package/lib/src/cli/build-image.js +42 -0
- package/lib/src/cli/cache.d.ts +8 -0
- package/lib/src/cli/cache.js +119 -0
- package/lib/src/cli/common.d.ts +29 -0
- package/lib/src/cli/common.js +100 -0
- package/lib/src/cli/git-context.d.ts +18 -0
- package/lib/src/cli/git-context.js +48 -0
- package/lib/src/cli/images.d.ts +6 -0
- package/lib/src/cli/images.js +285 -0
- package/lib/src/cli/index.d.ts +10 -0
- package/lib/src/cli/index.js +95 -0
- package/lib/src/cli/network.d.ts +34 -0
- package/lib/src/cli/network.js +56 -0
- package/lib/src/cli/outdated.d.ts +5 -0
- package/lib/src/cli/outdated.js +99 -0
- package/lib/src/cli/registry.d.ts +2 -0
- package/lib/src/cli/registry.js +127 -0
- package/lib/src/cli/serve-worker.d.ts +2 -0
- package/lib/src/cli/serve-worker.js +42 -0
- package/lib/src/cli/servers.d.ts +15 -0
- package/lib/src/cli/servers.js +467 -0
- package/lib/src/cli/vendor.d.ts +2 -0
- package/lib/src/cli/vendor.js +97 -0
- package/lib/src/cli/watch.d.ts +26 -0
- package/lib/src/cli/watch.js +81 -0
- package/lib/src/cli.d.ts +12 -0
- package/lib/src/cli.js +522 -0
- package/lib/src/csv.d.ts +27 -0
- package/lib/src/csv.js +117 -0
- package/lib/src/deflate.d.ts +76 -0
- package/lib/src/deflate.js +604 -0
- package/lib/src/digest.d.ts +3 -0
- package/lib/src/digest.js +7 -0
- package/lib/src/errors.d.ts +56 -0
- package/lib/src/errors.js +29 -0
- package/lib/src/executor.d.ts +393 -0
- package/lib/src/executor.js +2201 -0
- package/lib/src/explain.d.ts +64 -0
- package/lib/src/explain.js +445 -0
- package/lib/src/export-formats.d.ts +23 -0
- package/lib/src/export-formats.js +63 -0
- package/lib/src/exporter.d.ts +103 -0
- package/lib/src/exporter.js +676 -0
- package/lib/src/fingerprint.d.ts +40 -0
- package/lib/src/fingerprint.js +168 -0
- package/lib/src/frontmatter.d.ts +22 -0
- package/lib/src/frontmatter.js +45 -0
- package/lib/src/index.d.ts +13 -0
- package/lib/src/index.js +13 -0
- package/lib/src/language/ast.d.ts +360 -0
- package/lib/src/language/ast.js +57 -0
- package/lib/src/language/parser.d.ts +3 -0
- package/lib/src/language/parser.js +612 -0
- package/lib/src/language/scanner.d.ts +110 -0
- package/lib/src/language/scanner.js +449 -0
- package/lib/src/language/validate.d.ts +13 -0
- package/lib/src/language/validate.js +367 -0
- package/lib/src/links.d.ts +79 -0
- package/lib/src/links.js +583 -0
- package/lib/src/lint.d.ts +10 -0
- package/lib/src/lint.js +223 -0
- package/lib/src/markdown.d.ts +29 -0
- package/lib/src/markdown.js +612 -0
- package/lib/src/media.d.ts +82 -0
- package/lib/src/media.js +650 -0
- package/lib/src/memo.d.ts +57 -0
- package/lib/src/memo.js +93 -0
- package/lib/src/oci.d.ts +178 -0
- package/lib/src/oci.js +380 -0
- package/lib/src/outdated.d.ts +46 -0
- package/lib/src/outdated.js +195 -0
- package/lib/src/pages.d.ts +10 -0
- package/lib/src/pages.js +54 -0
- package/lib/src/paths.d.ts +39 -0
- package/lib/src/paths.js +101 -0
- package/lib/src/planner.d.ts +297 -0
- package/lib/src/planner.js +410 -0
- package/lib/src/plugins/cache.d.ts +96 -0
- package/lib/src/plugins/cache.js +325 -0
- package/lib/src/plugins/exec.d.ts +51 -0
- package/lib/src/plugins/exec.js +144 -0
- package/lib/src/plugins/host.d.ts +75 -0
- package/lib/src/plugins/host.js +277 -0
- package/lib/src/plugins/wasm.d.ts +23 -0
- package/lib/src/plugins/wasm.js +249 -0
- package/lib/src/profile.d.ts +42 -0
- package/lib/src/profile.js +68 -0
- package/lib/src/ref.d.ts +40 -0
- package/lib/src/ref.js +79 -0
- package/lib/src/registry-auth.d.ts +32 -0
- package/lib/src/registry-auth.js +151 -0
- package/lib/src/registry-cache.d.ts +71 -0
- package/lib/src/registry-cache.js +333 -0
- package/lib/src/registry-ops.d.ts +51 -0
- package/lib/src/registry-ops.js +248 -0
- package/lib/src/registry.d.ts +115 -0
- package/lib/src/registry.js +524 -0
- package/lib/src/render.d.ts +134 -0
- package/lib/src/render.js +576 -0
- package/lib/src/resources/archive.d.ts +11 -0
- package/lib/src/resources/archive.js +306 -0
- package/lib/src/resources/bundle-data.d.ts +16 -0
- package/lib/src/resources/bundle-data.js +50 -0
- package/lib/src/resources/convert.d.ts +12 -0
- package/lib/src/resources/convert.js +63 -0
- package/lib/src/resources/documents.d.ts +35 -0
- package/lib/src/resources/documents.js +122 -0
- package/lib/src/resources/git.d.ts +103 -0
- package/lib/src/resources/git.js +720 -0
- package/lib/src/resources/https.d.ts +74 -0
- package/lib/src/resources/https.js +413 -0
- package/lib/src/resources/ignore.d.ts +33 -0
- package/lib/src/resources/ignore.js +119 -0
- package/lib/src/resources/local.d.ts +64 -0
- package/lib/src/resources/local.js +248 -0
- package/lib/src/resources/lockfile.d.ts +52 -0
- package/lib/src/resources/lockfile.js +172 -0
- package/lib/src/resources/netpolicy.d.ts +6 -0
- package/lib/src/resources/netpolicy.js +82 -0
- package/lib/src/resources/network.d.ts +70 -0
- package/lib/src/resources/network.js +304 -0
- package/lib/src/resources/types.d.ts +69 -0
- package/lib/src/resources/types.js +13 -0
- package/lib/src/resources/vendor.d.ts +94 -0
- package/lib/src/resources/vendor.js +296 -0
- package/lib/src/resources/wbn-reader.d.ts +44 -0
- package/lib/src/resources/wbn-reader.js +302 -0
- package/lib/src/schema.d.ts +25 -0
- package/lib/src/schema.js +169 -0
- package/lib/src/secrets.d.ts +21 -0
- package/lib/src/secrets.js +103 -0
- package/lib/src/serve-rules.d.ts +29 -0
- package/lib/src/serve-rules.js +123 -0
- package/lib/src/server.d.ts +45 -0
- package/lib/src/server.js +246 -0
- package/lib/src/servers.d.ts +85 -0
- package/lib/src/servers.js +225 -0
- package/lib/src/site.d.ts +60 -0
- package/lib/src/site.js +169 -0
- package/lib/src/store.d.ts +151 -0
- package/lib/src/store.js +596 -0
- package/lib/src/tree.d.ts +50 -0
- package/lib/src/tree.js +134 -0
- package/lib/src/values.d.ts +7 -0
- package/lib/src/values.js +37 -0
- package/lib/src/vendor.d.ts +19 -0
- package/lib/src/vendor.js +145 -0
- package/lib/src/watch.d.ts +44 -0
- package/lib/src/watch.js +137 -0
- package/lib/src/wbn.d.ts +26 -0
- package/lib/src/wbn.js +241 -0
- package/lib/src/yaml.d.ts +34 -0
- package/lib/src/yaml.js +562 -0
- package/package.json +59 -4
package/lib/src/ref.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image reference grammar (shaped like Docker's so a registry can be added later):
|
|
3
|
+
*
|
|
4
|
+
* [registry-host[:port]/]name[:tag][@sha256:<64 hex>]
|
|
5
|
+
*
|
|
6
|
+
* name = component ('/' component)*, component = [a-z0-9]+([._-][a-z0-9]+)*
|
|
7
|
+
* tag = [A-Za-z0-9_][A-Za-z0-9_.-]{0,127} (default `latest`)
|
|
8
|
+
* The first path component is a registry host when it contains '.' or ':' or is
|
|
9
|
+
* `localhost` and something follows it.
|
|
10
|
+
*
|
|
11
|
+
* A reference with a registry host names an image on that registry; locally it is just a tag
|
|
12
|
+
* (`sitefile tag blog:1 localhost:5000/blog:1`), so the store keys it as `registry/name:tag`.
|
|
13
|
+
* Docker Hub aliases (`docker.io`, `index.docker.io`, `registry-1.docker.io`) are one registry,
|
|
14
|
+
* and a Hub name without a namespace lives in `library/`. A reference with NO registry host is
|
|
15
|
+
* local everywhere except `pull` and `BASE`, which default it to Docker Hub (`nginx` is
|
|
16
|
+
* `docker.io/library/nginx`); see {@link withDefaultRegistry}.
|
|
17
|
+
*/
|
|
18
|
+
export interface ImageRef {
|
|
19
|
+
readonly registry: string | undefined;
|
|
20
|
+
readonly name: string;
|
|
21
|
+
/** The explicit tag, if any. */
|
|
22
|
+
readonly tag: string | undefined;
|
|
23
|
+
/** `sha256:<hex>` when the reference pins an image ID. */
|
|
24
|
+
readonly digest: string | undefined;
|
|
25
|
+
}
|
|
26
|
+
export declare const DEFAULT_TAG = "latest";
|
|
27
|
+
export declare function parseRef(input: string): ImageRef;
|
|
28
|
+
export declare const DOCKER_HUB = "docker.io";
|
|
29
|
+
/** Canonical form of a reference with a registry host (Docker Hub aliases and `library/` applied). */
|
|
30
|
+
export declare function canonicalRef(ref: ImageRef): ImageRef;
|
|
31
|
+
/** `pull` / `BASE` semantics: a reference without a registry host means Docker Hub. */
|
|
32
|
+
export declare function withDefaultRegistry(ref: ImageRef): ImageRef;
|
|
33
|
+
/** The host to connect to for a (canonical) registry name. */
|
|
34
|
+
export declare function registryApiHost(registry: string): string;
|
|
35
|
+
/** Parse a reference that may be used as a local name or tag (a registry host is allowed). */
|
|
36
|
+
export declare function parseLocalRef(input: string): ImageRef;
|
|
37
|
+
/** The refs.json key of a local reference: `[registry/]name:tag`. */
|
|
38
|
+
export declare function refKey(ref: ImageRef): string;
|
|
39
|
+
/** Hex of at least 12 digits, optionally `sha256:`-prefixed: an image ID (prefix). */
|
|
40
|
+
export declare function looksLikeImageId(s: string): boolean;
|
package/lib/src/ref.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { SitefileError } from './errors.js';
|
|
2
|
+
const COMPONENT = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
|
|
3
|
+
const TAG = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/;
|
|
4
|
+
const HOST = /^(?:[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?|\[[0-9A-Fa-f:]+\])(?::[0-9]{1,5})?$/;
|
|
5
|
+
const DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
6
|
+
export const DEFAULT_TAG = 'latest';
|
|
7
|
+
function bad(ref, why) {
|
|
8
|
+
return new SitefileError('E_USAGE', `invalid image reference '${ref}': ${why}`);
|
|
9
|
+
}
|
|
10
|
+
export function parseRef(input) {
|
|
11
|
+
if (input === '')
|
|
12
|
+
throw bad(input, 'empty reference');
|
|
13
|
+
let rest = input;
|
|
14
|
+
let digest;
|
|
15
|
+
const at = rest.indexOf('@');
|
|
16
|
+
if (at !== -1) {
|
|
17
|
+
digest = rest.slice(at + 1);
|
|
18
|
+
rest = rest.slice(0, at);
|
|
19
|
+
if (!DIGEST.test(digest))
|
|
20
|
+
throw bad(input, "the digest must be 'sha256:' followed by 64 lowercase hex digits");
|
|
21
|
+
}
|
|
22
|
+
let tag;
|
|
23
|
+
const lastSlash = rest.lastIndexOf('/');
|
|
24
|
+
const colon = rest.indexOf(':', lastSlash + 1);
|
|
25
|
+
if (colon !== -1) {
|
|
26
|
+
tag = rest.slice(colon + 1);
|
|
27
|
+
rest = rest.slice(0, colon);
|
|
28
|
+
if (!TAG.test(tag))
|
|
29
|
+
throw bad(input, `invalid tag '${tag}' (letters, digits, '_', '.', '-'; at most 128 characters)`);
|
|
30
|
+
}
|
|
31
|
+
const parts = rest.split('/');
|
|
32
|
+
let registry;
|
|
33
|
+
const first = parts[0] ?? '';
|
|
34
|
+
if (parts.length > 1 && (first.includes('.') || first.includes(':') || first === 'localhost')) {
|
|
35
|
+
if (!HOST.test(first))
|
|
36
|
+
throw bad(input, `invalid registry host '${first}'`);
|
|
37
|
+
registry = first;
|
|
38
|
+
parts.shift();
|
|
39
|
+
}
|
|
40
|
+
const name = parts.join('/');
|
|
41
|
+
if (name === '' || name.length > 255)
|
|
42
|
+
throw bad(input, 'the name must be 1-255 characters');
|
|
43
|
+
for (const c of parts) {
|
|
44
|
+
if (!COMPONENT.test(c)) {
|
|
45
|
+
throw bad(input, `invalid name component '${c}' (lowercase letters and digits separated by '.', '_' or '-')`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { registry, name, tag, digest };
|
|
49
|
+
}
|
|
50
|
+
const DOCKER_HUB_ALIASES = new Set(['docker.io', 'index.docker.io', 'registry-1.docker.io']);
|
|
51
|
+
export const DOCKER_HUB = 'docker.io';
|
|
52
|
+
/** Canonical form of a reference with a registry host (Docker Hub aliases and `library/` applied). */
|
|
53
|
+
export function canonicalRef(ref) {
|
|
54
|
+
if (ref.registry === undefined || !DOCKER_HUB_ALIASES.has(ref.registry.toLowerCase()))
|
|
55
|
+
return ref;
|
|
56
|
+
return { ...ref, registry: DOCKER_HUB, name: ref.name.includes('/') ? ref.name : `library/${ref.name}` };
|
|
57
|
+
}
|
|
58
|
+
/** `pull` / `BASE` semantics: a reference without a registry host means Docker Hub. */
|
|
59
|
+
export function withDefaultRegistry(ref) {
|
|
60
|
+
return canonicalRef(ref.registry === undefined ? { ...ref, registry: DOCKER_HUB } : ref);
|
|
61
|
+
}
|
|
62
|
+
/** The host to connect to for a (canonical) registry name. */
|
|
63
|
+
export function registryApiHost(registry) {
|
|
64
|
+
return registry === DOCKER_HUB ? 'registry-1.docker.io' : registry;
|
|
65
|
+
}
|
|
66
|
+
/** Parse a reference that may be used as a local name or tag (a registry host is allowed). */
|
|
67
|
+
export function parseLocalRef(input) {
|
|
68
|
+
return canonicalRef(parseRef(input));
|
|
69
|
+
}
|
|
70
|
+
/** The refs.json key of a local reference: `[registry/]name:tag`. */
|
|
71
|
+
export function refKey(ref) {
|
|
72
|
+
const c = canonicalRef(ref);
|
|
73
|
+
return `${c.registry === undefined ? '' : `${c.registry}/`}${c.name}:${c.tag ?? DEFAULT_TAG}`;
|
|
74
|
+
}
|
|
75
|
+
/** Hex of at least 12 digits, optionally `sha256:`-prefixed: an image ID (prefix). */
|
|
76
|
+
export function looksLikeImageId(s) {
|
|
77
|
+
return /^(?:sha256:)?[0-9a-f]{12,64}$/.test(s);
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=ref.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry credentials. Sources, first match wins:
|
|
3
|
+
*
|
|
4
|
+
* 1. explicit (`--username` + `--password-stdin`; applies only to the registry the command names)
|
|
5
|
+
* 2. $SITEFILE_REGISTRY_AUTH JSON: { "<host>": "user:password" | {"username","password"} | {"identitytoken"} }
|
|
6
|
+
* 3. Docker config ($DOCKER_CONFIG/config.json or ~/.docker/config.json):
|
|
7
|
+
* credHelpers[<host>] -> docker-credential-<name> get
|
|
8
|
+
* auths[<host>].auth|username/password|identitytoken
|
|
9
|
+
* credsStore -> docker-credential-<name> get
|
|
10
|
+
*
|
|
11
|
+
* Running a credential helper executes a program, so it happens only when the config names one
|
|
12
|
+
* (the name must be a plain word, it is run without a shell, the registry URL goes to its stdin, and
|
|
13
|
+
* nothing secret is ever on argv). Secrets are returned to the caller and never logged or put in an
|
|
14
|
+
* error message.
|
|
15
|
+
*/
|
|
16
|
+
export interface Credentials {
|
|
17
|
+
readonly username?: string;
|
|
18
|
+
readonly password?: string;
|
|
19
|
+
/** A Docker "identity token" (OAuth2 refresh token), exchanged at the token endpoint. */
|
|
20
|
+
readonly identityToken?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface CredentialOptions {
|
|
23
|
+
readonly explicit?: Credentials | undefined;
|
|
24
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
25
|
+
/** Helper timeout (ms). */
|
|
26
|
+
readonly helperTimeoutMs?: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function dockerConfigPath(env: NodeJS.ProcessEnv): string;
|
|
29
|
+
/** Docker's `docker-credential-<name> get`: the server URL on stdin, JSON on stdout. */
|
|
30
|
+
export declare function runCredentialHelper(name: string, serverUrl: string, env: NodeJS.ProcessEnv, timeoutMs: number): Promise<Credentials | undefined>;
|
|
31
|
+
/** Resolve credentials for `registry` (canonical host, e.g. `docker.io` or `localhost:5000`), or undefined for anonymous access. */
|
|
32
|
+
export declare function resolveCredentials(registry: string, options?: CredentialOptions): Promise<Credentials | undefined>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { SitefileError } from './errors.js';
|
|
6
|
+
const DOCKER_HUB_KEYS = ['docker.io', 'index.docker.io', 'registry-1.docker.io'];
|
|
7
|
+
/** `https://index.docker.io/v1/`, `registry.example.com:5000/x` -> host key. */
|
|
8
|
+
function hostKey(k) {
|
|
9
|
+
const noScheme = k.replace(/^[a-z]+:\/\//i, '');
|
|
10
|
+
const host = (noScheme.split('/')[0] ?? '').toLowerCase();
|
|
11
|
+
return DOCKER_HUB_KEYS.includes(host) ? 'docker.io' : host;
|
|
12
|
+
}
|
|
13
|
+
/** Names under which a registry may appear in a docker config. */
|
|
14
|
+
function matches(configKey, registry) {
|
|
15
|
+
return hostKey(configKey) === hostKey(registry);
|
|
16
|
+
}
|
|
17
|
+
export function dockerConfigPath(env) {
|
|
18
|
+
const dir = env.DOCKER_CONFIG;
|
|
19
|
+
return path.join(dir !== undefined && dir !== '' ? dir : path.join(os.homedir(), '.docker'), 'config.json');
|
|
20
|
+
}
|
|
21
|
+
function isRecord(v) {
|
|
22
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
23
|
+
}
|
|
24
|
+
function str(v) {
|
|
25
|
+
return typeof v === 'string' && v !== '' ? v : undefined;
|
|
26
|
+
}
|
|
27
|
+
function fromEntry(e) {
|
|
28
|
+
if (typeof e === 'string') {
|
|
29
|
+
const i = e.indexOf(':');
|
|
30
|
+
return i > 0 ? { username: e.slice(0, i), password: e.slice(i + 1) } : undefined;
|
|
31
|
+
}
|
|
32
|
+
if (!isRecord(e))
|
|
33
|
+
return undefined;
|
|
34
|
+
const auth = str(e.auth);
|
|
35
|
+
if (auth) {
|
|
36
|
+
const text = Buffer.from(auth, 'base64').toString('utf8');
|
|
37
|
+
const i = text.indexOf(':');
|
|
38
|
+
if (i > 0)
|
|
39
|
+
return { username: text.slice(0, i), password: text.slice(i + 1) };
|
|
40
|
+
}
|
|
41
|
+
const identityToken = str(e.identitytoken) ?? str(e.identityToken);
|
|
42
|
+
if (identityToken)
|
|
43
|
+
return { identityToken, ...(str(e.username) ? { username: str(e.username) } : {}) };
|
|
44
|
+
const username = str(e.username);
|
|
45
|
+
const password = str(e.password);
|
|
46
|
+
if (username && password)
|
|
47
|
+
return { username, password };
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
/** Docker's `docker-credential-<name> get`: the server URL on stdin, JSON on stdout. */
|
|
51
|
+
export function runCredentialHelper(name, serverUrl, env, timeoutMs) {
|
|
52
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(name)) {
|
|
53
|
+
throw new SitefileError('E_AUTH', `refusing to run credential helper with unusual name '${name.slice(0, 40)}'`);
|
|
54
|
+
}
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const child = execFile(`docker-credential-${name}`, ['get'], { env, timeout: timeoutMs, maxBuffer: 1 << 20, windowsHide: true }, (err, stdout) => {
|
|
57
|
+
if (err) {
|
|
58
|
+
// The helper protocol's "no entry for this server" answer means anonymous access.
|
|
59
|
+
if (typeof stdout === 'string' && /credentials not found/i.test(stdout)) {
|
|
60
|
+
resolve(undefined);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Deliberately no stdout/stderr: it may contain the secret.
|
|
64
|
+
const code = err.code;
|
|
65
|
+
reject(new SitefileError('E_AUTH', code === 'ENOENT'
|
|
66
|
+
? `credential helper docker-credential-${name} (named in your Docker config) was not found on PATH`
|
|
67
|
+
: `credential helper docker-credential-${name} failed`));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const j = JSON.parse(stdout);
|
|
72
|
+
const user = str(j.Username);
|
|
73
|
+
const secret = str(j.Secret);
|
|
74
|
+
if (user === '<token>' && secret)
|
|
75
|
+
resolve({ identityToken: secret });
|
|
76
|
+
else
|
|
77
|
+
resolve(user && secret ? { username: user, password: secret } : undefined);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
reject(new SitefileError('E_AUTH', `credential helper docker-credential-${name} returned unreadable output`));
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
child.stdin?.on('error', () => undefined);
|
|
84
|
+
child.stdin?.end(serverUrl);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
async function readJson(file) {
|
|
88
|
+
let text;
|
|
89
|
+
try {
|
|
90
|
+
text = await fs.readFile(file, 'utf8');
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
if (e.code === 'ENOENT')
|
|
94
|
+
return undefined;
|
|
95
|
+
throw new SitefileError('E_AUTH', `cannot read ${file}: ${e.message}`);
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(text);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
throw new SitefileError('E_AUTH', `${file} is not valid JSON`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** Resolve credentials for `registry` (canonical host, e.g. `docker.io` or `localhost:5000`), or undefined for anonymous access. */
|
|
105
|
+
export async function resolveCredentials(registry, options = {}) {
|
|
106
|
+
if (options.explicit)
|
|
107
|
+
return options.explicit;
|
|
108
|
+
const env = options.env ?? process.env;
|
|
109
|
+
const raw = env.SITEFILE_REGISTRY_AUTH;
|
|
110
|
+
if (raw !== undefined && raw !== '') {
|
|
111
|
+
let j;
|
|
112
|
+
try {
|
|
113
|
+
j = JSON.parse(raw);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
throw new SitefileError('E_AUTH', 'SITEFILE_REGISTRY_AUTH must be a JSON object mapping registry hosts to "user:password"');
|
|
117
|
+
}
|
|
118
|
+
if (!isRecord(j))
|
|
119
|
+
throw new SitefileError('E_AUTH', 'SITEFILE_REGISTRY_AUTH must be a JSON object mapping registry hosts to "user:password"');
|
|
120
|
+
for (const [k, v] of Object.entries(j)) {
|
|
121
|
+
if (matches(k, registry)) {
|
|
122
|
+
const c = fromEntry(v);
|
|
123
|
+
if (c)
|
|
124
|
+
return c;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const cfg = await readJson(dockerConfigPath(env));
|
|
129
|
+
if (!isRecord(cfg))
|
|
130
|
+
return undefined;
|
|
131
|
+
const timeout = options.helperTimeoutMs ?? 15_000;
|
|
132
|
+
const serverUrl = hostKey(registry) === 'docker.io' ? 'https://index.docker.io/v1/' : hostKey(registry);
|
|
133
|
+
const helpers = isRecord(cfg.credHelpers) ? cfg.credHelpers : {};
|
|
134
|
+
for (const [k, v] of Object.entries(helpers)) {
|
|
135
|
+
if (matches(k, registry) && str(v))
|
|
136
|
+
return runCredentialHelper(str(v), serverUrl, env, timeout);
|
|
137
|
+
}
|
|
138
|
+
const auths = isRecord(cfg.auths) ? cfg.auths : {};
|
|
139
|
+
for (const [k, v] of Object.entries(auths)) {
|
|
140
|
+
if (matches(k, registry)) {
|
|
141
|
+
const c = fromEntry(v);
|
|
142
|
+
if (c)
|
|
143
|
+
return c;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const store = str(cfg.credsStore);
|
|
147
|
+
if (store)
|
|
148
|
+
return runCredentialHelper(store, serverUrl, env, timeout);
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=registry-auth.js.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type ImageRef } from './ref.js';
|
|
2
|
+
import type { RegistryClient } from './registry.js';
|
|
3
|
+
import type { CacheEntry, RemoteCache } from './plugins/cache.js';
|
|
4
|
+
/**
|
|
5
|
+
* TRANSFORM cache shared through an OCI registry (`build --cache-from <ref>` / `--cache-to <ref>`,
|
|
6
|
+
* docs/CLI.md "Registry cache"). The cache is one OCI artifact:
|
|
7
|
+
*
|
|
8
|
+
* manifest application/vnd.oci.image.manifest.v1+json, artifactType CACHE_ARTIFACT_TYPE,
|
|
9
|
+
* config = the empty descriptor ({}), layers = [index, ...result blobs by digest]
|
|
10
|
+
* index CACHE_INDEX_MEDIA: canonical JSON {"entries":{"<key>":{"files":[[path,hex,size]],"kind":k}},"version":1}
|
|
11
|
+
* blobs CACHE_BLOB_MEDIA: result bytes, exactly as in the local cache's blobs/ directory
|
|
12
|
+
*
|
|
13
|
+
* `--cache-from` reads the manifest and the index before the build; a result's blobs are fetched
|
|
14
|
+
* only on a hit (a local miss whose key is in the index), verified, and written to the local cache.
|
|
15
|
+
* `--cache-to` pushes, after a successful build, the entries this build used or wrote; blobs the
|
|
16
|
+
* registry already has are not uploaded again. A registry problem is a warning, never a build failure.
|
|
17
|
+
* Pushed bytes are deterministic and hold no local paths or secrets: keys are SHA-256 hashes, entries
|
|
18
|
+
* name output-relative paths, digests and sizes only.
|
|
19
|
+
*
|
|
20
|
+
* Kept byte-for-byte in step with rust/src/registry_cache.rs.
|
|
21
|
+
*/
|
|
22
|
+
export declare const CACHE_ARTIFACT_TYPE = "application/vnd.sitefile.cache.v1+json";
|
|
23
|
+
export declare const CACHE_INDEX_MEDIA = "application/vnd.sitefile.cache.index.v1+json";
|
|
24
|
+
export declare const CACHE_BLOB_MEDIA = "application/vnd.sitefile.cache.blob.v1";
|
|
25
|
+
export declare const EMPTY_MEDIA = "application/vnd.oci.empty.v1+json";
|
|
26
|
+
/** Largest cache index accepted. */
|
|
27
|
+
export declare const MAX_INDEX_BYTES: number;
|
|
28
|
+
export interface CacheRef {
|
|
29
|
+
/** As written on the command line (used in messages). */
|
|
30
|
+
readonly input: string;
|
|
31
|
+
readonly ref: ImageRef;
|
|
32
|
+
}
|
|
33
|
+
/** Validate a `--cache-from` / `--cache-to` reference (E_USAGE when it is not usable). */
|
|
34
|
+
export declare function parseCacheRef(flag: '--cache-from' | '--cache-to', input: string): CacheRef;
|
|
35
|
+
export interface RegistryCacheOptions {
|
|
36
|
+
readonly from: readonly CacheRef[];
|
|
37
|
+
readonly to?: CacheRef | undefined;
|
|
38
|
+
/** A client for a canonical registry name (auth, TLS and network policy are the client's). */
|
|
39
|
+
readonly client: (registry: string) => RegistryClient;
|
|
40
|
+
/** $SITEFILE_CACHE_MAX in bytes (0: no limit): a build never fetches more than this. */
|
|
41
|
+
readonly limit: number;
|
|
42
|
+
readonly warn: (message: string) => void;
|
|
43
|
+
readonly notice: (message: string) => void;
|
|
44
|
+
}
|
|
45
|
+
/** Parse and validate an index; every blob it names must be a layer of the manifest with that size. */
|
|
46
|
+
export declare function parseCacheIndex(bytes: Uint8Array, blobs: ReadonlyMap<string, number>): Map<string, CacheEntry>;
|
|
47
|
+
/** The index bytes for a set of entries (canonical JSON, keys sorted). */
|
|
48
|
+
export declare function encodeCacheIndex(entries: ReadonlyMap<string, CacheEntry>): Buffer;
|
|
49
|
+
/** The artifact manifest for an index and the result blobs (`[hex, size]`, any order). */
|
|
50
|
+
export declare function encodeCacheManifest(index: Uint8Array, blobs: ReadonlyArray<readonly [string, number]>): Buffer;
|
|
51
|
+
export declare class RegistryCache implements RemoteCache {
|
|
52
|
+
private readonly o;
|
|
53
|
+
/** Entries this build used or wrote (what `--cache-to` pushes). */
|
|
54
|
+
readonly usedEntries: Map<string, CacheEntry>;
|
|
55
|
+
/** Results fetched from `--cache-from` registries, and their blob bytes downloaded. */
|
|
56
|
+
fetched: number;
|
|
57
|
+
fetchedBytes: number;
|
|
58
|
+
private readonly sources;
|
|
59
|
+
private stopped;
|
|
60
|
+
constructor(o: RegistryCacheOptions);
|
|
61
|
+
/** Read every `--cache-from` manifest and index (in order). Problems are warnings. */
|
|
62
|
+
load(): Promise<void>;
|
|
63
|
+
private loadOne;
|
|
64
|
+
used(key: string, entry: CacheEntry): void;
|
|
65
|
+
fetch(key: string, blobPath: (hex: string) => string): Promise<CacheEntry | undefined>;
|
|
66
|
+
/** Stream a result blob into the local cache: verified size and digest, temp file + rename. */
|
|
67
|
+
private download;
|
|
68
|
+
/** Push the used entries to `--cache-to` (after a successful build). Problems are warnings. */
|
|
69
|
+
push(cacheDir: string): Promise<void>;
|
|
70
|
+
private pushTo;
|
|
71
|
+
}
|