@izykitten/ce 0.0.3-dev.6 → 0.0.3-dev.8
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 +151 -17
- package/dist/ce.js +2856 -1973
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
# @izykitten/ce
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CI wrapper for `docker`, `podman` and `buildah` builds and npm/Bun publishing: registry login (including Forgejo OIDC), container tag and cache rewriting, staged package releases, and a Docker credential helper that refreshes the Forgejo token.
|
|
4
4
|
|
|
5
5
|
Started as a port of the Python `ce` script from the `code.izy.sh/library/runner` image, with the same commands, flags, environment variables and exit codes, so a workflow only has to swap `ce` for `npx` or `bunx`. It runs on Node (22.15 or later) and Bun. Any job with Node or Bun and an engine CLI can use it; it no longer needs the runner image.
|
|
6
6
|
|
|
7
7
|
## Usage
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npx @izykitten/ce login
|
|
10
|
+
npx @izykitten/ce docker login
|
|
11
11
|
npx @izykitten/ce docker buildx build --push --cache type=gha,mode=max -t "$SERVER/$REPO:$REF,latest" .
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
`bunx @izykitten/ce …` works the same. The scripts run under `node`; `bun` runs them too when invoked as `bun node_modules/.bin/ce …` or through `bunx --bun`. When installed as a dependency, call it as `ce` (`npx ce …`). Run `ce` with no arguments for the full help, including every environment variable `login` reads.
|
|
15
15
|
|
|
16
|
+
Ce options immediately follow the tool, before its native arguments:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
ce docker --tag-prefix ci --dry-run buildx build -t 'registry.example/app:{ref}' .
|
|
20
|
+
ce npm --stage publish ./package.tgz
|
|
21
|
+
ce bun publish --tag next
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Options are scoped to their tool: container options apply to Docker, Podman and Buildah; publishing options apply to npm and Bun; `--dry-run` is shared. Ce stops consuming its options at the first native argument, or at an explicit `--`. For example, `ce docker --tag-prefix ci --context remote build .` passes `--context remote build .` to Docker. Existing options before the tool remain supported; a scalar option after the tool overrides one before it. Native arguments after the command retain their meaning: `ce npm --dry-run publish` only prints ce's command, while `ce npm publish --dry-run` runs npm's own dry run and its applicable lifecycle scripts.
|
|
25
|
+
|
|
16
26
|
In a Forgejo workflow:
|
|
17
27
|
|
|
18
28
|
```yaml
|
|
@@ -22,12 +32,35 @@ In a Forgejo workflow:
|
|
|
22
32
|
REGISTRY_USER: ${{ forgejo.actor }}
|
|
23
33
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_TOKEN }}
|
|
24
34
|
REGISTRY_HOST: ${{ forgejo.server_url }}
|
|
25
|
-
run: bunx @izykitten/ce login
|
|
35
|
+
run: bunx @izykitten/ce docker login
|
|
26
36
|
- name: Container Build & Push
|
|
27
|
-
run:
|
|
37
|
+
run: >
|
|
38
|
+
bunx @izykitten/ce docker buildx build --push
|
|
39
|
+
-t '{image}:{ref},{?version}{version},{?stable}{major}.{minor},{?stable}{major},{?stable}latest' .
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
[Recommended tags](#recommended-tags) says which tags that gives, and how to pick what `latest` follows.
|
|
43
|
+
|
|
44
|
+
For container engines, build commands are rewritten: `build`, `buildx build` and `image build` for Docker and Podman, `build` and `bud` for Buildah, and the tags of `docker buildx imagetools create`. Bare `<engine> login` uses ce's configured login; other commands, such as `docker run -t`, reach the engine unchanged.
|
|
45
|
+
|
|
46
|
+
## npm and Bun publishing
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
ce npm publish # direct publication with npm
|
|
50
|
+
ce npm --stage publish # npm staging, awaiting maintainer approval
|
|
51
|
+
ce bun publish # direct publication with Bun
|
|
52
|
+
ce bun --stage publish ./package.tgz # npm staging; no Bun executable is needed
|
|
28
53
|
```
|
|
29
54
|
|
|
30
|
-
|
|
55
|
+
Staging always runs [`npm stage publish`](https://docs.npmjs.com/cli/v12/commands/npm-stage), using npm's configuration and lifecycle scripts. It requires an npm version with that command (CI uses 12.2.1). Direct publication uses the requested client and its existing authentication. Ce neither probes token permissions nor retries a failed publish through another mode. The CI workflow always selects staging; direct publication remains an explicit publishing command outside that workflow.
|
|
56
|
+
|
|
57
|
+
Without an explicit tag, ce reads the selected package's version and infers the first prerelease label: `1.2.3-dev.8` gives `dev`, `1.2.3-rc.1` gives `rc`. Stable versions receive no injected tag, preserving the client's normal defaults and npm's check against implicitly moving `latest` to an older version. Numeric labels, version-like labels and the prerelease label `latest` require an explicit `--tag`.
|
|
58
|
+
|
|
59
|
+
Native `--tag` arguments and `publishConfig.tag` take precedence. With npm as the backend, `npm_config_tag`/`NPM_CONFIG_TAG` also take precedence; Bun does not read those tag variables. Use `ce npm --no-auto-tag publish ...` to leave all tag selection to native configuration, including a `tag` in `.npmrc`. The ce `--stage` and `--auto-tag` options must precede native arguments; a misplaced `--stage` is rejected rather than passed to a client that could ignore it and publish directly.
|
|
60
|
+
|
|
61
|
+
Inference supports one local package directory or tarball; tarball metadata is read with the system `tar` command. Remote package specs, workspace publishing and native options whose value syntax ce does not know require `--tag` or `--no-auto-tag`. Bun-specific options such as `--cwd`, `--filter` and `--gzip-level` are rejected for Bun staging, because that mode runs npm; use npm-compatible arguments or run ce from the package directory. Other npm/Bun commands pass through unchanged.
|
|
62
|
+
|
|
63
|
+
Inference selects a channel, not a release ordering policy. Version-changing lifecycle hooks can change a directory package after ce reads it; publish a packed tarball or set `--tag` explicitly in that case. Native first-publication behavior also remains: [Bun documents that an initial package version receives `latest` in addition to its specified tag](https://bun.com/docs/pm/cli/publish#tag). `--stage` keeps approval separate from the upload.
|
|
31
64
|
|
|
32
65
|
## Tags from the Forgejo run
|
|
33
66
|
|
|
@@ -47,15 +80,30 @@ Tags and the `TAG_PREFIX`/`CACHE_PREFIX` values can use placeholders, filled fro
|
|
|
47
80
|
|
|
48
81
|
`{name}` must have a value (an empty variable has none): if it has none in this run, ce fails with exit 2 before anything runs. To leave a tag out instead, guard it with `{?name}`, which expands to nothing and keeps the tag only when `name` has a value. Every guard in a tag has to pass, and a tag's other values are only needed when it is kept. Names are checked in every tag, so a typo fails even in one a guard leaves out. If the guards leave out every tag, ce fails rather than build an untagged image.
|
|
49
82
|
|
|
83
|
+
Guards can also test absence, compare a value, or accept alternatives:
|
|
84
|
+
|
|
85
|
+
| Guard | Keeps the output when |
|
|
86
|
+
| ----- | --------------------- |
|
|
87
|
+
| `{!pr}` | this is not a pull request |
|
|
88
|
+
| `{?env.PUBLISH_DOCKERHUB=true}` | `PUBLISH_DOCKERHUB` is exactly `true` |
|
|
89
|
+
| `{!env.CHANNEL=internal}` | `CHANNEL` is absent or differs from `internal` |
|
|
90
|
+
| `{?branch\|tag}` | this is a branch or Git-tag build |
|
|
91
|
+
| `{!branch\|tag}` | neither `branch` nor `tag` has a value |
|
|
92
|
+
| `{?default\|env.PUBLISH=true}` | this is the default branch or `PUBLISH` is exactly `true` |
|
|
93
|
+
|
|
94
|
+
Comparisons are case-sensitive and use the raw value before tag encoding. The right side is a literal, with no placeholder expansion; it can contain commas, whitespace and `=`, but not braces or `|`. Empty values count as absent, so use `{!name}` to test absence. `{?env.PUBLISH}` still checks presence: the string `false` passes it. `!` negates the whole expression, including its alternatives. Adjacent guards require every condition to pass.
|
|
95
|
+
|
|
50
96
|
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.
|
|
51
97
|
|
|
52
|
-
Prefixes can use guards too. A guard that does not pass leaves the prefix empty, since a prefix cannot be left out, and the prefix's other values are then not needed. `CACHE_PREFIX='{?pr}pr-{pr}'` gives each pull request a cache of its own (`pr-3`) and leaves every other build sharing the unprefixed one; with `CACHE_FROM_DEFAULT`, a PR also reads that shared one, as the prefix is empty on the default branch.
|
|
98
|
+
Prefixes can use guards too. A guard that does not pass leaves the prefix empty, since a prefix cannot be left out, and the prefix's other values are then not needed. `CACHE_PREFIX='{?pr}pr-{pr}'` gives each pull request a cache of its own (`pr-3`) and leaves every other build sharing the unprefixed one; with `CACHE_FROM_DEFAULT`, a PR also reads that shared one, as the prefix is empty on the default branch. Guards before the image name apply to its whole `-t` argument; guards embedded inside the image name are an error.
|
|
53
99
|
|
|
54
100
|
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}` and `{arg.NAME}` work everywhere. A guard that does not pass decides on its own, though: a tag with one is left out, and a prefix with one is empty, even if another guard in it could not be worked out, so `{?default}{?env.X}` outside a run with `X` empty is simply left out. 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.
|
|
55
101
|
|
|
102
|
+
Negation does not turn unavailable metadata into an absent value: `{!pr}` outside a run is still an error. An OR condition succeeds if any alternative matches, even if another cannot be worked out; otherwise unavailable metadata is an error. Names in every alternative are checked, including those that are not needed to decide the result.
|
|
103
|
+
|
|
56
104
|
Quote a template in a workflow's `env:`: YAML reads `CACHE_PREFIX: {ref}` as a mapping, and `{?pr}pr-{pr}` not at all.
|
|
57
105
|
|
|
58
|
-
A branch, PR and default-branch build:
|
|
106
|
+
A branch, PR and default-branch build, for an image deployed from its default branch with no releases yet, so `latest` follows that branch:
|
|
59
107
|
|
|
60
108
|
```yaml
|
|
61
109
|
- name: Container Build & Push
|
|
@@ -67,12 +115,46 @@ A branch, PR and default-branch build:
|
|
|
67
115
|
-t '{image}:{ref},{?default}latest,{?default}caddy-{env.CADDY_VERSION}' .
|
|
68
116
|
```
|
|
69
117
|
|
|
70
|
-
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`
|
|
118
|
+
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` after the engine name and before its native arguments, 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 that needs `{sha}` or `{revision}` there cannot be worked out for it, and is an error; one whose guards leave it empty there, like `{?pr}{sha}`, does not need them. This only picks what is read: whether it hits depends on what the default branch last wrote.
|
|
71
119
|
|
|
72
120
|
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`.
|
|
73
121
|
|
|
74
122
|
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.
|
|
75
123
|
|
|
124
|
+
### Conditional destinations and shared tag lists
|
|
125
|
+
|
|
126
|
+
A guard before the image name controls every tag in that `-t`. This keeps PR images in your registry while publishing the same branch and release tags to Docker Hub:
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
run: |
|
|
130
|
+
npx -y @izykitten/ce docker buildx build --push \
|
|
131
|
+
--cache type=gha,mode=max \
|
|
132
|
+
-t '{image}:{ref},{?default}sysbox' \
|
|
133
|
+
-t '{!pr}docker.io/izykitten/forgejo-runner:{ref},{?default}sysbox' \
|
|
134
|
+
-f Dockerfile .
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The Docker Hub argument is omitted entirely on PRs, including `pull_request_target`. Otherwise its tag list is unchanged. Each remaining tag still applies its own guards, so only the default branch gets `sysbox`.
|
|
138
|
+
|
|
139
|
+
To share a tag list across destinations, define it once with `--tag-list NAME=TEMPLATES` immediately after the engine name and use `{tags.NAME}`:
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
run: |
|
|
143
|
+
npx -y @izykitten/ce docker \
|
|
144
|
+
--tag-list 'runner={ref},{?default}sysbox' \
|
|
145
|
+
buildx build --push \
|
|
146
|
+
--cache type=gha,mode=max \
|
|
147
|
+
-t '{image}:{tags.runner}' \
|
|
148
|
+
-t '{!pr}docker.io/izykitten/forgejo-runner:{tags.runner}' \
|
|
149
|
+
-f Dockerfile .
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Add `{?env.PUBLISH_DOCKERHUB=true}` beside `{!pr}` to also require an explicit publishing choice. Every destination can have its own conditions and literal tags around the shared list, such as `app:{tags.runner},debug`.
|
|
153
|
+
|
|
154
|
+
Names use letters, digits and `_`, starting with a letter or `_`; each name can be defined only once. The attached form, `--tag-list=runner=...`, also works. Lists are comma/whitespace-separated tag templates and can reference other lists, defined in any order. A `{tags.NAME}` reference must be a whole entry after the image's colon; it cannot appear in an image name, a prefix, a guard or part of another tag. Ordinary `{env.NAME}` and `{arg.NAME}` values stay scalar and cannot introduce tags. Tags are deduplicated in the order they first appear.
|
|
155
|
+
|
|
156
|
+
Referenced lists and all their placeholder names are checked even when a destination is skipped. Undefined lists, cycles and malformed templates fail with exit 2 before the engine runs. Values inside skipped tags are not required, and ce reads no configuration file for tag lists.
|
|
157
|
+
|
|
76
158
|
### Recommended tags
|
|
77
159
|
|
|
78
160
|
For an image others pull by version:
|
|
@@ -137,34 +219,71 @@ RUN xcaddy build v${CADDY_VERSION}
|
|
|
137
219
|
- **The file:** the `-f` file (with Docker, the last one), else the context's `Dockerfile` (or `dockerfile`); podman and buildah look for `Containerfile` first.
|
|
138
220
|
- **The stage:** the one being built, `--target`'s or else the last. Other stages do not matter, unless this one is built `FROM` one of them, which it then starts from.
|
|
139
221
|
- **The default:** the stage's own `ARG NAME=…`, the last one winning, or that of the stage it is built from; else the global `ARG NAME=…` before the first `FROM`, which a bare `ARG NAME` in the stage, or a `FROM` line, also gets. No default anywhere, or an empty one, is no value, and a guard leaves its tag out.
|
|
140
|
-
- **`$` in a default:** worked out as the engine does, from the `ARG`s, `--build-arg`s and `ENV`s before it, with `$NAME`, `${NAME}`, `${NAME:-x}` and `${NAME:+x}` (and with Docker, `${NAME-x}` and `${NAME+x}`).
|
|
141
|
-
- **Builtins** such as `TARGETPLATFORM`, and `SOURCE_DATE_EPOCH` and proxies such as `HTTP_PROXY`, which the engine may set itself, are the builder's, unless a `--build-arg` sets them. Docker also takes a builtin's own `ARG` default, and works out a `SOURCE_DATE_EPOCH` that is not a plain number such as `1700000000` itself.
|
|
142
|
-
- **podman and buildah** differ inside a stage: an `ENV`, the base image's too, wins over an `ARG`, so ce only works out a reference to a name an `ENV` there sets. A stage gets the `ARG`s of the stage its `FROM` names as written, so none through `FROM $BASE`. `TARGETPLATFORM` is always the platform they build.
|
|
222
|
+
- **`$` in a default:** worked out as the engine does, from the `ARG`s, `--build-arg`s and `ENV`s before it, with `$NAME`, `${NAME}`, `${NAME:-x}` and `${NAME:+x}` (and with Docker, `${NAME-x}` and `${NAME+x}`). In a stage from `scratch`, a name nothing sets is empty, and `PATH` is the engines' `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`, which with podman and buildah is an `ENV`, so it wins over an `ARG PATH`.
|
|
223
|
+
- **Builtins** such as `TARGETPLATFORM`, and `SOURCE_DATE_EPOCH` and proxies such as `HTTP_PROXY`, which the engine may set itself, are the builder's, unless a `--build-arg` sets them. podman and buildah pass their environment's proxies only to `RUN`, so with them a proxy is like any other name. With a `--platform` such as `linux/arm/v7`, the `TARGET` ones are that platform's, as the engine normalizes it: `linux/arm` is `linux/arm/v7`, `linux/aarch64` is `linux/arm64`, a bare architecture such as `arm64` is for Linux when ce runs on Linux, and with Docker `linux/arm64/v8` is `linux/arm64`. With several, in one `--platform` such as `linux/amd64,linux/arm64` or in several, each is built on its own and every one gets the same tags, so ce works the value out for each: one they share is used, and one that differs is refused, naming two platforms it differs between. podman and buildah set them as ce works them out from podman 5.2 and buildah 1.37, so ce asks the engine its version (`podman info`, which with `--remote` is the server's); older ones let a `--build-arg TARGETPLATFORM` win, and podman 3.4 and buildah 1.23 ignore `--platform`. Docker also takes a builtin's own `ARG` default, and works out a `SOURCE_DATE_EPOCH` that is not a plain number such as `1700000000` itself.
|
|
224
|
+
- **podman and buildah** differ inside a stage: an `ENV`, the base image's too, wins over an `ARG`, so on a base image ce only works out a reference to a name an `ENV` there sets; from `scratch`, the stage's `ARG`s count too. `--env` sets an `ENV` at the start of every stage (`--env NAME` and `--env NAME*` from the environment), and `--unsetenv` drops the base image's. `--from` replaces the first `FROM`'s image, so that stage is on a base image even when its `FROM` is `scratch`. A stage gets the `ARG`s of the stage its `FROM` names as written, so none through `FROM $BASE`. `TARGETPLATFORM` is always the platform they build, and before the first `FROM` the builtins are not the `--platform`'s, but the machine's, or empty before podman 5.7 and buildah 1.42.
|
|
143
225
|
|
|
144
226
|
**What ce refuses**, with exit 2 and the reason:
|
|
145
227
|
|
|
146
228
|
- **The file is not there to read:** `-f -`, a `-f` URL or a remote context; with podman or buildah, several `-f` files or a `.in` one for `cpp`.
|
|
147
229
|
- **Another frontend:** a `# syntax=` directive or `BUILDKIT_SYNTAX` naming anything but `docker/dockerfile` untagged, `:1`, or `:1.21` and later, which may be pinned by a digest.
|
|
148
|
-
- **A value ce cannot know:** a reference to a
|
|
230
|
+
- **A value ce cannot know:** a reference to a name the base image's `ENV` may set; a builtin of the machine that builds, such as `BUILDARCH`; a `TARGET` one without a `--platform`, with one ce does not read (such as `linux` alone, or an empty one in a list, which podman and buildah build for the machine), with one that differs between the platforms, with podman before 5.2 or buildah before 1.37, or with podman's `--arch`, `--os`, `--variant` or `--all-platforms`; a form like `${NAME#x}`; with podman or buildah, a `$` in a `--env` value, which they expand, or a character they escape there, such as a line end or a tab, and quotes in a `--unsetenv`.
|
|
149
231
|
- **Instructions ce does not read:** an `ARG` or `ENV` name with something to expand, such as `ENV $NAME=x`, or with flags.
|
|
150
232
|
- **Characters other than ASCII's:** letters in an instruction, a stage name or a name after a `$`; space in an `ARG`, `ENV` or `FROM` line or a heredoc's line. Also a line ending in more than one carriage return.
|
|
151
233
|
- **Heredocs ce cannot place:** a delimiter that is not a word, which may be quoted; on its line, a `${}` with space or quotes in its word outside double quotes, or with quotes, `$` or `<` in it anywhere.
|
|
152
|
-
- **Stages that change under it:** a `--build-context` replacing a stage the built one comes from (with podman and buildah, what a `FROM` names); with podman or buildah, a stage built from one with an `ONBUILD ARG` or `ONBUILD ENV`, which they run only
|
|
234
|
+
- **Stages that change under it:** a `--build-context` replacing a stage the built one comes from (with podman and buildah, what a `FROM` names); with podman or buildah, a stage built from one with an `ONBUILD ARG` or `ONBUILD ENV`, which they run only with the `docker` image format, unless `--format` or `BUILDAH_FORMAT` says which (`containers.conf` may change the default).
|
|
153
235
|
|
|
154
236
|
Two places always need a `--build-arg NAME`: a `{arg.NAME}` in the cache prefix with `CACHE_FROM_DEFAULT`, as the default branch may have another Containerfile; and with podman's `--build-arg-file`, which ce does not read. ce does not see a base image's own `ONBUILD` lines, and does not model Docker's legacy builder (`DOCKER_BUILDKIT=0`). Every rule here has a test in `test/containerfile.test.ts`.
|
|
155
237
|
|
|
156
238
|
### Checking a template
|
|
157
239
|
|
|
158
|
-
`--dry-run`
|
|
240
|
+
`--dry-run` after the engine name, or `CE_DRY_RUN=true`, prints the build summary, skipped destinations and tags with the guards that excluded them, and the engine command ce would run. Explanations go to stderr without resolved variable values; stdout contains only the shell command. It exits 0 without running the command or waiting for the engine. A template that fails still exits 2, so it also works as a check before pushing a workflow. It checks the templates, not the engine: if the engine is not on `PATH`, it warns and still exits 0, so it works on a machine without one. The one exception is a `TARGET` arg from `--platform` with podman or buildah, which needs the engine's version: ce asks it, waiting for podman as a build does, and without an answer refuses the value, so the dry run exits 2:
|
|
159
241
|
|
|
160
242
|
```sh
|
|
161
243
|
GITHUB_REF=refs/tags/v1.2.3 GITHUB_SERVER_URL=https://code.example GITHUB_REPOSITORY=org/app \
|
|
162
|
-
ce --dry-run
|
|
244
|
+
ce docker --dry-run buildx build -t '{image}:{ref},{?stable}{major}.{minor}' .
|
|
163
245
|
```
|
|
164
246
|
|
|
165
247
|
### OCI labels
|
|
166
248
|
|
|
167
|
-
|
|
249
|
+
Builds in a run get these `org.opencontainers.image.` labels, and the same keys as annotations:
|
|
250
|
+
|
|
251
|
+
| Key | Value |
|
|
252
|
+
| ------------- | -------------------------------------------------------------------------------------- |
|
|
253
|
+
| `source` | the repository's page, `{server}/{owner}/{repo}` |
|
|
254
|
+
| `url` | the repository's website from the event, or else its page |
|
|
255
|
+
| `revision` | the commit, `GITHUB_SHA` |
|
|
256
|
+
| `version` | the tag's version, such as `1.2.3`, or else the ref's name (`pr-N` for a pull request) |
|
|
257
|
+
| `title` | the repository's name |
|
|
258
|
+
| `description` | the repository's description from the event |
|
|
259
|
+
| `created` | when ce started the build, to the second, or the time `SOURCE_DATE_EPOCH` gives |
|
|
260
|
+
|
|
261
|
+
Keys the run cannot tell are left out, and outside a run there are none, `created` included. `OCI_LABELS=false`, or `--no-oci-labels` after the engine name (`ce docker --no-oci-labels buildx build …`, like the prefix options), turns them off. The labels override a Dockerfile `LABEL` with the same key, and a `--label` you pass for a key wins over ce's label for it, an `--annotation` over its annotation. Labels and annotations are applied after the `RUN` steps, so `created` does not change their cache, but each build's image differs. For one that does not, set the epoch: `created` follows it as the engine does. With Docker, that is a `--build-arg SOURCE_DATE_EPOCH`, or else the environment's `SOURCE_DATE_EPOCH`, or else the default of a global `ARG SOURCE_DATE_EPOCH` before the first `FROM`, which ce reads from the Dockerfile. With podman and buildah, it is `--timestamp`, which they refuse beside any of the others, or else `--source-date-epoch`, or else the environment's `SOURCE_DATE_EPOCH`, or else the build arg. Each is read as the engine reads it: a whole number of seconds, which may be negative, and for `--timestamp` also in Go's other bases, such as `0x10` or `010` (8). An epoch ce cannot read, such as `1.5` or a `SOURCE_DATE_EPOCH` the environment gives that is not a number, leaves `created` out, as do a podman or buildah `--build-arg-file` with none of the others, as the file may set it, and with Docker a global default with a `$` or a Dockerfile ce cannot read, such as a remote context's.
|
|
262
|
+
|
|
263
|
+
podman and buildah expand a `$NAME` in a `--label`'s value from the build args, and make one that is not a build arg empty, with no way to escape the `$`. They also write a label's value as Go's `%q` does, so a line end becomes `\n`, as do a tab and other characters Go escapes. So with them, a label whose value has a `$`, such as a description that mentions `$HOME`, or a character they escape, is left out; its annotation, which they keep as it is, stays.
|
|
264
|
+
|
|
265
|
+
With Docker, a build for two or more platforms gets the annotations on the image index too (`manifest,index:`). BuildKit refuses index annotations for one platform, so a build for one gets them on its manifest only. `DOCKER_BUILDKIT=0` gets the labels only, as the legacy builder it picks has no `--annotation`; `docker buildx build` ignores it and always uses BuildKit. podman and buildah put them on the image's manifest, not on a `--manifest` list. With the `docker` image format they drop them, as that format has no annotations.
|
|
266
|
+
|
|
267
|
+
### SBOM
|
|
268
|
+
|
|
269
|
+
`SBOM` adds a software bill of materials to builds. `true` (or `spdx`) gives SPDX on every engine, and `cyclonedx` gives CycloneDX with podman and buildah. It is off when unset, empty or `false`; it also takes `yes`, `no`, `1` and `0`, in any case, and any other value exits 2.
|
|
270
|
+
|
|
271
|
+
- **Docker:** `--sbom=true`, BuildKit's SBOM attestation, unless you pass `--sbom` or an `--attest type=sbom`. [It is always SPDX](https://docs.docker.com/build/metadata/attestations/sbom/), so `SBOM=cyclonedx` exits 2 before the build, unless your own flag means ce adds none. With `--load`, Docker's classic image store refuses the attestation and says what to switch to: another driver, or the containerd image store.
|
|
272
|
+
- **podman and buildah:** `--sbom=syft-spdx`, or `--sbom=syft-cyclonedx` for `SBOM=cyclonedx`, unless you pass a `--sbom` preset. (buildah's plain `syft` preset is CycloneDX, so ce names the format.) The SBOM is written into the image as `/root/buildinfo/content_manifests/sbom.spdx.json`, or `sbom.cdx.json` for a CycloneDX preset. A `--sbom` you pass without an `--sbom-output`, `--sbom-image-output`, `--sbom-purl-output` or `--sbom-image-purl-output` is written there too, even without `SBOM`. The scanner runs from an image, which the engine pulls if it is missing.
|
|
273
|
+
|
|
274
|
+
## Registry login
|
|
275
|
+
|
|
276
|
+
```sh
|
|
277
|
+
ce docker login
|
|
278
|
+
ce podman login
|
|
279
|
+
ce buildah login
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
A bare container-engine login uses the existing `REGISTRY_HOST`, `REGISTRY_USER`, `REGISTRY_PASSWORD` groups and Forgejo OIDC settings, but runs only the named engine. A failure does not fall back to another engine. `ce login` remains compatible with the original fallback order: for each registry, try Docker, then Podman, then Buildah until one succeeds. No configured registries remains a successful no-op.
|
|
283
|
+
|
|
284
|
+
Native login arguments pass through, so `ce docker login registry.example --username alice` keeps Docker's normal behavior. To use native login without arguments, write `ce docker -- login`. `ce npm login` also uses npm's native login; publishing continues to use the selected client's authentication configuration.
|
|
285
|
+
|
|
286
|
+
`ce docker --dry-run login` validates configured targets and prints the login commands without sending passwords, fetching OIDC tokens or writing credentials. Scoped Docker OIDC login installs the Docker credential helper; scoped Podman and Buildah login do not modify Docker's helper configuration.
|
|
168
287
|
|
|
169
288
|
## The credential helper
|
|
170
289
|
|
|
@@ -184,7 +303,7 @@ These behave the same under Node and Bun, and [test/contract.test.ts](test/contr
|
|
|
184
303
|
|
|
185
304
|
The wording of messages is not part of that and may change.
|
|
186
305
|
|
|
187
|
-
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. podman and buildah are no longer run through `gosu` as `$USER` when it is on `PATH`: ce runs every engine directly, as the user ce runs as, so to drop root, run ce itself as another user, e.g. with a `USER` line in the image.
|
|
306
|
+
In 0.0.3, builds in a run get the OCI labels, and annotations, by default; `OCI_LABELS=false` or `--no-oci-labels` turns them off. 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. podman and buildah are no longer run through `gosu` as `$USER` when it is on `PATH`: ce runs every engine directly, as the user ce runs as, so to drop root, run ce itself as another user, e.g. with a `USER` line in the image.
|
|
188
307
|
|
|
189
308
|
Compared with the Python version:
|
|
190
309
|
|
|
@@ -197,9 +316,24 @@ Compared with the Python version:
|
|
|
197
316
|
|
|
198
317
|
## Development
|
|
199
318
|
|
|
319
|
+
### Build CI and npm staging
|
|
320
|
+
|
|
321
|
+
[The Forgejo build workflow](.forgejo/workflows/build.yml) builds, type-checks and tests PRs, pushes to `main` and `dev`, version tags (`v*`), and manual runs. Every successful build uploads the packed npm tarball as an artifact.
|
|
322
|
+
|
|
323
|
+
Version-tag pushes also authenticate with the repository's `NPM_TOKEN` Actions secret and stage that exact tarball using `ce npm --stage publish`. The Git tag must match `v` followed by the version in `package.json`. Ce infers a prerelease's channel from the packed version; stable versions keep npm's native default and version checks. For a manual run, select **stage** to request staging and optionally set **npm_tag** to override the dist-tag. Manual runs without **stage** build, check and upload the package artifact.
|
|
324
|
+
|
|
325
|
+
Use a granular npm token with read/write access to `@izykitten/ce`; a [stage-only token](https://docs.npmjs.com/about-access-tokens#about-stage-only-tokens) keeps final publication dependent on maintainer approval. The workflow configures token authentication without interactive `npm login`, verifies it with `npm whoami`, and removes its temporary npm configuration when the staging step exits. The token is available only to that step, which does not run on PRs or ordinary branch pushes.
|
|
326
|
+
|
|
327
|
+
CI runs `npm stage publish`, which [uploads a pending version without publishing it](https://docs.npmjs.com/cli/v12/commands/npm-stage). Review and approve the staged version in npm's dashboard with your own authentication. Staged and published versions share the same version namespace: bump `package.json` to an unused version before staging, and use the matching `v...` Git tag for an automatic release run. The workflow uses npm 12.2.1 for staging and never runs the approval command.
|
|
328
|
+
|
|
329
|
+
### Local checks
|
|
330
|
+
|
|
200
331
|
```sh
|
|
201
332
|
bun install
|
|
202
333
|
bun test # builds dist/, then unit tests plus the contract and process tests under Node and Bun
|
|
203
334
|
bun check
|
|
204
335
|
bun run build # dist/ce.js, which the bin scripts load; npm pack runs it
|
|
336
|
+
npm run stage -- --tag dev # stage the current version for approval, with npm authentication configured
|
|
205
337
|
```
|
|
338
|
+
|
|
339
|
+
`bun run check-engines` builds each case in [scripts/engine-cases.ts](scripts/engine-cases.ts) with real Docker, podman and buildah, and compares the build arg each build gets with the one ce works out. It fails if ce would tag with a wrong value. podman and buildah run in a privileged container each, from `quay.io/podman/stable` and `quay.io/buildah/stable` unless `PODMAN_IMAGE` or `BUILDAH_IMAGE` names another, such as an older version's; `bun run check-engines docker` checks Docker alone. Run it after changing how ce reads a Containerfile.
|