Compare commits

...

7 Commits

Author SHA1 Message Date
CrazyMax
b500d9c7b5 Missing entry in action.yml 2020-12-24 14:06:20 +01:00
CrazyMax
aa3823e012 Update CHANGELOG 2020-12-24 04:19:39 +01:00
CrazyMax
a4739af83c Update README 2020-12-24 04:16:21 +01:00
CrazyMax
10e9d5d585 Add bake-file output (#36)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-24 03:13:41 +00:00
CrazyMax
3479bd5aaa Add label-custom input (#35)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-23 21:09:38 +00:00
dependabot[bot]
c48ac80f46 Bump node-notifier from 8.0.0 to 8.0.1 (#33)
Bumps [node-notifier](https://github.com/mikaelbr/node-notifier) from 8.0.0 to 8.0.1.
- [Release notes](https://github.com/mikaelbr/node-notifier/releases)
- [Changelog](https://github.com/mikaelbr/node-notifier/blob/v8.0.1/CHANGELOG.md)
- [Commits](https://github.com/mikaelbr/node-notifier/compare/v8.0.0...v8.0.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-23 09:00:39 +00:00
CrazyMax
805449f25e Update dev workflow (#32)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-19 02:53:36 +00:00
20 changed files with 2246 additions and 151 deletions

View File

@@ -1 +1,5 @@
node_modules /.dev
/coverage
/dist
/lib
/node_modules

View File

@@ -2,33 +2,20 @@
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE). Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license)
to the public under the [project's open source license](LICENSE).
## Submitting a pull request ## Submitting a pull request
1. [Fork](https://github.com/crazy-max/ghaction-docker-meta/fork) and clone the repository 1. [Fork](https://github.com/crazy-max/ghaction-docker-meta/fork) and clone the repository
2. Configure and install the dependencies: `yarn install` 2. Configure and install the dependencies: `yarn install`
3. Make sure the tests pass on your machine: `yarn run test` 3. Create a new branch: `git checkout -b my-branch-name`
4. Create a new branch: `git checkout -b my-branch-name` 4. Make your changes
5. Make your change, add tests, and make sure the tests still pass 5. Make sure the tests pass: `docker buildx bake test`
6. Run pre-checkin: `yarn run pre-checkin` 6. Format code and build javascript artifacts: `docker buildx bake pre-checkin`
7. Push to your fork and [submit a pull request](https://github.com/crazy-max/ghaction-docker-meta/compare) 7. Validate all code has correctly formatted and built: `docker buildx bake validate`
8. Pat your self on the back and wait for your pull request to be reviewed and merged. 8. Push to your fork and [submit a pull request](https://github.com/crazy-max/ghaction-docker-meta/compare)
9. Pat your self on the back and wait for your pull request to be reviewed and merged.
## Container based developer flow
If you don't want to maintain a Node developer environment that fits this project you can use containerized commands instead of invoking yarn directly.
```
# format code and build javascript artifacts
docker buildx bake pre-checkin
# validate all code has correctly formatted and built
docker buildx bake validate
# run tests
docker buildx bake test
```
Here are a few things you can do that will increase the likelihood of your pull request being accepted: Here are a few things you can do that will increase the likelihood of your pull request being accepted:

View File

@@ -105,6 +105,25 @@ jobs:
{{major}}.{{minor}}.{{patch}} {{major}}.{{minor}}.{{patch}}
tag-latest: ${{ matrix.tag-latest }} tag-latest: ${{ matrix.tag-latest }}
label-custom:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
uses: ./
with:
images: |
${{ env.DOCKER_IMAGE }}
ghcr.io/name/app
label-custom: |
maintainer=CrazyMax
org.opencontainers.image.title=MyCustomTitle
org.opencontainers.image.description=Another description
org.opencontainers.image.vendor=MyCompany
docker-push: docker-push:
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
@@ -158,3 +177,38 @@ jobs:
name: Dump context name: Dump context
if: always() if: always()
uses: crazy-max/ghaction-dump-context@v1 uses: crazy-max/ghaction-dump-context@v1
bake:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
id: docker_meta
uses: ./
with:
images: |
${{ env.DOCKER_IMAGE }}
ghcr.io/name/app
tag-sha: true
tag-semver: |
{{version}}
{{major}}.{{minor}}
{{major}}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Build
uses: crazy-max/ghaction-docker-buildx-bake@v1
with:
files: |
./test/docker-bake.hcl
${{ steps.docker_meta.outputs.bake-file }}
targets: |
release

View File

@@ -8,39 +8,24 @@ on:
paths-ignore: paths-ignore:
- '**.md' - '**.md'
pull_request: pull_request:
branches:
- 'master'
paths-ignore: paths-ignore:
- '**.md' - '**.md'
jobs: jobs:
test-containerized:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Validate
run: docker buildx bake validate
-
name: Test
run: docker buildx bake test
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
-
name: Install
run: yarn install
- -
name: Test name: Test
run: yarn run test run: docker buildx bake test
- -
name: Upload coverage name: Upload coverage
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v1
if: success()
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage/clover.xml file: ./coverage/clover.xml

25
.github/workflows/validate.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: validate
on:
push:
branches:
- 'master'
- 'releases/v*'
paths-ignore:
- '**.md'
pull_request:
branches:
- 'master'
paths-ignore:
- '**.md'
jobs:
validate:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Validate
run: docker buildx bake validate

View File

@@ -1,5 +1,16 @@
# Changelog # Changelog
## 1.10.1 (2020/12/24)
* Missing entry in `action.yml`
## 1.10.0 (2020/12/24)
* Add `bake-file` output (#36)
* Add `label-custom` input (#35)
* Bump node-notifier from 8.0.0 to 8.0.1 (#33)
* Update dev workflow (#32)
## 1.9.1 (2020/12/07) ## 1.9.1 (2020/12/07)
* Replace forbidden chars derived from branch name (#31) * Replace forbidden chars derived from branch name (#31)

View File

@@ -1,9 +1,9 @@
#syntax=docker/dockerfile:1.1-experimental #syntax=docker/dockerfile:1.2
FROM node:12 AS deps FROM node:12 AS deps
WORKDIR /src WORKDIR /src
COPY package.json yarn.lock ./ COPY package.json yarn.lock ./
RUN --mount=type=cache,target=/usr/local/share/.cache/yarn \ RUN --mount=type=cache,target=/src/node_modules \
yarn install yarn install
FROM scratch AS update-yarn FROM scratch AS update-yarn
@@ -17,20 +17,29 @@ FROM deps AS base
COPY . . COPY . .
FROM base AS build FROM base AS build
RUN yarn build RUN --mount=type=cache,target=/src/node_modules \
yarn build
FROM deps AS test FROM deps AS test
ENV RUNNER_TEMP=/tmp/github_runner
ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache
COPY . . COPY . .
RUN yarn run test RUN --mount=type=cache,target=/src/node_modules \
yarn run test
FROM scratch AS test-coverage
COPY --from=test /src/coverage /coverage/
FROM base AS run-format FROM base AS run-format
RUN yarn run format RUN --mount=type=cache,target=/src/node_modules \
yarn run format
FROM scratch AS format FROM scratch AS format
COPY --from=run-format /src/src/*.ts /src/ COPY --from=run-format /src/src/*.ts /src/
FROM base AS validate-format FROM base AS validate-format
RUN yarn run format-check RUN --mount=type=cache,target=/src/node_modules \
yarn run format-check
FROM scratch AS dist FROM scratch AS dist
COPY --from=build /src/dist/ /dist/ COPY --from=build /src/dist/ /dist/

145
README.md
View File

@@ -16,11 +16,10 @@ If you are interested, [check out](https://git.io/Je09Y) my other :octocat: GitH
___ ___
* [Features](#features)
* [Usage](#usage) * [Usage](#usage)
* [Basic](#basic) * [Basic](#basic)
* [Semver](#semver) * [Semver](#semver)
* [Complete](#complete) * [Bake definition](#bake-definition)
* [Customizing](#customizing) * [Customizing](#customizing)
* [inputs](#inputs) * [inputs](#inputs)
* [outputs](#outputs) * [outputs](#outputs)
@@ -31,15 +30,9 @@ ___
* [Schedule tag](#schedule-tag) * [Schedule tag](#schedule-tag)
* [Overwrite labels](#overwrite-labels) * [Overwrite labels](#overwrite-labels)
* [Keep up-to-date with GitHub Dependabot](#keep-up-to-date-with-github-dependabot) * [Keep up-to-date with GitHub Dependabot](#keep-up-to-date-with-github-dependabot)
* [How can I help?](#how-can-i-help) * [Contributing](#contributing)
* [License](#license) * [License](#license)
## Features
* Docker tags generated from GitHub action event and Git metadata
* [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/master/annotations.md) used to generate Docker labels
* [Handlebars template](https://handlebarsjs.com/guide/) to apply to schedule tag
## Usage ## Usage
### Basic ### Basic
@@ -85,7 +78,7 @@ jobs:
- -
name: Login to DockerHub name: Login to DockerHub
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@v1 uses: docker/login-action@v1
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -147,7 +140,7 @@ jobs:
- -
name: Login to DockerHub name: Login to DockerHub
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@v1 uses: docker/login-action@v1
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -163,23 +156,29 @@ jobs:
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.docker_meta.outputs.labels }}
``` ```
### Complete ### Bake definition
| Event | Ref | Commit SHA | Docker Tags | This action also handles a bake definition file that can be used with the
|-----------------|-------------------------------|------------|-----------------------------------------| [Docker Buildx Bake action](https://github.com/crazy-max/ghaction-docker-buildx-bake). You just have to declare a
| `schedule` | `refs/heads/master` | `45f132a` | `sha-45f132a`, `nightly` | target named `ghaction-docker-meta`.
| `pull_request` | `refs/pull/2/merge` | `a123b57` | `sha-a123b57`, `pr-2` |
| `push` | `refs/heads/master` | `cf20257` | `sha-cf20257`, `master` | ```hcl
| `push` | `refs/heads/my/branch` | `a5df687` | `sha-a5df687`, `my-branch` | // docker-bake.hcl
| `push tag` | `refs/tags/v1.2.3` | `ad132f5` | `sha-ad132f5`, `1.2.3`, `1.2`, `latest` |
| `push tag` | `refs/tags/v2.0.8-beta.67` | `fc89efd` | `sha-fc89efd`, `2.0.8-beta.67` | target "ghaction-docker-meta" {}
target "build" {
inherits = ["ghaction-docker-meta"]
context = "./"
dockerfile = "Dockerfile"
platforms = ["linux/amd64", "linux/arm/v6", "linux/arm/v7", "linux/arm64", "linux/386", "linux/ppc64le"]
}
```
```yaml ```yaml
name: ci name: ci
on: on:
schedule:
- cron: '0 10 * * *' # everyday at 10am
push: push:
branches: branches:
- '**' - '**'
@@ -211,22 +210,40 @@ jobs:
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v1
- -
name: Login to DockerHub name: Build
if: github.event_name != 'pull_request' uses: crazy-max/ghaction-docker-buildx-bake@v1
uses: docker/login-action@v1
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} files: |
password: ${{ secrets.DOCKERHUB_TOKEN }} ./docker-bake.hcl
- ${{ steps.docker_meta.outputs.bake-file }}
name: Build and push targets: |
uses: docker/build-push-action@v2 build
with: ```
context: .
file: ./Dockerfile Content of `${{ steps.docker_meta.outputs.bake-file }}` file will look like this:
platforms: linux/amd64,linux/arm64,linux/386
push: ${{ github.event_name != 'pull_request' }} ```json
tags: ${{ steps.docker_meta.outputs.tags }} {
labels: ${{ steps.docker_meta.outputs.labels }} "target": {
"ghaction-docker-meta": {
"tags": [
"name/app:1.1.1",
"name/app:1.1",
"name/app:latest"
],
"labels": {
"org.opencontainers.image.title": "Hello-World",
"org.opencontainers.image.description": "This your first repo!",
"org.opencontainers.image.url": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.source": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.version": "1.1.1",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
``` ```
## Customizing ## Customizing
@@ -235,10 +252,23 @@ jobs:
Following inputs can be used as `step.with` keys Following inputs can be used as `step.with` keys
> `List` type is a newline-delimited string
> ```yaml
> label-custom: |
> org.opencontainers.image.title=MyCustomTitle
> org.opencontainers.image.description=Another description
> org.opencontainers.image.vendor=MyCompany
> ```
> `CSV` type is a comma-delimited string
> ```yaml
> images: name/app,ghcr.io/name/app
> ```
| Name | Type | Description | | Name | Type | Description |
|---------------------|----------|------------------------------------| |---------------------|----------|------------------------------------|
| `images` | List/CSV | List of Docker images to use as base name for tags | | `images` | List/CSV | List of Docker images to use as base name for tags |
| `tag-sha` | Bool | Add git short SHA as Docker tag (default `false`) | | `tag-sha` | Bool | Add git short commit as Docker tag (default `false`) |
| `tag-edge` | Bool | Enable edge branch tagging (default `false`) | | `tag-edge` | Bool | Enable edge branch tagging (default `false`) |
| `tag-edge-branch` | String | Branch that will be tagged as edge (default `repo.default_branch`) | | `tag-edge-branch` | String | Branch that will be tagged as edge (default `repo.default_branch`) |
| `tag-semver` | List/CSV | Handle Git tag as semver [template](#handle-semver-tag) if possible | | `tag-semver` | List/CSV | Handle Git tag as semver [template](#handle-semver-tag) if possible |
@@ -248,11 +278,10 @@ Following inputs can be used as `step.with` keys
| `tag-schedule` | String | [Template](#schedule-tag) to apply to schedule tag (default `nightly`) | | `tag-schedule` | String | [Template](#schedule-tag) to apply to schedule tag (default `nightly`) |
| `tag-custom` | List/CSV | List of custom tags | | `tag-custom` | List/CSV | List of custom tags |
| `tag-custom-only` | Bool | Only use `tag-custom` as Docker tags | | `tag-custom-only` | Bool | Only use `tag-custom` as Docker tags |
| `label-custom` | List | List of custom labels |
| `sep-tags` | String | Separator to use for tags output (default `\n`) | | `sep-tags` | String | Separator to use for tags output (default `\n`) |
| `sep-labels` | String | Separator to use for labels output (default `\n`) | | `sep-labels` | String | Separator to use for labels output (default `\n`) |
> List/CSV type can be a newline or comma delimited string
> `tag-semver` and `tag-match` are mutually exclusive > `tag-semver` and `tag-match` are mutually exclusive
### outputs ### outputs
@@ -261,9 +290,10 @@ Following outputs are available
| Name | Type | Description | | Name | Type | Description |
|---------------|---------|---------------------------------------| |---------------|---------|---------------------------------------|
| `version` | String | Generated Docker image version | | `version` | String | Docker image version |
| `tags` | String | Generated Docker tags | | `tags` | String | Docker tags |
| `labels` | String | Generated Docker labels | | `labels` | String | Docker labels |
| `bake-file` | File | [Bake definition file](https://github.com/docker/buildx#file-definition) path |
## Notes ## Notes
@@ -315,7 +345,7 @@ the following expressions:
| Expression | Example | Description | | Expression | Example | Description |
|-------------------------|-------------------------------------------|------------------------------------------| |-------------------------|-------------------------------------------|------------------------------------------|
| `{{date 'format'}}` | `{{date 'YYYYMMDD'}}` > `20200110` | Render date by its [moment format](https://momentjs.com/docs/#/displaying/format/) | `{{date 'format'}}` | `{{date 'YYYYMMDD'}}` > `20200110` | Render date by its [moment format](https://momentjs.com/docs/#/displaying/format/)
You can find more examples in the [CI workflow](.github/workflows/ci.yml). You can find more examples in the [CI workflow](.github/workflows/ci.yml).
@@ -326,16 +356,13 @@ labels generated are not suitable, you can overwrite them like this:
```yaml ```yaml
- -
name: Build and push name: Docker meta
uses: docker/build-push-action@v2 id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with: with:
context: . images: name/app
file: ./Dockerfile label-custom: |
platforms: linux/amd64,linux/arm64,linux/386 maintainer=CrazyMax
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: |
${{ steps.docker_meta.outputs.labels }}
org.opencontainers.image.title=MyCustomTitle org.opencontainers.image.title=MyCustomTitle
org.opencontainers.image.description=Another description org.opencontainers.image.description=Another description
org.opencontainers.image.vendor=MyCompany org.opencontainers.image.vendor=MyCompany
@@ -357,12 +384,14 @@ updates:
interval: "daily" interval: "daily"
``` ```
## How can I help? ## Contributing
All kinds of contributions are welcome :raised_hands:! The most basic way to show your support is to star :star2: Want to contribute? Awesome! The most basic way to show your support is to star :star2: the project,
the project, or to raise issues :speech_balloon: You can also support this project by or to raise issues :speech_balloon:. If you want to open a pull request, please read the
[**becoming a sponsor on GitHub**](https://github.com/sponsors/crazy-max) :clap: or by making a [contributing guidelines](.github/CONTRIBUTING.md).
[Paypal donation](https://www.paypal.me/crazyws) to ensure this journey continues indefinitely! :rocket:
You can also support this project by [**becoming a sponsor on GitHub**](https://github.com/sponsors/crazy-max) or by
making a [Paypal donation](https://www.paypal.me/crazyws) to ensure this journey continues indefinitely!
Thanks again for your support, it is much appreciated! :pray: Thanks again for your support, it is much appreciated! :pray:

View File

@@ -1,54 +1,161 @@
import * as fs from 'fs';
import * as path from 'path';
import * as context from '../src/context'; import * as context from '../src/context';
jest.spyOn(context, 'tmpDir').mockImplementation((): string => {
const tmpDir = path.join('/tmp/.ghaction-docker-meta-jest').split(path.sep).join(path.posix.sep);
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true});
}
return tmpDir;
});
describe('getInputList', () => { describe('getInputList', () => {
it('handles single line correctly', async () => { it('single line correctly', async () => {
await setInput('foo', 'bar'); await setInput('foo', 'bar');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar']); expect(res).toEqual(['bar']);
}); });
it('handles multiple lines correctly', async () => { it('multiline correctly', async () => {
setInput('foo', 'bar\nbaz'); setInput('foo', 'bar\nbaz');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar', 'baz']); expect(res).toEqual(['bar', 'baz']);
}); });
it('remove empty lines correctly', async () => { it('empty lines correctly', async () => {
setInput('foo', 'bar\n\nbaz'); setInput('foo', 'bar\n\nbaz');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar', 'baz']); expect(res).toEqual(['bar', 'baz']);
}); });
it('handles comma correctly', async () => { it('comma correctly', async () => {
setInput('foo', 'bar,baz'); setInput('foo', 'bar,baz');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar', 'baz']); expect(res).toEqual(['bar', 'baz']);
}); });
it('remove empty result correctly', async () => { it('empty result correctly', async () => {
setInput('foo', 'bar,baz,'); setInput('foo', 'bar,baz,');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar', 'baz']); expect(res).toEqual(['bar', 'baz']);
}); });
it('handles different new lines correctly', async () => { it('different new lines correctly', async () => {
setInput('foo', 'bar\r\nbaz'); setInput('foo', 'bar\r\nbaz');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar', 'baz']); expect(res).toEqual(['bar', 'baz']);
}); });
it('handles different new lines and comma correctly', async () => { it('different new lines and comma correctly', async () => {
setInput('foo', 'bar\r\nbaz,bat'); setInput('foo', 'bar\r\nbaz,bat');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar', 'baz', 'bat']); expect(res).toEqual(['bar', 'baz', 'bat']);
}); });
it('multiline and ignoring comma correctly', async () => {
setInput('cache-from', 'user/app:cache\ntype=local,src=path/to/dir');
const res = await context.getInputList('cache-from', true);
console.log(res);
expect(res).toEqual(['user/app:cache', 'type=local,src=path/to/dir']);
});
it('different new lines and ignoring comma correctly', async () => {
setInput('cache-from', 'user/app:cache\r\ntype=local,src=path/to/dir');
const res = await context.getInputList('cache-from', true);
console.log(res);
expect(res).toEqual(['user/app:cache', 'type=local,src=path/to/dir']);
});
it('multiline values', async () => {
setInput(
'secrets',
`GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789
"MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc"
FOO=bar`
);
const res = await context.getInputList('secrets', true);
console.log(res);
expect(res).toEqual([
'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789',
`MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc`,
'FOO=bar'
]);
});
it('multiline values with empty lines', async () => {
setInput(
'secrets',
`GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789
"MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc"
FOO=bar
"EMPTYLINE=aaaa
bbbb
ccc"`
);
const res = await context.getInputList('secrets', true);
console.log(res);
expect(res).toEqual([
'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789',
`MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc`,
'FOO=bar',
`EMPTYLINE=aaaa
bbbb
ccc`
]);
});
it('multiline values without quotes', async () => {
setInput(
'secrets',
`GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789
MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc
FOO=bar`
);
const res = await context.getInputList('secrets', true);
console.log(res);
expect(res).toEqual(['GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789', 'MYSECRET=aaaaaaaa', 'bbbbbbb', 'ccccccccc', 'FOO=bar']);
});
it('multiline values escape quotes', async () => {
setInput(
'secrets',
`GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789
"MYSECRET=aaaaaaaa
bbbb""bbb
ccccccccc"
FOO=bar`
);
const res = await context.getInputList('secrets', true);
console.log(res);
expect(res).toEqual([
'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789',
`MYSECRET=aaaaaaaa
bbbb\"bbb
ccccccccc`,
'FOO=bar'
]);
});
}); });
describe('asyncForEach', () => { describe('asyncForEach', () => {

View File

@@ -1009,6 +1009,42 @@ describe('latest', () => {
"org.opencontainers.image.licenses=MIT" "org.opencontainers.image.licenses=MIT"
] ]
], ],
[
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/MyUSER/MyApp'],
tagLatest: false,
labelCustom: [
"maintainer=CrazyMax",
"org.opencontainers.image.title=MyCustomTitle",
"org.opencontainers.image.description=Another description",
"org.opencontainers.image.vendor=MyCompany",
],
} as Inputs,
{
main: 'v1.1.1',
partial: [],
latest: false
} as Version,
[
'org/app:v1.1.1',
'ghcr.io/myuser/myapp:v1.1.1',
],
[
"org.opencontainers.image.title=Hello-World",
"org.opencontainers.image.description=This your first repo!",
"org.opencontainers.image.url=https://github.com/octocat/Hello-World",
"org.opencontainers.image.source=https://github.com/octocat/Hello-World",
"org.opencontainers.image.version=v1.1.1",
"org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses=MIT",
"maintainer=CrazyMax",
"org.opencontainers.image.title=MyCustomTitle",
"org.opencontainers.image.description=Another description",
"org.opencontainers.image.vendor=MyCompany"
]
],
])('given %p event ', tagsLabelsTest); ])('given %p event ', tagsLabelsTest);
}); });
@@ -1448,3 +1484,244 @@ describe('custom', () => {
], ],
])('given %p event ', tagsLabelsTest); ])('given %p event ', tagsLabelsTest);
}); });
describe('bake-file', () => {
// prettier-ignore
test.each([
[
'event_push.env',
{
images: ['user/app'],
tagCustom: ['my', 'custom', 'tags']
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"user/app:dev",
"user/app:my",
"user/app:custom",
"user/app:tags"
],
"labels": {
"org.opencontainers.image.title": "Hello-World",
"org.opencontainers.image.description": "This your first repo!",
"org.opencontainers.image.url": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.source": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.version": "dev",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
],
[
'event_push.env',
{
images: ['user/app'],
tagCustom: ['my']
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"user/app:dev",
"user/app:my",
],
"labels": {
"org.opencontainers.image.title": "Hello-World",
"org.opencontainers.image.description": "This your first repo!",
"org.opencontainers.image.url": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.source": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.version": "dev",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
],
[
'event_tag_release1.env',
{
images: ['user/app'],
tagCustom: ['my', 'custom', 'tags']
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"user/app:release1",
"user/app:my",
"user/app:custom",
"user/app:tags",
"user/app:latest"
],
"labels": {
"org.opencontainers.image.title": "Hello-World",
"org.opencontainers.image.description": "This your first repo!",
"org.opencontainers.image.url": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.source": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.version": "release1",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
],
[
'event_tag_20200110-RC2.env',
{
images: ['user/app'],
tagMatch: `\\d{8}`,
tagLatest: false,
tagCustom: ['my', 'custom', 'tags']
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"user/app:20200110",
"user/app:my",
"user/app:custom",
"user/app:tags"
],
"labels": {
"org.opencontainers.image.title": "Hello-World",
"org.opencontainers.image.description": "This your first repo!",
"org.opencontainers.image.url": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.source": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.version": "20200110",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
],
[
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
tagSemver: ['{{version}}', '{{major}}.{{minor}}', '{{major}}'],
tagCustom: ['my', 'custom', 'tags']
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"org/app:1.1.1",
"org/app:1.1",
"org/app:1",
"org/app:my",
"org/app:custom",
"org/app:tags",
"org/app:latest",
"ghcr.io/user/app:1.1.1",
"ghcr.io/user/app:1.1",
"ghcr.io/user/app:1",
"ghcr.io/user/app:my",
"ghcr.io/user/app:custom",
"ghcr.io/user/app:tags",
"ghcr.io/user/app:latest"
],
"labels": {
"org.opencontainers.image.title": "Hello-World",
"org.opencontainers.image.description": "This your first repo!",
"org.opencontainers.image.url": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.source": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.version": "1.1.1",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
],
[
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
tagSemver: ['{{version}}', '{{major}}.{{minor}}.{{patch}}'],
tagCustom: ['my', 'custom', 'tags'],
tagCustomOnly: true,
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"org/app:my",
"org/app:custom",
"org/app:tags",
"ghcr.io/user/app:my",
"ghcr.io/user/app:custom",
"ghcr.io/user/app:tags"
],
"labels": {
"org.opencontainers.image.title": "Hello-World",
"org.opencontainers.image.description": "This your first repo!",
"org.opencontainers.image.url": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.source": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.version": "my",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
],
[
'event_tag_v1.1.1.env',
{
images: ['org/app'],
labelCustom: [
"maintainer=CrazyMax",
"org.opencontainers.image.title=MyCustom=Title",
"org.opencontainers.image.description=Another description",
"org.opencontainers.image.vendor=MyCompany",
],
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"org/app:v1.1.1",
"org/app:latest"
],
"labels": {
"maintainer": "CrazyMax",
"org.opencontainers.image.title": "MyCustom=Title",
"org.opencontainers.image.description": "Another description",
"org.opencontainers.image.url": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.source": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.vendor": "MyCompany",
"org.opencontainers.image.version": "v1.1.1",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
]
])('given %p event ', async (envFile: string, inputs: Inputs, exBakeDefinition: {}) => {
process.env = dotenv.parse(fs.readFileSync(path.join(__dirname, 'fixtures', envFile)));
const context = github.context();
console.log(process.env, context);
const repo = await github.repo(process.env.GITHUB_TOKEN || '');
const meta = new Meta({...getInputs(), ...inputs}, context, repo);
const bakeFile = meta.bakeFile();
console.log('bakeFile', bakeFile, fs.readFileSync(bakeFile, 'utf8'));
expect(JSON.parse(fs.readFileSync(bakeFile, 'utf8'))).toEqual(exBakeDefinition);
});
});

View File

@@ -50,6 +50,9 @@ inputs:
tag-custom-only: tag-custom-only:
description: 'Only use tag-custom as Docker tags' description: 'Only use tag-custom as Docker tags'
required: false required: false
label-custom:
description: 'List of custom labels'
required: false
sep-tags: sep-tags:
description: 'Separator to use for tags output (default \n)' description: 'Separator to use for tags output (default \n)'
required: false required: false
@@ -68,6 +71,8 @@ outputs:
description: 'Generated Docker tags' description: 'Generated Docker tags'
labels: labels:
description: 'Generated Docker labels' description: 'Generated Docker labels'
bake-file:
description: 'Bake definiton file'
runs: runs:
using: 'node12' using: 'node12'

1506
dist/index.js generated vendored

File diff suppressed because it is too large Load Diff

View File

@@ -7,36 +7,48 @@ group "pre-checkin" {
} }
group "validate" { group "validate" {
targets = ["validate-format", "validate-build", "validate-yarn"] targets = ["validate-format", "validate-build", "validate-yarn"]
}
target "dockerfile" {
dockerfile = "Dockerfile.dev"
} }
target "update-yarn" { target "update-yarn" {
inherits = ["dockerfile"]
target = "update-yarn" target = "update-yarn"
output = ["."] output = ["."]
} }
target "build" { target "build" {
inherits = ["dockerfile"]
target = "dist" target = "dist"
output = ["."] output = ["."]
} }
target "test" { target "test" {
target = "test" inherits = ["dockerfile"]
target = "test-coverage"
output = ["."]
} }
target "format" { target "format" {
inherits = ["dockerfile"]
target = "format" target = "format"
output = ["."] output = ["."]
} }
target "validate-format" { target "validate-format" {
inherits = ["dockerfile"]
target = "validate-format" target = "validate-format"
} }
target "validate-build" { target "validate-build" {
inherits = ["dockerfile"]
target = "validate-build" target = "validate-build"
} }
target "validate-yarn" { target "validate-yarn" {
target = "validate-yarn" inherits = ["dockerfile"]
target = "validate-yarn"
} }

View File

@@ -25,6 +25,7 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
"@actions/github": "^4.0.0", "@actions/github": "^4.0.0",
"csv-parse": "^4.14.2",
"handlebars": "^4.7.6", "handlebars": "^4.7.6",
"moment": "^2.29.1", "moment": "^2.29.1",
"semver": "^7.3.4" "semver": "^7.3.4"

View File

@@ -1,4 +1,10 @@
import csvparse from 'csv-parse/lib/sync';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
let _tmpDir: string;
export interface Inputs { export interface Inputs {
images: string[]; images: string[];
@@ -12,11 +18,19 @@ export interface Inputs {
tagSchedule: string; tagSchedule: string;
tagCustom: string[]; tagCustom: string[];
tagCustomOnly: boolean; tagCustomOnly: boolean;
labelCustom: string[];
sepTags: string; sepTags: string;
sepLabels: string; sepLabels: string;
githubToken: string; githubToken: string;
} }
export function tmpDir(): string {
if (!_tmpDir) {
_tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ghaction-docker-meta-')).split(path.sep).join(path.posix.sep);
}
return _tmpDir;
}
export function getInputs(): Inputs { export function getInputs(): Inputs {
return { return {
images: getInputList('images'), images: getInputList('images'),
@@ -30,21 +44,37 @@ export function getInputs(): Inputs {
tagSchedule: core.getInput('tag-schedule') || 'nightly', tagSchedule: core.getInput('tag-schedule') || 'nightly',
tagCustom: getInputList('tag-custom'), tagCustom: getInputList('tag-custom'),
tagCustomOnly: /true/i.test(core.getInput('tag-custom-only') || 'false'), tagCustomOnly: /true/i.test(core.getInput('tag-custom-only') || 'false'),
labelCustom: getInputList('label-custom'),
sepTags: core.getInput('sep-tags') || `\n`, sepTags: core.getInput('sep-tags') || `\n`,
sepLabels: core.getInput('sep-labels') || `\n`, sepLabels: core.getInput('sep-labels') || `\n`,
githubToken: core.getInput('github-token') githubToken: core.getInput('github-token')
}; };
} }
export function getInputList(name: string): string[] { export function getInputList(name: string, ignoreComma?: boolean): string[] {
let res: Array<string> = [];
const items = core.getInput(name); const items = core.getInput(name);
if (items == '') { if (items == '') {
return []; return res;
} }
return items
.split(/\r?\n/) for (let output of csvparse(items, {
.filter(x => x) columns: false,
.reduce<string[]>((acc, line) => acc.concat(line.split(',').filter(x => x)).map(pat => pat.trim()), []); relaxColumnCount: true,
skipLinesWithEmptyValues: true
}) as Array<string[]>) {
if (output.length == 1) {
res.push(output[0]);
continue;
} else if (!ignoreComma) {
res.push(...output);
continue;
}
res.push(output.join(','));
}
return res.filter(item => item).map(pat => pat.trim());
} }
export const asyncForEach = async (array, callback) => { export const asyncForEach = async (array, callback) => {

View File

@@ -1,3 +1,4 @@
import * as fs from 'fs';
import {getInputs, Inputs} from './context'; import {getInputs, Inputs} from './context';
import * as github from './github'; import * as github from './github';
import {Meta, Version} from './meta'; import {Meta, Version} from './meta';
@@ -33,6 +34,7 @@ async function run() {
core.endGroup(); core.endGroup();
core.setOutput('version', version.main || ''); core.setOutput('version', version.main || '');
// Docker tags
const tags: Array<string> = meta.tags(); const tags: Array<string> = meta.tags();
core.startGroup(`Docker tags`); core.startGroup(`Docker tags`);
for (let tag of tags) { for (let tag of tags) {
@@ -41,6 +43,7 @@ async function run() {
core.endGroup(); core.endGroup();
core.setOutput('tags', tags.join(inputs.sepTags)); core.setOutput('tags', tags.join(inputs.sepTags));
// Docker labels
const labels: Array<string> = meta.labels(); const labels: Array<string> = meta.labels();
core.startGroup(`Docker labels`); core.startGroup(`Docker labels`);
for (let label of labels) { for (let label of labels) {
@@ -48,6 +51,13 @@ async function run() {
} }
core.endGroup(); core.endGroup();
core.setOutput('labels', labels.join(inputs.sepLabels)); core.setOutput('labels', labels.join(inputs.sepLabels));
// Bake definition file
const bakeFile: string = meta.bakeFile();
core.startGroup(`Bake definition file`);
core.info(fs.readFileSync(bakeFile, 'utf8'));
core.endGroup();
core.setOutput('bake-file', bakeFile);
} catch (error) { } catch (error) {
core.setFailed(error.message); core.setFailed(error.message);
} }

View File

@@ -1,7 +1,9 @@
import * as handlebars from 'handlebars'; import * as handlebars from 'handlebars';
import * as moment from 'moment'; import * as fs from 'fs';
import * as path from 'path';
import moment from 'moment';
import * as semver from 'semver'; import * as semver from 'semver';
import {Inputs} from './context'; import {Inputs, tmpDir} from './context';
import * as core from '@actions/core'; import * as core from '@actions/core';
import {Context} from '@actions/github/lib/context'; import {Context} from '@actions/github/lib/context';
import {ReposGetResponseData} from '@octokit/types'; import {ReposGetResponseData} from '@octokit/types';
@@ -130,7 +132,7 @@ export class Meta {
} }
public labels(): Array<string> { public labels(): Array<string> {
return [ let labels: Array<string> = [
`org.opencontainers.image.title=${this.repo.name || ''}`, `org.opencontainers.image.title=${this.repo.name || ''}`,
`org.opencontainers.image.description=${this.repo.description || ''}`, `org.opencontainers.image.description=${this.repo.description || ''}`,
`org.opencontainers.image.url=${this.repo.html_url || ''}`, `org.opencontainers.image.url=${this.repo.html_url || ''}`,
@@ -140,5 +142,37 @@ export class Meta {
`org.opencontainers.image.revision=${this.context.sha || ''}`, `org.opencontainers.image.revision=${this.context.sha || ''}`,
`org.opencontainers.image.licenses=${this.repo.license?.spdx_id || ''}` `org.opencontainers.image.licenses=${this.repo.license?.spdx_id || ''}`
]; ];
labels.push(...this.inputs.labelCustom);
return labels;
}
public bakeFile(): string {
let jsonLabels = {};
for (let label of this.labels()) {
const matches = label.match(/([^=]*)=(.*)/);
if (!matches) {
continue;
}
jsonLabels[matches[1]] = matches[2];
}
const bakeFile = path.join(tmpDir(), 'ghaction-docker-meta-bake.json').split(path.sep).join(path.posix.sep);
fs.writeFileSync(
bakeFile,
JSON.stringify(
{
target: {
'ghaction-docker-meta': {
tags: this.tags(),
labels: jsonLabels
}
}
},
null,
2
)
);
return bakeFile;
} }
} }

38
test/docker-bake.hcl Normal file
View File

@@ -0,0 +1,38 @@
target "ghaction-docker-meta" {}
group "default" {
targets = ["db", "app"]
}
group "release" {
targets = ["db", "app-plus"]
}
target "db" {
context = "./test"
tags = ["docker.io/tonistiigi/db"]
}
target "app" {
inherits = ["ghaction-docker-meta"]
context = "./test"
dockerfile = "Dockerfile"
args = {
name = "foo"
}
}
target "cross" {
platforms = [
"linux/amd64",
"linux/arm64",
"linux/386"
]
}
target "app-plus" {
inherits = ["app", "cross"]
args = {
IAMPLUS = "true"
}
}

View File

@@ -11,9 +11,11 @@
"rootDir": "./src", "rootDir": "./src",
"strict": true, "strict": true,
"noImplicitAny": false, "noImplicitAny": false,
"esModuleInterop": false, "esModuleInterop": true,
"resolveJsonModule": true,
"sourceMap": true "sourceMap": true
}, },
"exclude": ["node_modules", "**/*.test.ts"] "exclude": [
"node_modules",
"**/*.test.ts"
]
} }

View File

@@ -1176,6 +1176,11 @@ cssstyle@^2.2.0:
dependencies: dependencies:
cssom "~0.3.6" cssom "~0.3.6"
csv-parse@^4.14.2:
version "4.14.2"
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.14.2.tgz#c1329cff95a99b8773a92c4e62f8bff114b34726"
integrity sha512-YE2xlTKtM035/94llhgsp9qFQxGi47EkQJ1pZ+mLT/98GpIsbjkMGAb7Rmu9hNxVfYFOLf10hP+rPVqnoccLgw==
dashdash@^1.12.0: dashdash@^1.12.0:
version "1.14.1" version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@@ -2720,9 +2725,9 @@ node-modules-regexp@^1.0.0:
integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
node-notifier@^8.0.0: node-notifier@^8.0.0:
version "8.0.0" version "8.0.1"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.0.tgz#a7eee2d51da6d0f7ff5094bc7108c911240c1620" resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.1.tgz#f86e89bbc925f2b068784b31f382afdc6ca56be1"
integrity sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA== integrity sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==
dependencies: dependencies:
growly "^1.3.0" growly "^1.3.0"
is-wsl "^2.2.0" is-wsl "^2.2.0"
@@ -3662,9 +3667,9 @@ uuid@^3.3.2:
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^8.3.0: uuid@^8.3.0:
version "8.3.1" version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-to-istanbul@^6.0.1: v8-to-istanbul@^6.0.1:
version "6.0.1" version "6.0.1"