@izykitten/ce 0.0.3-dev.0 → 0.0.3-dev.2
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 +20 -3
- package/dist/ce.js +139 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ Tags and the `TAG_PREFIX`/`CACHE_PREFIX` values can use placeholders, filled fro
|
|
|
38
38
|
| `{image}` | registry host and repository, lowercased, e.g. `code.izy.sh/library/gateway` |
|
|
39
39
|
| `{ref}` | the branch name, `pr-<number>` for a pull request, or the tag name |
|
|
40
40
|
| `{branch}`, `{pr}`, `{tag}` | the branch, PR number or tag, set only when building that kind of ref |
|
|
41
|
-
| `{default}` | set only when building the default branch, never for a pull request |
|
|
41
|
+
| `{default}` | set only when building the default branch, never for a pull request or tag |
|
|
42
42
|
| `{revision}`, `{sha}` | the commit, in full and as its first 7 characters |
|
|
43
43
|
| `{version}`, `{major}`, `{minor}`, `{patch}` | from a SemVer tag such as `v1.2.3` or `1.2.3-rc.1` |
|
|
44
44
|
| `{stable}` | set only for a SemVer tag without a pre-release |
|
|
@@ -48,7 +48,7 @@ Tags and the `TAG_PREFIX`/`CACHE_PREFIX` values can use placeholders, filled fro
|
|
|
48
48
|
|
|
49
49
|
Values are made safe for a tag: any ASCII character a tag cannot have becomes `-`, so branch `ci/modernize-build` gives `ci-modernize-build`; a non-ASCII character is an error. A value never adds a tag or changes the image name. Prefixes can use `{name}` but not guards.
|
|
50
50
|
|
|
51
|
-
Guards decide from the run's metadata, so they need it too: outside a Forgejo Actions run,
|
|
51
|
+
Guards decide from the run's metadata, so they need it too: outside a Forgejo Actions run, `{?default}` fails like `{default}` would. Only `{env.NAME}` works everywhere. To tell whether a branch is the default one, ce reads `repository.default_branch` from the event file, or `DEFAULT_BRANCH` when the event has none, as on a scheduled run; set `DEFAULT_BRANCH: main` in such a workflow. Pull requests and tags are never the default branch, so they need neither.
|
|
52
52
|
|
|
53
53
|
Put templates in single quotes: `?` is a glob character in zsh.
|
|
54
54
|
|
|
@@ -64,6 +64,12 @@ A branch, PR and default-branch build:
|
|
|
64
64
|
-t '{image}:{ref},{?default}latest,{?default}caddy-{env.CADDY_VERSION}' .
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
With `CACHE_PREFIX='{ref}'`, each branch and pull request gets a gha cache of its own, so a new one starts cold. With `CACHE_FROM_DEFAULT=true`, or `--cache-from-default` before the engine, each gha `--cache-to` is also read from its scope on the default branch: ce expands the cache prefix as it would be there, so PR 3 reads `pr-3` and `main` and writes only `pr-3`, and with `scope=js` it reads `main-js`. The default branch comes from the event file or `DEFAULT_BRANCH`, as for `{default}`. A prefix with `{sha}` or `{revision}` cannot be worked out for it, and is an error. This only picks what is read: whether it hits depends on what the default branch last wrote.
|
|
68
|
+
|
|
69
|
+
The same can be written by hand, since ce only prefixes a `--cache-from` whose scope matches a `--cache-to` and passes any other on unchanged: `--cache type=gha,mode=max --cache-from type=gha,scope=main`.
|
|
70
|
+
|
|
71
|
+
The `--cache-from` that `--cache` makes leaves out a gha `mode=`, which only applies to export, and a `--cache-from` given twice, as the default branch's own scope is on the default branch, is passed once.
|
|
72
|
+
|
|
67
73
|
A release on a SemVer tag, with the version aliases only for stable releases:
|
|
68
74
|
|
|
69
75
|
```sh
|
|
@@ -72,6 +78,15 @@ A release on a SemVer tag, with the version aliases only for stable releases:
|
|
|
72
78
|
|
|
73
79
|
`{stable}` does not mean newest: rebuilding an older release moves its aliases back to it.
|
|
74
80
|
|
|
81
|
+
### Checking a template
|
|
82
|
+
|
|
83
|
+
`--dry-run` before the engine, or `CE_DRY_RUN=true`, prints the build summary and the engine command ce would run, and exits 0 without running it or waiting for the engine. A template that fails still exits 2, so it also works as a check before pushing a workflow:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
GITHUB_REF=refs/tags/v1.2.3 GITHUB_SERVER_URL=https://code.example GITHUB_REPOSITORY=org/app \
|
|
87
|
+
ce --dry-run docker buildx build -t '{image}:{ref},{?stable}{major}.{minor}' .
|
|
88
|
+
```
|
|
89
|
+
|
|
75
90
|
### OCI labels
|
|
76
91
|
|
|
77
92
|
With `OCI_LABELS=true`, or `--oci-labels` before the engine (`ce --oci-labels docker buildx build …`, like the prefix options), builds get `org.opencontainers.image.source`, `revision`, `version`, `title` and `description` labels from the run. Labels the run cannot tell are left out. They override a Dockerfile `LABEL` with the same key, and a `--label` you pass wins over them. `created` is not set, so the labels do not change the image on every rebuild.
|
|
@@ -84,7 +99,7 @@ The package installs `docker-credential-ce-forgejo` next to `ce`. `ce` puts that
|
|
|
84
99
|
|
|
85
100
|
## Compatibility
|
|
86
101
|
|
|
87
|
-
These
|
|
102
|
+
These behave the same under Node and Bun, and [test/contract.test.ts](test/contract.test.ts) checks them by running the built package under both. A release that changes one says so here:
|
|
88
103
|
|
|
89
104
|
- commands, flags and the environment variables `login` reads;
|
|
90
105
|
- the arguments the engine receives after tag and cache rewriting, and its exit code, signals and PID (`ce` replaces itself with the engine);
|
|
@@ -94,6 +109,8 @@ These stay the same from release to release, and [test/contract.test.ts](test/co
|
|
|
94
109
|
|
|
95
110
|
The wording of messages is not part of that and may change.
|
|
96
111
|
|
|
112
|
+
In 0.0.3, the `--cache-from` that `--cache` makes no longer has a gha `mode=`, which only applies to export, and a `--cache-from` given twice is passed once.
|
|
113
|
+
|
|
97
114
|
Compared with the Python version:
|
|
98
115
|
|
|
99
116
|
- The state file is locked with `ce-forgejo-credentials.json.pidlock`, holding the holder's PID and hostname, rather than `flock`, which Node does not have. A lock whose holder died is broken right away if the PID is gone on the same host, otherwise after two minutes. It does not exclude a Python `ce` using the same Docker config at the same moment.
|
package/dist/ce.js
CHANGED
|
@@ -298,7 +298,7 @@ class CiMetadata {
|
|
|
298
298
|
const ref = this.#parsedRef();
|
|
299
299
|
if (ref.kind !== "branch")
|
|
300
300
|
return;
|
|
301
|
-
return ref.name === this
|
|
301
|
+
return ref.name === this.defaultBranch() ? ref.name : undefined;
|
|
302
302
|
}
|
|
303
303
|
case "revision":
|
|
304
304
|
return this.#require("SHA", `{${name}}`);
|
|
@@ -359,11 +359,12 @@ class CiMetadata {
|
|
|
359
359
|
const ref = this.#parsedRef();
|
|
360
360
|
return ref.kind === "tag" ? SEMVER.exec(ref.name) : null;
|
|
361
361
|
}
|
|
362
|
-
|
|
363
|
-
const repository = this.#payload().repository;
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
362
|
+
defaultBranch() {
|
|
363
|
+
const repository = this.#ciEnv("EVENT_PATH") ? this.#payload().repository : undefined;
|
|
364
|
+
const fromEvent = isObject(repository) ? repository.default_branch : undefined;
|
|
365
|
+
const branch = typeof fromEvent === "string" && fromEvent || nonEmpty(this.#env.DEFAULT_BRANCH);
|
|
366
|
+
if (!branch) {
|
|
367
|
+
throw new CeError("ce: {default} needs repository.default_branch in the event file or DEFAULT_BRANCH, and neither is set");
|
|
367
368
|
}
|
|
368
369
|
return branch;
|
|
369
370
|
}
|
|
@@ -383,6 +384,21 @@ class CiMetadata {
|
|
|
383
384
|
return payload;
|
|
384
385
|
}
|
|
385
386
|
}
|
|
387
|
+
var ON_DEFAULT_BRANCH = new Set(["ref", "branch", "default"]);
|
|
388
|
+
var UNKNOWN_ON_DEFAULT_BRANCH = new Set(["revision", "sha"]);
|
|
389
|
+
function onDefaultBranch(vars, branch) {
|
|
390
|
+
return {
|
|
391
|
+
known: (name) => vars.known(name),
|
|
392
|
+
get(name) {
|
|
393
|
+
if (ON_DEFAULT_BRANCH.has(name))
|
|
394
|
+
return branch;
|
|
395
|
+
if (UNKNOWN_ON_DEFAULT_BRANCH.has(name)) {
|
|
396
|
+
throw new CeError(`ce: the default branch's cache scope cannot use {${name}}: this run does not know its commit`);
|
|
397
|
+
}
|
|
398
|
+
return name === "image" || name.startsWith("env.") ? vars.get(name) : undefined;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
}
|
|
386
402
|
|
|
387
403
|
// src/system.ts
|
|
388
404
|
import { spawnSync } from "node:child_process";
|
|
@@ -505,6 +521,11 @@ function checkValue(template, vars, what) {
|
|
|
505
521
|
}
|
|
506
522
|
function expandValue(template, vars, encode, what) {
|
|
507
523
|
checkValue(template, vars, what);
|
|
524
|
+
for (const part of parse(template, vars)) {
|
|
525
|
+
if ("name" in part && vars.get(part.name) === undefined) {
|
|
526
|
+
throw new CeError(`ce: {${part.name}} has no value here, in the ${what} ${JSON.stringify(template)}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
508
529
|
return expand(template, vars, encode);
|
|
509
530
|
}
|
|
510
531
|
|
|
@@ -658,6 +679,7 @@ function ghaFields(value) {
|
|
|
658
679
|
const type = fields?.findLast((field) => fieldKey(field) === "type");
|
|
659
680
|
return type?.slice("type=".length) === "gha" ? fields : null;
|
|
660
681
|
}
|
|
682
|
+
var isGhaCache = (value) => ghaFields(value) !== null;
|
|
661
683
|
var scopeIndex = (fields) => fields.findLastIndex((field) => fieldKey(field) === "scope");
|
|
662
684
|
function cacheScopeName(value) {
|
|
663
685
|
const fields = ghaFields(value);
|
|
@@ -691,6 +713,12 @@ function normalizeCacheScope(value, cachePrefix) {
|
|
|
691
713
|
fields[i] = `scope=${scope}`;
|
|
692
714
|
return csvJoin(fields);
|
|
693
715
|
}
|
|
716
|
+
function withoutCacheMode(value) {
|
|
717
|
+
const fields = ghaFields(value);
|
|
718
|
+
if (!fields?.some((field) => fieldKey(field) === "mode"))
|
|
719
|
+
return value;
|
|
720
|
+
return csvJoin(fields.filter((field) => fieldKey(field) !== "mode"));
|
|
721
|
+
}
|
|
694
722
|
|
|
695
723
|
// src/build-args.ts
|
|
696
724
|
function consumeValue(argv, flag) {
|
|
@@ -710,7 +738,7 @@ var SUMMARIZED = {
|
|
|
710
738
|
"--build-arg": "buildArgs",
|
|
711
739
|
"--label": "labels"
|
|
712
740
|
};
|
|
713
|
-
function parseBuildArgs(argv, tagPrefix, cachePrefix, { vars = new CiMetadata, labels = {} } = {}) {
|
|
741
|
+
function parseBuildArgs(argv, tagPrefix, cachePrefix, { vars = new CiMetadata, labels = {}, defaultBranch } = {}) {
|
|
714
742
|
const args = [];
|
|
715
743
|
const rawCacheFrom = [];
|
|
716
744
|
const rawCacheTo = [];
|
|
@@ -764,7 +792,7 @@ function parseBuildArgs(argv, tagPrefix, cachePrefix, { vars = new CiMetadata, l
|
|
|
764
792
|
rawCacheTo.push(attached ?? next());
|
|
765
793
|
else if (name === "--cache") {
|
|
766
794
|
const v = attached ?? next();
|
|
767
|
-
rawCacheFrom.push(v);
|
|
795
|
+
rawCacheFrom.push(withoutCacheMode(v));
|
|
768
796
|
rawCacheTo.push(v);
|
|
769
797
|
} else if (Object.hasOwn(SUMMARIZED, name)) {
|
|
770
798
|
const flag = name;
|
|
@@ -792,11 +820,17 @@ function parseBuildArgs(argv, tagPrefix, cachePrefix, { vars = new CiMetadata, l
|
|
|
792
820
|
const added = Object.entries(labels).filter(([key]) => !ownKeys.has(key)).map(([key, value]) => `${key}=${value}`);
|
|
793
821
|
args.unshift(...added.map((label) => `--label=${label}`));
|
|
794
822
|
summary.labels.unshift(...added);
|
|
823
|
+
const prefixTemplate = cachePrefix;
|
|
795
824
|
if (rawCacheFrom.length || rawCacheTo.length)
|
|
796
825
|
cachePrefix = expandPrefix(cachePrefix, vars, "cache prefix");
|
|
797
826
|
const toNames = new Set(rawCacheTo.map(cacheScopeName));
|
|
798
|
-
|
|
799
|
-
|
|
827
|
+
const cacheFrom = rawCacheFrom.map((c) => toNames.has(cacheScopeName(c)) ? normalizeCacheScope(c, cachePrefix) : c);
|
|
828
|
+
const ghaTo = rawCacheTo.filter(isGhaCache);
|
|
829
|
+
if (defaultBranch && ghaTo.length) {
|
|
830
|
+
const prefix = expandPrefix(prefixTemplate, onDefaultBranch(vars, defaultBranch()), "cache prefix");
|
|
831
|
+
cacheFrom.push(...ghaTo.map((c) => normalizeCacheScope(withoutCacheMode(c), prefix)));
|
|
832
|
+
}
|
|
833
|
+
for (const rewritten of new Set(cacheFrom)) {
|
|
800
834
|
args.push(`--cache-from=${rewritten}`);
|
|
801
835
|
summary.cacheFrom.push(rewritten);
|
|
802
836
|
}
|
|
@@ -1163,17 +1197,46 @@ function envFlag(name) {
|
|
|
1163
1197
|
return true;
|
|
1164
1198
|
throw new CeError(`ce: ${name} must be true or false, not ${JSON.stringify(process.env[name])}`);
|
|
1165
1199
|
}
|
|
1166
|
-
|
|
1200
|
+
function shellQuote(arg) {
|
|
1201
|
+
return /^[A-Za-z0-9_@%+:,./-][A-Za-z0-9_@%+=:,./-]*$/u.test(arg) ? arg : `'${arg.replaceAll("'", `'\\''`)}'`;
|
|
1202
|
+
}
|
|
1203
|
+
function engineCommand(engine, args) {
|
|
1204
|
+
if (engine === "docker" || !system.which("gosu"))
|
|
1205
|
+
return [engine, ...args];
|
|
1206
|
+
const user = process.env.USER ?? "";
|
|
1207
|
+
if (!user) {
|
|
1208
|
+
logError("ce: gosu is available but USER is not set; cannot drop privileges. Set USER or remove gosu from PATH.");
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
return ["gosu", user, engine, ...args];
|
|
1212
|
+
}
|
|
1213
|
+
async function runEngine(engine, argv, tagPrefix, cachePrefix, options = {}) {
|
|
1214
|
+
const dryRun = options.dryRun ?? envFlag("CE_DRY_RUN");
|
|
1167
1215
|
const command = findBuildCommand(engine, argv);
|
|
1168
1216
|
let args = argv;
|
|
1169
1217
|
let summary;
|
|
1170
1218
|
if (command) {
|
|
1171
1219
|
const vars = new CiMetadata;
|
|
1172
|
-
const labels = !command.tagsOnly && (ociLabels ?? envFlag("OCI_LABELS")) ? vars.labels() : {};
|
|
1173
|
-
const
|
|
1220
|
+
const labels = !command.tagsOnly && (options.ociLabels ?? envFlag("OCI_LABELS")) ? vars.labels() : {};
|
|
1221
|
+
const fromDefault = !command.tagsOnly && (options.cacheFromDefault ?? envFlag("CACHE_FROM_DEFAULT"));
|
|
1222
|
+
const parsed = parseBuildArgs(argv.slice(command.start), tagPrefix, cachePrefix, {
|
|
1223
|
+
vars,
|
|
1224
|
+
labels,
|
|
1225
|
+
defaultBranch: fromDefault ? () => vars.defaultBranch() : undefined
|
|
1226
|
+
});
|
|
1174
1227
|
args = [...argv.slice(0, command.start), ...parsed.args];
|
|
1175
1228
|
summary = parsed.summary;
|
|
1176
1229
|
}
|
|
1230
|
+
if (dryRun) {
|
|
1231
|
+
const run = engineCommand(engine, args);
|
|
1232
|
+
if (!run)
|
|
1233
|
+
return 1;
|
|
1234
|
+
if (summary)
|
|
1235
|
+
printBuildSummary(engine, args, summary);
|
|
1236
|
+
io.stdout(`${run.map(shellQuote).join(" ")}
|
|
1237
|
+
`);
|
|
1238
|
+
return 0;
|
|
1239
|
+
}
|
|
1177
1240
|
ensureHelperOnPath();
|
|
1178
1241
|
if (!system.which(engine)) {
|
|
1179
1242
|
logError(`Engine not found: ${engine}`);
|
|
@@ -1183,17 +1246,8 @@ async function runEngine(engine, argv, tagPrefix, cachePrefix, ociLabels) {
|
|
|
1183
1246
|
return 1;
|
|
1184
1247
|
if (summary)
|
|
1185
1248
|
printBuildSummary(engine, args, summary);
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
if (system.which("gosu")) {
|
|
1189
|
-
const user = process.env.USER ?? "";
|
|
1190
|
-
if (!user) {
|
|
1191
|
-
logError("ce: gosu is available but USER is not set; cannot drop privileges. Set USER or remove gosu from PATH.");
|
|
1192
|
-
return 1;
|
|
1193
|
-
}
|
|
1194
|
-
return system.exec("gosu", ["gosu", user, engine, ...args]);
|
|
1195
|
-
}
|
|
1196
|
-
return system.exec(engine, [engine, ...args]);
|
|
1249
|
+
const run = engineCommand(engine, args);
|
|
1250
|
+
return run ? system.exec(run[0], run) : 1;
|
|
1197
1251
|
}
|
|
1198
1252
|
|
|
1199
1253
|
// src/login.ts
|
|
@@ -1203,14 +1257,18 @@ function registryGroups() {
|
|
|
1203
1257
|
const suffixed = Object.keys(process.env).filter((name) => name.startsWith(prefix) && name.length > prefix.length).sort().map((name) => name.slice(prefix.length));
|
|
1204
1258
|
return [...process.env.REGISTRY_HOST ? [""] : [], ...suffixed];
|
|
1205
1259
|
}
|
|
1260
|
+
function unsetOrEmpty(name) {
|
|
1261
|
+
return `${name} is ${process.env[name] === undefined ? "unset" : "empty"}`;
|
|
1262
|
+
}
|
|
1206
1263
|
function oidcConfig() {
|
|
1207
1264
|
const audience = process.env.AUD ?? "";
|
|
1208
1265
|
if (!audience)
|
|
1209
1266
|
return null;
|
|
1210
1267
|
const requestUrl = process.env.ACTIONS_ID_TOKEN_REQUEST_URL ?? "";
|
|
1211
1268
|
const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN ?? "";
|
|
1212
|
-
|
|
1213
|
-
|
|
1269
|
+
const missing = ["ACTIONS_ID_TOKEN_REQUEST_URL", "ACTIONS_ID_TOKEN_REQUEST_TOKEN"].filter((name) => !process.env[name]);
|
|
1270
|
+
if (missing.length) {
|
|
1271
|
+
return `Forgejo OIDC login config is incomplete: AUD is set, but ${missing.map(unsetOrEmpty).join("; ")}`;
|
|
1214
1272
|
}
|
|
1215
1273
|
try {
|
|
1216
1274
|
assertAbsoluteHttpUrl(requestUrl, "must be an absolute HTTP(S) URL");
|
|
@@ -1257,36 +1315,46 @@ async function login() {
|
|
|
1257
1315
|
let failed = false;
|
|
1258
1316
|
for (const group of groups) {
|
|
1259
1317
|
const suffix = group ? `__${group}` : "";
|
|
1260
|
-
const
|
|
1261
|
-
const
|
|
1262
|
-
const
|
|
1318
|
+
const hostName = `REGISTRY_HOST${suffix}`;
|
|
1319
|
+
const userName = `REGISTRY_USER${suffix}`;
|
|
1320
|
+
const passwordName = `REGISTRY_PASSWORD${suffix}`;
|
|
1321
|
+
const host = process.env[hostName] ?? "";
|
|
1322
|
+
const user = process.env[userName] ?? "";
|
|
1323
|
+
const password = process.env[passwordName] ?? "";
|
|
1263
1324
|
const label = group || "default";
|
|
1264
|
-
const
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
failed = true;
|
|
1268
|
-
continue;
|
|
1269
|
-
}
|
|
1270
|
-
for (const rawRegistry of host.split(/\s+/u).filter(Boolean)) {
|
|
1271
|
-
let authority;
|
|
1272
|
-
let registry;
|
|
1325
|
+
const hosts = host.split(/\s+/u).filter(Boolean);
|
|
1326
|
+
const registries = [];
|
|
1327
|
+
for (const rawRegistry of hosts) {
|
|
1273
1328
|
try {
|
|
1274
|
-
authority = urlAuthority(rawRegistry);
|
|
1275
|
-
registry = registryServer(rawRegistry);
|
|
1329
|
+
const authority = urlAuthority(rawRegistry);
|
|
1330
|
+
const registry = registryServer(rawRegistry);
|
|
1331
|
+
registries.push({ registry, useOidc: oidc !== null && sameAuthority(authority, oidc.authority) });
|
|
1276
1332
|
} catch (exc) {
|
|
1277
1333
|
if (!(exc instanceof CeError))
|
|
1278
1334
|
throw exc;
|
|
1279
|
-
logError(`Invalid registry host for '${label}' (${rawRegistry}): ${exc.message}`);
|
|
1335
|
+
logError(rawRegistry.includes("@") ? `Invalid registry host for '${label}' in ${hostName}, not shown as it has an "@" and may hold a password` : `Invalid registry host for '${label}' (${rawRegistry}): ${exc.message}`);
|
|
1280
1336
|
failed = true;
|
|
1281
|
-
continue;
|
|
1282
|
-
}
|
|
1283
|
-
const useOidc = oidc !== null && sameAuthority(authority, oidc.authority);
|
|
1284
|
-
if (!useOidc && !password) {
|
|
1285
|
-
logError(incomplete);
|
|
1286
|
-
failed = true;
|
|
1287
|
-
continue;
|
|
1288
1337
|
}
|
|
1289
|
-
|
|
1338
|
+
}
|
|
1339
|
+
const missing = [];
|
|
1340
|
+
if (!host)
|
|
1341
|
+
missing.push(unsetOrEmpty(hostName));
|
|
1342
|
+
else if (!hosts.length)
|
|
1343
|
+
missing.push(`${hostName} has no registry in it`);
|
|
1344
|
+
if (!user)
|
|
1345
|
+
missing.push(unsetOrEmpty(userName));
|
|
1346
|
+
const needPassword = registries.filter((r) => !r.useOidc).map((r) => r.registry);
|
|
1347
|
+
if (!password && needPassword.length)
|
|
1348
|
+
missing.push(`${unsetOrEmpty(passwordName)}, which ${needPassword.join(", ")} needs`);
|
|
1349
|
+
if (missing.length) {
|
|
1350
|
+
logError(`Login config for '${label}' is incomplete: ${missing.join("; ")}`);
|
|
1351
|
+
failed = true;
|
|
1352
|
+
}
|
|
1353
|
+
if (!user)
|
|
1354
|
+
continue;
|
|
1355
|
+
for (const { registry, useOidc } of registries) {
|
|
1356
|
+
if (useOidc || password)
|
|
1357
|
+
targets.push({ registry, label, user, password, useOidc });
|
|
1290
1358
|
}
|
|
1291
1359
|
}
|
|
1292
1360
|
let jwt = null;
|
|
@@ -1332,7 +1400,8 @@ async function login() {
|
|
|
1332
1400
|
|
|
1333
1401
|
// src/cli.ts
|
|
1334
1402
|
function helpText() {
|
|
1335
|
-
return `Usage: ${io.argv0()} [--tag-prefix=<prefix>] [--cache-prefix=<prefix>] [--oci-labels]
|
|
1403
|
+
return `Usage: ${io.argv0()} [--tag-prefix=<prefix>] [--cache-prefix=<prefix>] [--oci-labels]
|
|
1404
|
+
[--cache-from-default] [--dry-run] [command|engine] [args]
|
|
1336
1405
|
|
|
1337
1406
|
Options:
|
|
1338
1407
|
--tag-prefix=<prefix>
|
|
@@ -1348,10 +1417,21 @@ Options:
|
|
|
1348
1417
|
Also available via CACHE_PREFIX environment variable.
|
|
1349
1418
|
--cache=<spec> Shorthand for passing <spec> to both --cache-from and
|
|
1350
1419
|
--cache-to. Supports the same forms as --cache-from/--cache-to.
|
|
1420
|
+
A gha cache-from made this way leaves out mode=, which
|
|
1421
|
+
only applies to export. The same cache-from given twice
|
|
1422
|
+
is passed once.
|
|
1423
|
+
--cache-from-default
|
|
1424
|
+
Also read each gha --cache-to from its scope on the
|
|
1425
|
+
default branch, from the cache prefix as it is there,
|
|
1426
|
+
so a new branch or PR starts from its cache.
|
|
1427
|
+
Also available via CACHE_FROM_DEFAULT=true.
|
|
1351
1428
|
--oci-labels Add org.opencontainers.image.* labels (source, revision,
|
|
1352
1429
|
version, title, description) from the Forgejo Actions
|
|
1353
1430
|
run to builds. A --label with the same key wins.
|
|
1354
1431
|
Also available via OCI_LABELS=true.
|
|
1432
|
+
--dry-run Print the build summary and the engine command, and
|
|
1433
|
+
exit without running it. Template errors still exit 2.
|
|
1434
|
+
Also available via CE_DRY_RUN=true.
|
|
1355
1435
|
|
|
1356
1436
|
Placeholders, in build tags and in the tag and cache prefixes:
|
|
1357
1437
|
{name} The value of name; an error if it has none in this run.
|
|
@@ -1361,7 +1441,8 @@ Placeholders, in build tags and in the tag and cache prefixes:
|
|
|
1361
1441
|
{ref} Branch name, pr-<number>, or tag name
|
|
1362
1442
|
{branch} {pr} {tag}
|
|
1363
1443
|
Set only when building that kind of ref
|
|
1364
|
-
{default} Set only when building the default branch
|
|
1444
|
+
{default} Set only when building the default branch, named by
|
|
1445
|
+
the event file or else DEFAULT_BRANCH
|
|
1365
1446
|
{revision} {sha} The commit, in full and as 7 characters
|
|
1366
1447
|
{version} {major} {minor} {patch}
|
|
1367
1448
|
From a SemVer tag such as v1.2.3 (v optional)
|
|
@@ -1407,7 +1488,7 @@ Environment variables (used by login):
|
|
|
1407
1488
|
`;
|
|
1408
1489
|
}
|
|
1409
1490
|
async function main(argv) {
|
|
1410
|
-
|
|
1491
|
+
const options = {};
|
|
1411
1492
|
let tagPrefix = process.env.TAG_PREFIX ?? "";
|
|
1412
1493
|
let cachePrefix = process.env.CACHE_PREFIX ?? process.env.TAG_PREFIX ?? "";
|
|
1413
1494
|
for (;; ) {
|
|
@@ -1421,7 +1502,11 @@ async function main(argv) {
|
|
|
1421
1502
|
else if (a?.startsWith("--cache-prefix="))
|
|
1422
1503
|
[cachePrefix, argv] = [a.slice("--cache-prefix=".length), argv.slice(1)];
|
|
1423
1504
|
else if (a === "--oci-labels")
|
|
1424
|
-
[ociLabels, argv] = [true, argv.slice(1)];
|
|
1505
|
+
[options.ociLabels, argv] = [true, argv.slice(1)];
|
|
1506
|
+
else if (a === "--cache-from-default")
|
|
1507
|
+
[options.cacheFromDefault, argv] = [true, argv.slice(1)];
|
|
1508
|
+
else if (a === "--dry-run")
|
|
1509
|
+
[options.dryRun, argv] = [true, argv.slice(1)];
|
|
1425
1510
|
else
|
|
1426
1511
|
break;
|
|
1427
1512
|
}
|
|
@@ -1435,7 +1520,7 @@ async function main(argv) {
|
|
|
1435
1520
|
if (command === "credential-helper")
|
|
1436
1521
|
return credentialHelper(rest);
|
|
1437
1522
|
if (isEngine(command))
|
|
1438
|
-
return runEngine(command, rest, tagPrefix, cachePrefix,
|
|
1523
|
+
return runEngine(command, rest, tagPrefix, cachePrefix, options);
|
|
1439
1524
|
io.stderr(helpText());
|
|
1440
1525
|
return 1;
|
|
1441
1526
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@izykitten/ce",
|
|
3
|
-
"version": "0.0.3-dev.
|
|
3
|
+
"version": "0.0.3-dev.2",
|
|
4
4
|
"description": "Container engine wrapper: tag/cache rewriting for docker, podman and buildah, registry login, and a Forgejo OIDC Docker credential helper",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BSD-3-Clause",
|