Compare commits

..

90 Commits

Author SHA1 Message Date
CrazyMax
e1b7f96249 Merge pull request #338 from crazy-max/network
Add network input
2021-04-06 23:42:58 +02:00
CrazyMax
5a4a26c0fc Add network input
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-04-06 14:49:15 +02:00
CrazyMax
8891861577 Merge pull request #337 from crazy-max/sort-inputs
Sort inputs
2021-04-06 13:56:35 +02:00
CrazyMax
f6a733366a Sort inputs
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-04-06 13:55:04 +02:00
CrazyMax
eb4f14646c Merge pull request #331 from crazy-max/doc
Fix doc links
2021-04-06 13:11:23 +02:00
CrazyMax
646552f0a1 Fix doc links
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-04-03 20:41:36 +02:00
CrazyMax
2ec8f1d346 Merge pull request #328 from crazy-max/github-serverurl
Handle GitHub server URL for default context
2021-04-01 23:59:21 +02:00
CrazyMax
8f5c91aad9 Handle GitHub server URL for default context
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-04-01 20:07:51 +02:00
CrazyMax
6a12baa867 Merge pull request #325 from docker/dependabot/npm_and_yarn/y18n-4.0.1
Bump y18n from 4.0.0 to 4.0.1
2021-03-30 13:13:45 +02:00
dependabot[bot]
da940a9403 Bump y18n from 4.0.0 to 4.0.1
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-29 21:52:47 +00:00
CrazyMax
9cf6eb2b16 Merge pull request #318 from docker/dependabot/npm_and_yarn/semver-7.3.5
Bump semver from 7.3.4 to 7.3.5
2021-03-29 23:51:27 +02:00
CrazyMax
3c2d8e5269 Update generated content
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-03-29 23:47:36 +02:00
CrazyMax
d693655c74 Merge pull request #323 from crazy-max/meta-v2
Meta action v2
2021-03-29 22:22:23 +02:00
CrazyMax
bfea497a8e Merge pull request #322 from crazy-max/workflow
Enhance workflow
2021-03-29 22:09:29 +02:00
CrazyMax
8ca2ca55d4 Meta action v2
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-03-29 14:33:52 +02:00
CrazyMax
460e0e47f5 Enhance workflow
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-03-28 16:43:57 +02:00
dependabot[bot]
edb2e175f1 Bump semver from 7.3.4 to 7.3.5
Bumps [semver](https://github.com/npm/node-semver) from 7.3.4 to 7.3.5.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.3.4...v7.3.5)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-23 05:56:49 +00:00
CrazyMax
09f8407c80 Merge pull request #303 from docker/dependabot/npm_and_yarn/csv-parse-4.15.3
Bump csv-parse from 4.15.1 to 4.15.3
2021-03-18 20:50:28 +01:00
dependabot[bot]
614833a85f Bump csv-parse from 4.15.1 to 4.15.3
Bumps [csv-parse](https://github.com/wdavidw/node-csv-parse) from 4.15.1 to 4.15.3.
- [Release notes](https://github.com/wdavidw/node-csv-parse/releases)
- [Changelog](https://github.com/adaltas/node-csv-parse/blob/master/CHANGELOG.md)
- [Commits](https://github.com/wdavidw/node-csv-parse/compare/v4.15.1...v4.15.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-18 19:48:26 +00:00
CrazyMax
0ec1157bb5 Merge pull request #305 from TanguyChiffoleau/master
Fix wrong link for login action repo
2021-02-24 23:55:51 +01:00
Tanguy Chiffoleau
22d49d64f5 Fix wrong link for login action repo
Signed-off-by: TanguyChiffoleau <55456592+TanguyChiffoleau@users.noreply.github.com>
2021-02-24 23:44:08 +01:00
CrazyMax
9379083e42 Merge pull request #299 from crazy-max/split-docs
Enhance documentation
2021-02-17 19:08:07 +01:00
CrazyMax
a63b18dea2 Enhance documentation
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-17 18:53:20 +01:00
CrazyMax
af867d4937 Merge pull request #296 from crazy-max/secret-file
Allow to use secret file mount
2021-02-16 13:15:29 +01:00
CrazyMax
33eec1587d Update action.yml
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-16 11:56:02 +01:00
CrazyMax
3db4797dd2 Merge pull request #298 from crazy-max/virtual-env
Enhance virtual-env workflow
2021-02-15 20:48:57 +01:00
CrazyMax
659fcba376 Enhance virtual-env workflow
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-15 20:39:21 +01:00
CrazyMax
080cadd33e Allow to use secret file mount
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-15 10:08:25 +01:00
CrazyMax
dc4c1fca8b Merge pull request #297 from crazy-max/labels
Remove label workflow
2021-02-14 23:17:33 +01:00
CrazyMax
b280b0485b Merge pull request #287 from docker/dependabot/npm_and_yarn/csv-parse-4.15.1
Bump csv-parse from 4.14.2 to 4.15.1
2021-02-14 23:13:24 +01:00
CrazyMax
b87564a5cc Remove label workflow
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-14 23:12:36 +01:00
CrazyMax
d2bc6a5d16 Update generated content
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-14 22:41:16 +01:00
CrazyMax
e5f26cdae4 Merge pull request #295 from crazy-max/update-buildx
Update buildx
2021-02-14 22:30:37 +01:00
CrazyMax
616efcd405 Update buildx
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-14 22:28:22 +01:00
dependabot[bot]
0539e1a717 Bump csv-parse from 4.14.2 to 4.15.1
Bumps [csv-parse](https://github.com/wdavidw/node-csv-parse) from 4.14.2 to 4.15.1.
- [Release notes](https://github.com/wdavidw/node-csv-parse/releases)
- [Changelog](https://github.com/adaltas/node-csv-parse/blob/master/CHANGELOG.md)
- [Commits](https://github.com/wdavidw/node-csv-parse/compare/v4.14.2...v4.15.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-02 05:59:48 +00:00
Tõnis Tiigi
636b4540ec Merge pull request #273 from crazy-max/fix-workflow
Fix workflow for auto-push impl
2021-01-15 19:36:02 -08:00
CrazyMax
af932bfb2e Fix workflow for auto-push impl
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-01-15 19:21:19 +01:00
CrazyMax
2db03de115 Merge pull request #272 from crazy-max/virtual-env
Add virtual-env workflow
2021-01-15 19:17:18 +01:00
CrazyMax
4643aec7c4 Add virtual-env workflow
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-01-15 19:13:45 +01:00
CrazyMax
4a531fa5a6 Merge pull request #267 from agabani/patch-1
Fix README
2021-01-01 22:41:37 +01:00
Ahmed Agabani
565d16e074 Fix README
Signed-off-by: agabani <agabani@users.noreply.github.com>
2021-01-01 03:26:46 +00:00
Tõnis Tiigi
c473874c2c Merge pull request #244 from liboz/master
Use default behavior for file flag
2020-12-29 11:17:34 -08:00
CrazyMax
b94cedd686 Merge pull request #266 from crazy-max/add-labels
Add registry issue labels
2020-12-29 19:06:18 +01:00
CrazyMax
76c8b42a58 Add registry issue labels
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-29 18:51:10 +01:00
CrazyMax
920f0da143 Merge pull request #261 from crazy-max/e2e-gar
Add e2e tests for GAR
2020-12-22 17:44:55 +01:00
CrazyMax
e723b420bf Add e2e tests for GAR
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-22 17:42:19 +01:00
CrazyMax
f9deaa080c Merge pull request #260 from crazy-max/e2e-gcr
Add e2e tests for GCR
2020-12-22 11:24:13 +01:00
CrazyMax
b4c22c3e33 Add e2e tests for GCR
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-22 11:21:32 +01:00
Libo Zeng
a8587cb818 use default docker command line values for file when it is missing
Signed-off-by: Libo Zeng <libo@mabl.com>
2020-12-18 10:49:11 -05:00
CrazyMax
f2a733f179 Merge pull request #255 from docker/dependabot/npm_and_yarn/csv-parse-4.14.2
Bump csv-parse from 4.14.1 to 4.14.2
2020-12-17 23:19:07 +01:00
CrazyMax
35ab0dd217 Update generated content
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-17 23:16:55 +01:00
CrazyMax
46d5afd128 Merge pull request #257 from crazy-max/fix-public-ecr
Fix public ECR slug and add cache to registry
2020-12-17 15:27:43 +01:00
CrazyMax
a8bb35be5a Fix public ECR slug and add cache to regitry
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-17 14:56:10 +01:00
CrazyMax
5c278cd8ab Merge pull request #256 from crazy-max/e2e-ecr
Add e2e tests for ECR
2020-12-17 12:11:44 +01:00
CrazyMax
3b98ff3c03 Add e2e tests for ECR
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-17 12:06:34 +01:00
dependabot[bot]
6b88c3e647 Bump csv-parse from 4.14.1 to 4.14.2
Bumps [csv-parse](https://github.com/wdavidw/node-csv-parse) from 4.14.1 to 4.14.2.
- [Release notes](https://github.com/wdavidw/node-csv-parse/releases)
- [Changelog](https://github.com/adaltas/node-csv-parse/blob/master/CHANGELOG.md)
- [Commits](https://github.com/wdavidw/node-csv-parse/compare/v4.14.1...v4.14.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-17 06:18:28 +00:00
CrazyMax
0db984c182 Merge pull request #250 from crazy-max/master
Typo in README
2020-12-05 03:53:54 +01:00
CrazyMax
35e3637576 Typo in README
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-05 03:52:01 +01:00
CrazyMax
a29353b5c7 Merge pull request #243 from docker/dependabot/npm_and_yarn/semver-7.3.4
Bump semver from 7.3.2 to 7.3.4
2020-12-05 03:50:01 +01:00
CrazyMax
241c03788f Update generated content
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-05 03:45:58 +01:00
CrazyMax
a6ea296fed Merge pull request #249 from crazy-max/trim-inputlist-items
Trim input list items
2020-12-05 03:42:27 +01:00
CrazyMax
13137a8f9b Trim input list items
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-05 03:40:44 +01:00
CrazyMax
22b2fa68fc Merge pull request #246 from malkam03/h-doubled-quotes
Update documentation on escaping quotes
2020-12-03 23:41:19 +01:00
Malcolm Davis Steele
9ada3141a9 Apply suggestions
Signed-off-by: Malcolm Davis Steele <me@malcolmdavis.xyz>
2020-12-03 16:39:04 -06:00
Malcolm Davis Steele
e53bafea73 Update documentation on escaping quotes
Signed-off-by: Malcolm Davis Steele <me@malcolmdavis.xyz>
2020-12-03 16:25:25 -06:00
dependabot[bot]
b9335d6c83 Bump semver from 7.3.2 to 7.3.4
Bumps [semver](https://github.com/npm/node-semver) from 7.3.2 to 7.3.4.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.3.2...v7.3.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-02 06:19:44 +00:00
CrazyMax
d51711af0d Merge pull request #241 from crazy-max/fix-readme
Fix README
2020-11-30 13:32:46 +01:00
CrazyMax
b0a38c7db9 Fix README
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-11-30 13:29:17 +01:00
CrazyMax
6925f94b6b Merge pull request #236 from crazy-max/master
Fix e2e workflow syntax
2020-11-19 23:58:10 +01:00
CrazyMax
bf3d577ea5 Fix e2e workflow syntax
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-11-19 23:56:28 +01:00
CrazyMax
1f1cc26e46 Merge pull request #235 from crazy-max/e2e
Add e2e tests
2020-11-19 23:52:02 +01:00
CrazyMax
3c98919e7f Add e2e tests
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-11-19 10:40:39 +01:00
Tõnis Tiigi
eae00c3028 Merge pull request #233 from crazy-max/secret-multiline
Handle multi-line secret value
2020-11-17 12:45:24 -08:00
CrazyMax
1471dfb80d Handle multi-line secret value
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-11-17 21:38:51 +01:00
CrazyMax
9c13ff40b3 Merge pull request #231 from docker/dependabot/npm_and_yarn/csv-parse-4.14.1
Bump csv-parse from 4.14.0 to 4.14.1
2020-11-17 10:19:52 +01:00
CrazyMax
61a74b1e3a Merge pull request #232 from crazy-max/actions-major
Use major version of actions
2020-11-17 10:11:11 +01:00
CrazyMax
d3ddc4b4ef Use major version of actions
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-11-17 10:06:56 +01:00
dependabot[bot]
50caab8424 Bump csv-parse from 4.14.0 to 4.14.1
Bumps [csv-parse](https://github.com/wdavidw/node-csv-parse) from 4.14.0 to 4.14.1.
- [Release notes](https://github.com/wdavidw/node-csv-parse/releases)
- [Changelog](https://github.com/adaltas/node-csv-parse/blob/master/CHANGELOG.md)
- [Commits](https://github.com/wdavidw/node-csv-parse/compare/v4.14.0...v4.14.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-17 06:17:23 +00:00
CrazyMax
d971423a6f Merge pull request #221 from andygrunwald/fix-label-opencontainers.image.source
Set container label "org.opencontainers.image.source" to establish a repository connection on github
2020-11-08 20:55:03 +01:00
Andy Grunwald
ae5ee4ca11 Update UPGRADE.md: Use github.event.repository.html_url to craft the repository url
Co-authored-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
Signed-off-by: Andy Grunwald <andygrunwald@gmail.com>
2020-11-08 20:50:59 +01:00
Andy Grunwald
3c6bad5f82 Set container label "org.opencontainers.image.source" to establish a repository connection on github
Signed-off-by: Andy Grunwald <andygrunwald@gmail.com>
2020-11-08 20:16:03 +01:00
CrazyMax
6e1d94b6b3 Merge pull request #218 from docker/dependabot/npm_and_yarn/csv-parse-4.14.0
Bump csv-parse from 4.12.0 to 4.14.0
2020-11-06 15:07:33 +01:00
CrazyMax
11ca7847e4 Merge pull request #216 from docker/dependabot/github_actions/actions/checkout-v2.3.4
Bump actions/checkout from v2.3.3 to v2.3.4
2020-11-06 14:57:33 +01:00
CrazyMax
35f1834293 Update generated content
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-11-06 14:56:57 +01:00
CrazyMax
d651be4597 Merge pull request #189 from jtomaszewski/patch-1
docs(README): Improve example of `file` option so it's obvious that it is not relative to `context` option
2020-11-06 14:34:22 +01:00
Jacek Tomaszewski
8832f2902d docs(README): Improve example of file option so it's obvious that it is not relative to context option
Signed-off-by: Jacek Tomaszewski <jacek@jtom.me>
2020-11-06 14:26:21 +01:00
dependabot[bot]
b6150991af Bump csv-parse from 4.12.0 to 4.14.0
Bumps [csv-parse](https://github.com/wdavidw/node-csv-parse) from 4.12.0 to 4.14.0.
- [Release notes](https://github.com/wdavidw/node-csv-parse/releases)
- [Changelog](https://github.com/adaltas/node-csv-parse/blob/master/CHANGELOG.md)
- [Commits](https://github.com/wdavidw/node-csv-parse/compare/v4.12.0...v4.14.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-06 06:22:06 +00:00
dependabot[bot]
6b0583b656 Bump actions/checkout from v2.3.3 to v2.3.4
Bumps [actions/checkout](https://github.com/actions/checkout) from v2.3.3 to v2.3.4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.3.3...5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-04 06:20:47 +00:00
CrazyMax
f96d0fb6b7 Merge pull request #213 from danlester/patch-1
Note that build_args has been renamed build-args
2020-11-03 21:11:08 +01:00
Dan Lester
9e2f4416f3 Note that build_args has been renamed build-args
Signed-off-by: Dan Lester <dan@danlester.com>
2020-11-03 19:45:45 +00:00
38 changed files with 2822 additions and 1214 deletions

View File

@@ -1 +1,2 @@
node_modules /coverage
/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://docs.github.com/en/github/site-policy/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/docker/build-push-action/fork) and clone the repository 1. [Fork](https://github.com/docker/build-push-action/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/docker/build-push-action/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/docker/build-push-action/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:
@@ -40,5 +27,5 @@ Here are a few things you can do that will increase the likelihood of your pull
## Resources ## Resources
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) - [Using Pull Requests](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests)
- [GitHub Help](https://help.github.com) - [GitHub Help](https://docs.github.com/en)

View File

@@ -34,4 +34,4 @@ Before sumbitting a bug report please read the [Troubleshooting doc](https://git
### Logs ### Logs
> Download the [log file of your build](https://help.github.com/en/actions/configuring-and-managing-workflows/managing-a-workflow-run#downloading-logs) and [attach it](https://help.github.com/en/github/managing-your-work-on-github/file-attachments-on-issues-and-pull-requests) to this issue. > Download the [log file of your build](https://docs.github.com/en/actions/managing-workflow-runs/using-workflow-run-logs#downloading-logs) and [attach it](https://docs.github.com/en/github/managing-your-work-on-github/file-attachments-on-issues-and-pull-requests) to this issue.

79
.github/labels.yml vendored
View File

@@ -1,79 +0,0 @@
## more info https://github.com/crazy-max/ghaction-github-labeler
- # automerge
name: ":bell: automerge"
color: "8f4fbc"
description: ""
- # bot
name: ":robot: bot"
color: "69cde9"
description: ""
- # bug
name: ":bug: bug"
color: "b60205"
description: ""
- # dependencies
name: ":game_die: dependencies"
color: "0366d6"
description: ""
from_name: "dependencies"
- # documentation
name: ":memo: documentation"
color: "c5def5"
description: ""
- # duplicate
name: ":busts_in_silhouette: duplicate"
color: "cccccc"
description: ""
- # enhancement
name: ":sparkles: enhancement"
color: "0054ca"
description: ""
- # feature request
name: ":bulb: feature request"
color: "0e8a16"
description: ""
- # feedback
name: ":mega: feedback"
color: "03a9f4"
description: ""
- # future maybe
name: ":rocket: future maybe"
color: "fef2c0"
description: ""
- # good first issue
name: ":hatching_chick: good first issue"
color: "7057ff"
description: ""
- # help wanted
name: ":pray: help wanted"
color: "4caf50"
description: ""
- # hold
name: ":hand: hold"
color: "24292f"
description: ""
- # invalid
name: ":no_entry_sign: invalid"
color: "e6e6e6"
description: ""
- # maybe bug
name: ":interrobang: maybe bug"
color: "ff5722"
description: ""
- # needs more info
name: ":thinking: needs more info"
color: "795548"
description: ""
- # question
name: ":question: question"
color: "3f51b5"
description: ""
from_name: "question"
- # upstream
name: ":eyes: upstream"
color: "fbca04"
description: ""
- # wontfix
name: ":coffin: wontfix"
color: "ffffff"
description: ""

View File

@@ -15,7 +15,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2.3.3 uses: actions/checkout@v2
with: with:
path: action path: action
- -
@@ -41,7 +41,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2.3.3 uses: actions/checkout@v2
with: with:
path: action path: action
- -
@@ -95,7 +95,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2.3.3 uses: actions/checkout@v2
with: with:
path: action path: action
- -
@@ -121,6 +121,14 @@ jobs:
localhost:5000/name/app:1.0.0 localhost:5000/name/app:1.0.0
secrets: | secrets: |
GIT_AUTH_TOKEN=${{ github.token }} GIT_AUTH_TOKEN=${{ github.token }}
"MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc"
FOO=bar
"EMPTYLINE=aaaa
bbbb
ccc"
- -
name: Inspect name: Inspect
run: | run: |
@@ -156,7 +164,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2.3.3 uses: actions/checkout@v2
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
@@ -203,7 +211,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2.3.3 uses: actions/checkout@v2
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
@@ -236,12 +244,6 @@ jobs:
docker-driver: docker-driver:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
push:
- true
- false
services: services:
registry: registry:
image: registry:2 image: registry:2
@@ -250,28 +252,16 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2.3.3 uses: actions/checkout@v2
- -
name: Build name: Build
id: docker_build id: docker_build
continue-on-error: ${{ matrix.push }}
uses: ./ uses: ./
with: with:
context: ./test context: ./test
file: ./test/Dockerfile file: ./test/Dockerfile
push: ${{ matrix.push }} push: true
tags: localhost:5000/name/app:latest tags: localhost:5000/name/app:latest
-
name: Check
run: |
echo "${{ toJson(steps.docker_build) }}"
if [ "${{ matrix.push }}" = "false" ]; then
exit 0
fi
if [ "${{ steps.docker_build.outcome }}" != "failure" ] || [ "${{ steps.docker_build.conclusion }}" != "success" ]; then
echo "::error::Should have failed"
exit 1
fi
- -
name: Dump context name: Dump context
if: always() if: always()
@@ -282,7 +272,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2.3.3 uses: actions/checkout@v2
- -
name: Build name: Build
uses: ./ uses: ./
@@ -300,6 +290,30 @@ jobs:
if: always() if: always()
uses: crazy-max/ghaction-dump-context@v1 uses: crazy-max/ghaction-dump-context@v1
network:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: List networks
run: docker network ls
-
name: Build
uses: ./
with:
context: ./test
tags: name/app:latest
network: host
-
name: Dump context
if: always()
uses: crazy-max/ghaction-dump-context@v1
multi: multi:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@@ -319,7 +333,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2.3.3 uses: actions/checkout@v2
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
@@ -336,7 +350,7 @@ jobs:
uses: ./ uses: ./
with: with:
context: ./test context: ./test
file: ./test/Dockerfile-${{ matrix.dockerfile }} file: ./test/${{ matrix.dockerfile }}.Dockerfile
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
@@ -372,7 +386,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2.3.3 uses: actions/checkout@v2
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
@@ -389,7 +403,7 @@ jobs:
uses: ./ uses: ./
with: with:
context: ./test context: ./test
file: ./test/Dockerfile-multi file: ./test/multi.Dockerfile
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
@@ -422,7 +436,7 @@ jobs:
uses: ./ uses: ./
with: with:
context: ./test context: ./test
file: ./test/Dockerfile-multi file: ./test/multi.Dockerfile
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
@@ -470,7 +484,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2.3.3 uses: actions/checkout@v2
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
@@ -499,7 +513,7 @@ jobs:
uses: ./ uses: ./
with: with:
context: ./test context: ./test
file: ./test/Dockerfile-multi file: ./test/multi.Dockerfile
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
@@ -538,7 +552,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2.3.3 uses: actions/checkout@v2
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
@@ -564,7 +578,7 @@ jobs:
uses: ./ uses: ./
with: with:
context: ./test context: ./test
file: ./test/Dockerfile-multi file: ./test/multi.Dockerfile
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true

105
.github/workflows/e2e.yml vendored Normal file
View File

@@ -0,0 +1,105 @@
name: e2e
on:
workflow_dispatch:
schedule:
- cron: '0 10 * * *' # everyday at 10am
push:
branches:
- master
tags:
- v*
jobs:
docker:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
-
registry: ''
slug: ghactionstest/ghactionstest
username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN
-
registry: ghcr.io
slug: ghcr.io/docker-ghactiontest/test
username_secret: GHCR_USERNAME
password_secret: GHCR_PAT
-
registry: registry.gitlab.com
slug: registry.gitlab.com/test1716/test
username_secret: GITLAB_USERNAME
password_secret: GITLAB_TOKEN
-
registry: 175142243308.dkr.ecr.us-east-2.amazonaws.com
slug: 175142243308.dkr.ecr.us-east-2.amazonaws.com/sandbox/test-docker-action
username_secret: AWS_ACCESS_KEY_ID
password_secret: AWS_SECRET_ACCESS_KEY
-
registry: public.ecr.aws
slug: public.ecr.aws/q3b5f1u4/test-docker-action
username_secret: AWS_ACCESS_KEY_ID
password_secret: AWS_SECRET_ACCESS_KEY
-
registry: us-east4-docker.pkg.dev
slug: us-east4-docker.pkg.dev/sandbox-298914/docker-official-github-actions/test-docker-action
username_secret: GAR_USERNAME
password_secret: GAR_JSON_KEY
-
registry: gcr.io
slug: gcr.io/sandbox-298914/test-docker-action
username_secret: GCR_USERNAME
password_secret: GCR_JSON_KEY
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
id: meta
uses: crazy-max/ghaction-docker-meta@v2
with:
images: ${{ matrix.slug }}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
registry: ${{ matrix.registry }}
username: ${{ secrets[matrix.username_secret] }}
password: ${{ secrets[matrix.password_secret] }}
-
name: Build and push
uses: ./
with:
context: ./test
file: ./test/multi.Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ matrix.slug }}:master
cache-to: type=inline
-
name: Inspect image
if: github.event_name != 'pull_request'
run: |
docker pull ${{ matrix.slug }}:${{ steps.meta.outputs.version }}
docker image inspect ${{ matrix.slug }}:${{ steps.meta.outputs.version }}
-
name: Check manifest
if: github.event_name != 'pull_request'
run: |
docker buildx imagetools inspect ${{ matrix.slug }}:${{ steps.meta.outputs.version }}
-
name: Dump context
if: always()
uses: crazy-max/ghaction-dump-context@v1

View File

@@ -25,14 +25,21 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2.3.3 uses: actions/checkout@v2
- -
name: Docker meta name: Docker meta
id: docker_meta id: meta
uses: crazy-max/ghaction-docker-meta@v1 uses: crazy-max/ghaction-docker-meta@v2
with: with:
images: ${{ env.DOCKER_IMAGE }} # list of Docker images to use as base name for tags images: ${{ env.DOCKER_IMAGE }}
tag-sha: true # add git short SHA as Docker tag tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v1
@@ -45,8 +52,8 @@ jobs:
context: ./test context: ./test
file: ./test/Dockerfile file: ./test/Dockerfile
load: true load: true
tags: ${{ steps.docker_meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
- -
name: Build and push to local registry name: Build and push to local registry
uses: ./ uses: ./
@@ -54,17 +61,17 @@ jobs:
context: ./test context: ./test
file: ./test/Dockerfile file: ./test/Dockerfile
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
- -
name: Inspect image name: Inspect image
run: | run: |
docker image inspect ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} docker image inspect ${{ env.DOCKER_IMAGE }}:${{ steps.meta.outputs.version }}
- -
name: Check manifest name: Check manifest
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
run: | run: |
docker buildx imagetools inspect ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} docker buildx imagetools inspect ${{ env.DOCKER_IMAGE }}:${{ steps.meta.outputs.version }}
- -
name: Dump context name: Dump context
if: always() if: always()

View File

@@ -1,20 +0,0 @@
name: labels
on:
push:
branches:
- 'master'
paths:
- '.github/labels.yml'
- '.github/workflows/labels.yml'
jobs:
labeler:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2.3.3
-
name: Run Labeler
uses: crazy-max/ghaction-github-labeler@v3.1.0

View File

@@ -3,41 +3,30 @@ name: test
on: on:
push: push:
branches: branches:
- master - 'master'
pull_request: pull_request:
branches: branches:
- master - 'master'
jobs: jobs:
test-containerized:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2.3.3
-
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.3.3 uses: actions/checkout@v2
- -
name: Install name: Validate
run: yarn install uses: docker/bake-action@v1
with:
targets: validate
- -
name: Test name: Test
run: yarn run test uses: docker/bake-action@v1
with:
targets: test
- -
name: Upload coverage name: Upload coverage
uses: codecov/codecov-action@v1.0.14 uses: codecov/codecov-action@v1
if: success()
with: with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage/clover.xml file: ./coverage/clover.xml

38
.github/workflows/virtual-env.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: virtual-env
on:
workflow_dispatch:
schedule:
- cron: '0 10 * * *' # everyday at 10am
jobs:
os:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- ubuntu-20.04
- ubuntu-18.04
- ubuntu-16.04
steps:
-
name: List install packages
run: apt list --installed
-
name: Docker info
run: docker info
-
name: Docker version
run: docker version
-
name: buildx version
run: docker buildx version
-
name: containerd version
run: containerd --version
-
name: Dump context
if: always()
uses: crazy-max/ghaction-dump-context@v1

View File

@@ -1,52 +0,0 @@
#syntax=docker/dockerfile:1.1-experimental
FROM node:12 AS deps
WORKDIR /src
COPY package.json yarn.lock ./
RUN --mount=type=cache,target=/usr/local/share/.cache/yarn \
yarn install
FROM scratch AS update-yarn
COPY --from=deps /src/yarn.lock /
FROM deps AS validate-yarn
COPY .git .git
RUN status=$(git status --porcelain -- yarn.lock); if [ -n "$status" ]; then echo $status; exit 1; fi
FROM deps AS base
COPY . .
FROM base AS build
RUN yarn build
FROM deps AS test
COPY --from=docker /usr/local/bin/docker /usr/bin/
ARG TARGETOS
ARG TARGETARCH
ARG BUILDX_VERSION=v0.4.2
ENV RUNNER_TEMP=/tmp/github_runner
ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache
RUN mkdir -p /usr/local/lib/docker/cli-plugins && \
curl -fsSL https://github.com/docker/buildx/releases/download/$BUILDX_VERSION/buildx-$BUILDX_VERSION.$TARGETOS-$TARGETARCH > /usr/local/lib/docker/cli-plugins/docker-buildx && \
chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx && \
docker buildx version
COPY . .
RUN yarn run test
FROM base AS run-format
RUN yarn run format
FROM scratch AS format
COPY --from=run-format /src/src/*.ts /src/
FROM base AS validate-format
RUN yarn run format-check
FROM scratch AS dist
COPY --from=build /src/dist/ /dist/
FROM build AS validate-build
RUN status=$(git status --porcelain -- dist); if [ -n "$status" ]; then echo $status; exit 1; fi
FROM base AS dev
ENTRYPOINT ["bash"]

555
README.md
View File

@@ -6,23 +6,18 @@
## Upgrade from v1 ## Upgrade from v1
`v2` of this action includes significant updates and now uses Docker [Buildx](https://github.com/docker/buildx). It `v2` of this action includes significant updates and now uses Docker [Buildx](https://github.com/docker/buildx). It's
works with 3 new actions ([login](https://github.com/docker/login-action), [setup-buildx](https://github.com/docker/setup-buildx-action) also rewritten as a [typescript-action](https://github.com/actions/typescript-action/) to be as close as possible
and [setup-qemu](https://github.com/docker/setup-qemu-action)) that we have created. It's also rewritten as a of the [GitHub Runner](https://github.com/actions/virtual-environments) during its execution.
[typescript-action](https://github.com/actions/typescript-action/) to be as closed as possible of the
[GitHub Runner](https://github.com/actions/virtual-environments) during its execution.
[Upgrade notes](UPGRADE.md) and many [usage examples](#usage) have been added to handle most use cases but `v1` is [Upgrade notes](UPGRADE.md) with many [usage examples](#advanced-usage) have been added to handle most use cases but
still available through [`releases/v1` branch](https://github.com/docker/build-push-action/tree/releases/v1). `v1` is still available through [`releases/v1` branch](https://github.com/docker/build-push-action/tree/releases/v1).
## About ## About
GitHub Action to build and push Docker images with [Buildx](https://github.com/docker/buildx). GitHub Action to build and push Docker images with [Buildx](https://github.com/docker/buildx) with full support of the
features provided by [Moby BuildKit](https://github.com/moby/buildkit) builder toolkit. This includes multi-platform
> :bulb: See also: build, secrets, remote cache, etc. and different builder deployment/namespacing options.
> * [login](https://github.com/docker/login-action) action
> * [setup-buildx](https://github.com/docker/setup-buildx-action) action
> * [setup-qemu](https://github.com/docker/setup-qemu-action) action
![Screenshot](.github/build-push-action.png) ![Screenshot](.github/build-push-action.png)
@@ -31,16 +26,18 @@ ___
* [Usage](#usage) * [Usage](#usage)
* [Git context](#git-context) * [Git context](#git-context)
* [Path context](#path-context) * [Path context](#path-context)
* [Isolated builders](#isolated-builders)
* [Multi-platform image](#multi-platform-image)
* [Advanced usage](#advanced-usage) * [Advanced usage](#advanced-usage)
* [Push to multi-registries](#push-to-multi-registries) * [Multi-platform image](docs/advanced/multi-platform.md)
* [Cache to registry](#push-to-multi-registries) * [Secrets](docs/advanced/secrets.md)
* [Local registry](#local-registry) * [Isolated builders](docs/advanced/isolated-builders.md)
* [Export image to Docker](#export-image-to-docker) * [Push to multi-registries](docs/advanced/push-multi-registries.md)
* [Leverage GitHub cache](#leverage-github-cache) * [Cache](docs/advanced/cache.md)
* [Handle tags and labels](#handle-tags-and-labels) * [Registry cache](docs/advanced/cache.md#registry-cache)
* [Update DockerHub repo description](#update-dockerhub-repo-description) * [GitHub cache](docs/advanced/cache.md#github-cache)
* [Local registry](docs/advanced/local-registry.md)
* [Export image to Docker](docs/advanced/export-docker.md)
* [Handle tags and labels](docs/advanced/tags-labels.md)
* [Update DockerHub repo description](docs/advanced/dockerhub-desc.md)
* [Customizing](#customizing) * [Customizing](#customizing)
* [inputs](#inputs) * [inputs](#inputs)
* [outputs](#outputs) * [outputs](#outputs)
@@ -50,25 +47,37 @@ ___
## Usage ## Usage
This action uses our [setup-buildx](https://github.com/docker/setup-buildx-action) action that extends the By default, this action uses the [Git context](#git-context) so you don't need to use the
`docker build` command named [buildx](https://github.com/docker/buildx) with the full support of the features [`actions/checkout`](https://github.com/actions/checkout/) action to checkout the repository because this will be
provided by [Moby BuildKit](https://github.com/moby/buildkit) builder toolkit. This includes multi-arch build, done directly by buildkit. The git reference will be based on the [event that triggered your workflow](https://docs.github.com/en/actions/reference/events-that-trigger-workflows)
build-secrets, remote cache, etc. and different builder deployment/namespacing options. and will result in the following context: `https://github.com/<owner>/<repo>.git#<ref>`.
Be careful because **any file mutation in the steps that precede the build step will be ignored** since
the context is based on the git reference. However, you can use the [Path context](#path-context) using the
[`context` input](#inputs) alongside the [`actions/checkout`](https://github.com/actions/checkout/) action to remove
this restriction.
In the examples below we are using 3 other actions:
* [`setup-buildx`](https://github.com/docker/setup-buildx-action) action will create and boot a builder using by
default the `docker-container` [builder driver](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#driver).
This is **not required but recommended** using it to be able to build multi-platform images, export cache, etc.
* [`setup-qemu`](https://github.com/docker/setup-qemu-action) action can be useful if you want
to add emulation support with QEMU to be able to build against more platforms.
* [`login`](https://github.com/docker/login-action) action will take care to log in against a Docker registry.
### Git context ### Git context
The default behavior of this action is to use the [Git context invoked](https://github.com/docker/build-push-action/blob/master/src/context.ts#L31-L35)
by your workflow.
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: master branches:
- 'master'
jobs: jobs:
main: docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
@@ -90,17 +99,14 @@ jobs:
with: with:
push: true push: true
tags: user/app:latest tags: user/app:latest
build-args: |
arg1=value1
arg2=value2
- -
name: Image digest name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }} run: echo ${{ steps.docker_build.outputs.digest }}
``` ```
Building from current repository automatically uses the [GitHub Token](https://help.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token) Building from the current repository automatically uses the [GitHub Token](https://help.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token)
as provided by `secrets` so it does not need to be passed. But if you want to authenticate against another private so it does not need to be passed. If you want to authenticate against another private repository, you have to use
repository, you have to use a secret named `GIT_AUTH_TOKEN` to be able to authenticate against it with buildx: a [secret](docs/advanced/secrets.md) named `GIT_AUTH_TOKEN` to be able to authenticate against it with buildx:
```yaml ```yaml
- -
@@ -114,24 +120,21 @@ repository, you have to use a secret named `GIT_AUTH_TOKEN` to be able to authen
GIT_AUTH_TOKEN=${{ secrets.MYTOKEN }} GIT_AUTH_TOKEN=${{ secrets.MYTOKEN }}
``` ```
> :warning: Subdir for Git context is [not yet supported](https://github.com/docker/build-push-action/issues/120). > :warning: Subdir for Git context is not yet supported ([moby/buildkit#1684](https://github.com/moby/buildkit/issues/1684))
> For the moment you can use the [path context](#path-context). > but you can use the [path context](#path-context) in the meantime. More info on [Docker docs website](https://docs.docker.com/engine/reference/commandline/build/#git-repositories).
> More info: https://docs.docker.com/engine/reference/commandline/build/#git-repositories
### Path context ### Path context
You can also use the `PATH` context alongside the [`actions/checkout`](https://github.com/actions/checkout/) action.
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: master branches:
- 'master'
jobs: jobs:
path-context: docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
@@ -154,435 +157,23 @@ jobs:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64,linux/386
push: true push: true
tags: user/app:latest tags: user/app:latest
``` ```
### Isolated builders
```yaml
name: ci
on:
push:
branches: master
jobs:
multi-builders:
runs-on: ubuntu-latest
steps:
-
uses: docker/setup-buildx-action@v1
id: builder1
-
uses: docker/setup-buildx-action@v1
id: builder2
-
name: Builder 1 name
run: echo ${{ steps.builder1.outputs.name }}
-
name: Builder 2 name
run: echo ${{ steps.builder2.outputs.name }}
-
name: Build against builder1
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder1.outputs.name }}
target: mytarget1
-
name: Build against builder2
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder2.outputs.name }}
target: mytarget2
```
### Multi-platform image
```yaml
name: ci
on:
push:
branches: master
jobs:
multi:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
push: true
tags: |
user/app:latest
user/app:1.0.0
```
## Advanced usage ## Advanced usage
### Push to multi-registries * [Multi-platform image](docs/advanced/multi-platform.md)
* [Secrets](docs/advanced/secrets.md)
The following workflow will connect you to [DockerHub](https://github.com/docker/login-action#dockerhub) * [Isolated builders](docs/advanced/isolated-builders.md)
and [GitHub Container Registry](https://github.com/docker/login-action#github-container-registry) and push the * [Push to multi-registries](docs/advanced/push-multi-registries.md)
image to these registries. * [Cache](docs/advanced/cache.md)
* [Registry cache](docs/advanced/cache.md#registry-cache)
<details> * [GitHub cache](docs/advanced/cache.md#github-cache)
<summary><b>Show workflow</b></summary> * [Local registry](docs/advanced/local-registry.md)
* [Export image to Docker](docs/advanced/export-docker.md)
```yaml * [Handle tags and labels](docs/advanced/tags-labels.md)
name: ci * [Update DockerHub repo description](docs/advanced/dockerhub-desc.md)
on:
push:
branches: master
jobs:
multi-registries:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.CR_PAT }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
push: true
tags: |
user/app:latest
user/app:1.0.0
ghcr.io/user/app:latest
ghcr.io/user/app:1.0.0
```
</details>
### Cache to registry
You can import/export cache from a cache manifest or (special) image configuration on the registry.
<details>
<summary><b>Show workflow</b></summary>
```yaml
name: ci
on:
push:
branches: master
jobs:
registry-cache:
runs-on: ubuntu-latest
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
push: true
tags: user/app:latest
cache-from: type=registry,ref=user/app:latest
cache-to: type=inline
```
</details>
### Local registry
For testing purposes you may need to create a [local registry](https://hub.docker.com/_/registry) to push images into:
<details>
<summary><b>Show workflow</b></summary>
```yaml
name: ci
on:
push:
branches: master
jobs:
local-registry:
runs-on: ubuntu-latest
services:
registry:
image: registry:2
ports:
- 5000:5000
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
driver-opts: network=host
-
name: Build and push to local registry
uses: docker/build-push-action@v2
with:
push: true
tags: localhost:5000/name/app:latest
-
name: Inspect
run: |
docker buildx imagetools inspect localhost:5000/name/app:latest
```
</details>
### Export image to Docker
You may want your build result to be available in the Docker client through `docker images` to be able to use it
in another step of your workflow:
<details>
<summary><b>Show workflow</b></summary>
```yaml
name: ci
on:
push:
branches: master
jobs:
export-docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Build
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
load: true
tags: myimage:latest
-
name: Inspect
run: |
docker image inspect myimage:latest
```
</details>
### Leverage GitHub cache
You can leverage [GitHub cache](https://docs.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows)
using [actions/cache](https://github.com/actions/cache) with this action:
<details>
<summary><b>Show workflow</b></summary>
```yaml
name: ci
on:
push:
branches: master
jobs:
github-cache:
runs-on: ubuntu-latest
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
push: true
tags: user/app:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
```
</details>
> If you want to [export layers for all stages](https://github.com/docker/buildx#--cache-tonametypetypekeyvalue),
> you have to specify `mode=max` attribute in `cache-to`.
### Handle tags and labels
If you come from [`v1`](https://github.com/docker/build-push-action/tree/releases/v1#readme) and want an
"automatic" tag management and [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/master/annotations.md)
for labels, you can do it in a dedicated step. The following workflow will use the [Docker meta action](https://github.com/crazy-max/ghaction-docker-meta)
to handle tags and labels based on GitHub actions events and Git metadata.
<details>
<summary><b>Show workflow</b></summary>
```yaml
name: ci
on:
schedule:
- cron: '0 10 * * *' # everyday at 10am
push:
branches:
- '**'
tags:
- 'v*.*.*'
pull_request:
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: name/app # list of Docker images to use as base name for tags
tag-sha: true # add git short SHA as Docker tag
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64,linux/386
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
```
</details>
### Update DockerHub repo description
You can update the [DockerHub repository description](https://docs.docker.com/docker-hub/repos/) using
a third-party action called [DockerHub Description](https://github.com/peter-evans/dockerhub-description)
with this action:
<details>
<summary><b>Show workflow</b></summary>
```yaml
name: ci
on:
push:
branches: master
jobs:
main:
runs-on: ubuntu-latest
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
push: true
tags: user/app:latest
-
name: Update repo description
uses: peter-evans/dockerhub-description@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
repository: user/app
```
</details>
## Customizing ## Customizing
@@ -604,24 +195,26 @@ Following inputs can be used as `step.with` keys
| Name | Type | Description | | Name | Type | Description |
|---------------------|----------|------------------------------------| |---------------------|----------|------------------------------------|
| `allow` | List/CSV | List of [extra privileged entitlement](https://github.com/docker/buildx/blob/master/docs/reference/buildx_build.md#allow) (eg. `network.host,security.insecure`) |
| `builder` | String | Builder instance (see [setup-buildx](https://github.com/docker/setup-buildx-action) action) | | `builder` | String | Builder instance (see [setup-buildx](https://github.com/docker/setup-buildx-action) action) |
| `context` | String | Build's context is the set of files located in the specified [`PATH` or `URL`](https://docs.docker.com/engine/reference/commandline/build/) (default [Git context](#git-context)) |
| `file` | String | Path to the Dockerfile (default `Dockerfile`) |
| `build-args` | List | List of build-time variables | | `build-args` | List | List of build-time variables |
| `cache-from` | List | List of [external cache sources](https://github.com/docker/buildx/blob/master/docs/reference/buildx_build.md#cache-from) (eg. `type=local,src=path/to/dir`) |
| `cache-to` | List | List of [cache export destinations](https://github.com/docker/buildx/blob/master/docs/reference/buildx_build.md#cache-to) (eg. `type=local,dest=path/to/dir`) |
| `context` | String | Build's context is the set of files located in the specified [`PATH` or `URL`](https://docs.docker.com/engine/reference/commandline/build/) (default [Git context](#git-context)) |
| `file` | String | Path to the Dockerfile. (default `{context}/Dockerfile`) |
| `labels` | List | List of metadata for an image | | `labels` | List | List of metadata for an image |
| `tags` | List/CSV | List of tags | | `load` | Bool | [Load](https://github.com/docker/buildx/blob/master/docs/reference/buildx_build.md#load) is a shorthand for `--output=type=docker` (default `false`) |
| `pull` | Bool | Always attempt to pull a newer version of the image (default `false`) | | `network` | String | Set the networking mode for the `RUN` instructions during build |
| `target` | String | Sets the target stage to build |
| `allow` | List/CSV | List of [extra privileged entitlement](https://github.com/docker/buildx#--allowentitlement) (eg. `network.host,security.insecure`) |
| `no-cache` | Bool | Do not use cache when building the image (default `false`) | | `no-cache` | Bool | Do not use cache when building the image (default `false`) |
| `platforms` | List/CSV | List of [target platforms](https://github.com/docker/buildx#---platformvaluevalue) for build | | `outputs` | List | List of [output destinations](https://github.com/docker/buildx/blob/master/docs/reference/buildx_build.md#output) (format: `type=local,dest=path`) |
| `load` | Bool | [Load](https://github.com/docker/buildx#--load) is a shorthand for `--output=type=docker` (default `false`) | | `platforms` | List/CSV | List of [target platforms](https://github.com/docker/buildx/blob/master/docs/reference/buildx_build.md#platform) for build |
| `push` | Bool | [Push](https://github.com/docker/buildx#--push) is a shorthand for `--output=type=registry` (default `false`) | | `pull` | Bool | Always attempt to pull a newer version of the image (default `false`) |
| `outputs` | List | List of [output destinations](https://github.com/docker/buildx#-o---outputpath-typetypekeyvalue) (format: `type=local,dest=path`) | | `push` | Bool | [Push](https://github.com/docker/buildx/blob/master/docs/reference/buildx_build.md#push) is a shorthand for `--output=type=registry` (default `false`) |
| `cache-from` | List | List of [external cache sources](https://github.com/docker/buildx#--cache-fromnametypetypekeyvalue) (eg. `type=local,src=path/to/dir`) | | `secrets` | List | List of secrets to expose to the build (eg. `key=string`, `GIT_AUTH_TOKEN=mytoken`) |
| `cache-to` | List | List of [cache export destinations](https://github.com/docker/buildx#--cache-tonametypetypekeyvalue) (eg. `type=local,dest=path/to/dir`) | | `secret-files` | List | List of secret files to expose to the build (eg. `key=filename`, `MY_SECRET=./secret.txt`) |
| `secrets` | List | List of secrets to expose to the build (eg. `key=value`, `GIT_AUTH_TOKEN=mytoken`) |
| `ssh` | List | List of SSH agent socket or keys to expose to the build | | `ssh` | List | List of SSH agent socket or keys to expose to the build |
| `tags` | List/CSV | List of tags |
| `target` | String | Sets the target stage to build |
### outputs ### outputs

View File

@@ -1,113 +1,7 @@
# Troubleshooting # Troubleshooting
* [`auto-push is currently not implemented for docker driver`](#auto-push-is-currently-not-implemented-for-docker-driver)
* [Cannot push to a registry](#cannot-push-to-a-registry) * [Cannot push to a registry](#cannot-push-to-a-registry)
## `auto-push is currently not implemented for docker driver`
If you're using the default builder (which uses the docker driver) without using our `setup-buildx-action`, you may
encounter this error message if you try to push your image:
```
Run docker/build-push-action@v2
📣 Buildx version: 0.4.2
🏃 Starting build...
/usr/bin/docker buildx build --tag localhost:5000/name/app:latest --iidfile /tmp/docker-build-push-eYl8PB/iidfile --file ./test/Dockerfile --push ./test
auto-push is currently not implemented for docker driver
Error: buildx call failed with: auto-push is currently not implemented for docker driver
```
While waiting for an implementation to be done on buildx/buildkit, you have the following possibilities
to solve this atm:
### With `docker-container` driver and `setup-buildx`
> Recommended solution
```yaml
jobs:
build:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.USER }}
password: ${{ secrets.PASSWORD }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
tags: ${{ env.REGISTRY }}/myapp:latest
push: true
```
### With `docker` driver
```yaml
jobs:
build:
-
name: Checkout
uses: actions/checkout@v2
-
name: Login
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.USER }}
password: ${{ secrets.PASSWORD }}
-
name: Build
uses: docker/build-push-action@v2
with:
context: .
tags: ${{ env.REGISTRY }}/myapp:latest
load: true
-
name: Push
run: docker push ${{ env.REGISTRY }}/myapp:latest
```
### With `docker` driver and `setup-buildx`
```yaml
jobs:
build:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
driver: docker
-
name: Login
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.USER }}
password: ${{ secrets.PASSWORD }}
-
name: Build
uses: docker/build-push-action@v2
with:
context: .
tags: ${{ env.REGISTRY }}/myapp:latest
load: true
-
name: Push
run: docker push ${{ env.REGISTRY }}/myapp:latest
```
## Cannot push to a registry ## Cannot push to a registry
While pushing to a registry, you may encounter these kinds of issues: While pushing to a registry, you may encounter these kinds of issues:
@@ -165,8 +59,7 @@ jobs:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
file: ./Dockerfile platforms: linux/amd64,linux/arm64
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
tags: docker.io/user/app:latest tags: docker.io/user/app:latest
outputs: type=oci,dest=/tmp/image.tar outputs: type=oci,dest=/tmp/image.tar
- -

View File

@@ -7,12 +7,13 @@
* Rename `dockerfile` input to `file` for consistency with other Docker build tools * Rename `dockerfile` input to `file` for consistency with other Docker build tools
* Rename `always_pull` input to `pull` for consistency with other Docker build tools * Rename `always_pull` input to `pull` for consistency with other Docker build tools
* Add `builder` input to be able to choose a builder instance through our [setup-buildx action](https://github.com/docker/setup-buildx-action) * Add `builder` input to be able to choose a builder instance through our [setup-buildx action](https://github.com/docker/setup-buildx-action)
* Add [`platforms`](https://github.com/docker/buildx#---platformvaluevalue) input to support multi-platform builds * Add `platforms` input to support multi-platform builds
* Add [`allow`](https://github.com/docker/buildx#--allowentitlement) input * Add `allow` input
* Add [`load`](https://github.com/docker/buildx#--load) input * Add `load` input
* Add [`outputs`](https://github.com/docker/buildx#-o---outputpath-typetypekeyvalue) input * Add `outputs` input
* Add [`cache-from`](https://github.com/docker/buildx#--cache-fromnametypetypekeyvalue) input (`cache_froms` removed) * Add `cache-from` input (`cache_froms` removed)
* Add [`cache-to`](https://github.com/docker/buildx#--cache-tonametypetypekeyvalue) input * Add `cache-to` input
* Rename `build_args` input to `build-args` for consistency with other Docker build tools
* Add `secrets` input * Add `secrets` input
* Review `tags` input * Review `tags` input
* Remove `repository` input. See [Simple workflow](#simple-workflow) for migration * Remove `repository` input. See [Simple workflow](#simple-workflow) for migration
@@ -62,7 +63,6 @@ steps:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
file: ./Dockerfile
pull: true pull: true
push: true push: true
build-args: | build-args: |
@@ -135,14 +135,13 @@ steps:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
file: ./Dockerfile
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.prep.outputs.tags }} tags: ${{ steps.prep.outputs.tags }}
labels: | labels: |
org.opencontainers.image.source=${{ github.event.repository.clone_url }} org.opencontainers.image.source=${{ github.event.repository.html_url }}
org.opencontainers.image.created=${{ steps.prep.outputs.created }} org.opencontainers.image.created=${{ steps.prep.outputs.created }}
org.opencontainers.image.revision=${{ github.sha }} org.opencontainers.image.revision=${{ github.sha }}
``` ```
> You can also use the [Docker meta action](https://github.com/crazy-max/ghaction-docker-meta) to handle tags and > You can also use the [Docker meta action to handle tags and labels](docs/advanced/tags-labels.md) based on GitHub
> labels based on GitHub actions events and Git metadata. A workflow example is available in the [README](README.md#handle-tags-and-labels). > actions events and Git metadata.

View File

@@ -1,9 +1,10 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as semver from 'semver'; import * as semver from 'semver';
import * as buildx from '../src/buildx'; import * as buildx from '../src/buildx';
import * as docker from '../src/docker';
import * as context from '../src/context'; import * as context from '../src/context';
import * as docker from '../src/docker';
const tmpNameSync = path.join('/tmp/.docker-build-push-jest', '.tmpname-jest').split(path.sep).join(path.posix.sep); const tmpNameSync = path.join('/tmp/.docker-build-push-jest', '.tmpname-jest').split(path.sep).join(path.posix.sep);
const digest = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9'; const digest = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9';
@@ -118,15 +119,36 @@ describe('parseVersion', () => {
describe('getSecret', () => { describe('getSecret', () => {
test.each([ test.each([
['A_SECRET', 'abcdef0123456789'], ['A_SECRET=abcdef0123456789', false, 'A_SECRET', 'abcdef0123456789', false],
['GIT_AUTH_TOKEN', 'abcdefghijklmno=0123456789'], ['GIT_AUTH_TOKEN=abcdefghijklmno=0123456789', false, 'GIT_AUTH_TOKEN', 'abcdefghijklmno=0123456789', false],
['MY_KEY', 'c3RyaW5nLXdpdGgtZXF1YWxzCg=='] ['MY_KEY=c3RyaW5nLXdpdGgtZXF1YWxzCg==', false, 'MY_KEY', 'c3RyaW5nLXdpdGgtZXF1YWxzCg==', false],
])('given %p key and %p secret', async (key, secret) => { ['aaaaaaaa', false, '', '', true],
const secretArgs = await buildx.getSecret(`${key}=${secret}`); ['aaaaaaaa=', false, '', '', true],
console.log(`secretArgs: ${secretArgs}`); ['=bbbbbbb', false, '', '', true],
expect(secretArgs).toEqual(`id=${key},src=${tmpNameSync}`); [
const secretContent = await fs.readFileSync(tmpNameSync, 'utf-8'); `foo=${path.join(__dirname, 'fixtures', 'secret.txt').split(path.sep).join(path.posix.sep)}`,
console.log(`secretValue: ${secretContent}`); true,
expect(secretContent).toEqual(secret); 'foo',
'bar',
false
],
[`notfound=secret`, true, '', '', true]
])('given %p key and %p secret', async (kvp, file, exKey, exValue, invalid) => {
try {
let secret: string;
if (file) {
secret = await buildx.getSecretFile(kvp);
} else {
secret = await buildx.getSecretString(kvp);
}
expect(true).toBe(!invalid);
console.log(`secret: ${secret}`);
expect(secret).toEqual(`id=${exKey},src=${tmpNameSync}`);
const secretValue = await fs.readFileSync(tmpNameSync, 'utf-8');
console.log(`secretValue: ${secretValue}`);
expect(secretValue).toEqual(exValue);
} catch (err) {
expect(true).toBe(invalid);
}
}); });
}); });

View File

@@ -1,7 +1,115 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as context from '../src/context'; import * as context from '../src/context';
const pgp = `-----BEGIN PGP PRIVATE KEY BLOCK-----
lQdGBF6tzaABEACjFbX7PFEG6vDPN2MPyxYW7/3o/sonORj4HXUFjFxxJxktJ3x3
N1ayHPJ1lqIeoiY7jVbq0ZdEVGkd3YsKG9ZMdZkzGzY6PQPC/+M8OnzOiOPwUdWc
+Tdhh115LvVz0MMKYiab6Sn9cgxj9On3LCQKpjvMDpPo9Ttf6v2GQIw8h2ACvdzQ
71LtIELS/I+dLbfZiwpUu2fhQT13EJkEnYMOYwM5jNUd66P9itUc7MrOWjkicrKP
oF1dQaCM+tuKuxvD8WLdiwU5x60NoGkJHHUehKQXl2dVzjpqEqHKEBJt9tfJ9lpE
YIisgwB8o3pes0fgCehjW2zI95/o9+ayJ6nl4g5+mSvWRXEu66h71nwM0Yuvquk8
3me7qhYfDrDdCwcxS5BS1hwakTgUQLD99FZjbx1j8sq96I65O0GRdyU2PR8KIjwu
JrkTH4ZlKxK3FQghUhFoA5GkiDb+eClmRMSni5qg+81T4XChmUkEprA3eWCHL+Ma
xRNNxLS+r6hH9HG5JBxpV3iaTI9HHpnQKhEeaLXqsUTDZliN9hP7Ywo8bpUB8j2d
oWYwDV4dPyMKr6Fb8RDCh2q5gJGbVp8w/NmmBTeL+IP2fFggJkRfyumv3Ul7x66L
tBFQ4rYo4JUUrGweSTneG6REIgxH66hIrNl6Vo/D1ZyknTe1dMOu/BTkkQARAQAB
/gcDAqra8KO+h3bfyu90vxTL1ro4x/x9il7VBcWlIR4cBP7Imgxv+T4hwPIu8P1x
lOlxLNWegFOV0idoTy1o3VLLBev/F+IlspX4A+2XEIddR6nZnKFi0Lv2L4TKgE9E
VJJTszmviDIRLMLN9dWzDfA8hj5tR5Inot92CHRF414AS22JHvlhbFSLQnjqsN+C
n1cQpNOJhkxsSfZsxjnFa/70y/u8v0o8mzyLZmk9HpzRHGzoz8IfpLp8OTqBR9u6
zzoKLy16zZO55OKbj7h8uVZvDUq9l8iDICpqWMdZqBJIl56MBexYKgYxh3YO/8v2
oXli+8Xuaq5QLiCN3yT7IbKoYzplnFfaJwFiMh7R1iPLXaYAZ0qdRijlbtseTK1m
oHNkwUbxVzjkh4LfE8UpmMwZn5ZjWni3230SoiXuKy0OHkGvwGvWWAL1mEuoYuUI
mFMcH5MnixP8oQYZKDj2IR/yEeOpdU6B/tr3Tk1NidLf7pUMqG7Ff1NU6dAUeBpa
9xahITMjHvrhgMISY4IYZep5cEnVw8lQTpUJtW/ePMzrFhu3sA7oNdj9joW/VMfz
H7MHwwavtICsYqoqV3lnjX4EC9dW6o8PTUg2u956dmtK7KAyUK/+w2aLNGT28ChN
jhRYHvHzB9Kw5asqI/lTM49eqslBqYQMTTjdBphkYuSZQzNMf291j/ZmoLhD1A1a
S8tUnNygKV4D1cJYgSXfzhFoU8ib/0SPo+KqQ+CzGS+wxXg6WNBA6wepTjpnVVx3
4JADP8IJcDC3P0iwAreWjSy15F1cvemFFB0SLNUkyZGzsxtKzbM1+8khl68+eazC
LzRj0rxfIF5znWjX1QFhKxCk6eF0IWDY0+b3DBkmChME9YDXJ3TthcqA7JgcX4JI
M4/wdqhgerJYOmj+i2Q0M+Bu02icOJYMwTMMsDVl7XGHkaCuRgZ54eZAUH7JFwUm
1Ct3tcaqiTMmz0ngHVqBTauzgqKDvzwdVqdfg05H364nJMay/3omR6GayIb5CwSo
xdNVwG3myPPradT9MP09mDr4ys2zcnQmCkvTVBF6cMZ1Eh6PQQ8CyQWv0zkaBnqj
JrM1hRpgW4ZlRosSIjCaaJjolN5QDcXBM9TbW9ww+ZYstazN2bV1ZQ7BEjlHQPa1
BhzMsvqkbETHsIpDNF52gZKn3Q9eIX05BeadzpHUb5/XOheIHVIdhSaTlgl/qQW5
hQgPGSzSV6KhXEY7aevTdvOgq++WiELkjfz2f2lQFesTjFoQWEvxVDUmLxHtEhaN
DOuh4H3mX5Opn3pLQmqWVhJTbFdx+g5qQd0NCW4mDaTFWTRLFLZQsSJxDSeg9xrY
gmaii8NhMZRwquADW+6iU6KfraBhngi7HRz4TfqPr9ma/KUY464cqim1fnwXejyx
jsb5YHR9R66i+F6P/ysF5w+QuVdDt1fnf9GLay0r6qxpA8ft2vGPcDs4806Huj+7
Aq5VeJaNkCuh3GR3xVnCFAz/7AtkO6xKuZm8B3q904UuMdSmkhWbaobIuF/B2B6S
eawIXQHEOplK3ic26d8Ckf4gbjeORfELcMAEi5nGXpTThCdmxQApCLxAYYnTfQT1
xhlDwT9xPEabo98mIwJJsAU5VsTDYW+qfo4qIx8gYoSKc9Xu3yVh3n+9k43Gcm5V
9lvK1slijf+TzODZt/jsmkF8mPjXyP5KOI+xQp/m4PxW3pp57YrYj/Rnwga+8DKX
jMsW7mLAAZ/e+PY6z/s3x1Krfk+Bb5Ph4mI0zjw5weQdtyEToRgveda0GEpvZSBU
ZXN0ZXIgPGpvZUBmb28uYmFyPokCNgQQAQgAIAUCXq3NoAYLCQcIAwIEFQgKAgQW
AgEAAhkBAhsDAh4BAAoJEH2FHrctc72gxtQP/AulaClIcn/kDt43mhYnyLglPfbo
AqPlU26chXolBg0Wo0frFY3aIs5SrcWEf8aR4XLwCFGyi3vya0CUxjghN5tZBYqo
vswbT00zP3ohxxlJFCRRR9bc7OZXCgTddtfVf6EKrUAzIkbWyAhaJnwJy/1UGpSw
SEO/KpastrVKf3sv1wqOeFQ4DFyjaNda+xv3dVWS8db7KogqJiPFZXrQK3FKVIxS
fxRSmKaYN7//d+xwVAEY++RrnL/o8B2kV6N68cCpQWJELyYnJzis9LBcWd/3wiYh
efTyY+ePKUjcB+kEZnyJfLc7C2hll2e7UJ0fxv+k8vHReRhrNWmGRXsjNRxiw3U0
hfvxD/C8nyqAbeTHp4XDX78Tc3XCysAqIYboIL+RyewDMjjLj5vzUYAdUdtyNaD7
C6M2R6pN1GAt52CJmC/Z6F7W7GFGoYOdEkVdMQDsjCwScyEUNlGj9Zagw5M2EgSe
6gaHgMgTzsMzCc4W6WV5RcS55cfDNOXtxPsMJTt4FmXrjl11prBzpMfpU5a9zxDZ
oi54ZZ8VPE6jsT4Lzw3sni3c83wm28ArM20AzZ1vh7fk3Sfd0u4Yaz7s9JlEm5+D
34tEyli28+QjCQc18EfQUiJqiYEJRxJXJ3esvMHfYi45pV/Eh5DgRW1305fUJV/6
+rGpg0NejsHoZdZPnQdGBF6tzaABEAC4mVXTkVk6Kdfa4r5zlzsoIrR27laUlMkb
OBMt+aokqS+BEbmTnMg6xIAmcUT5uvGAc8S/WhrPoYfc15fTUyHIz8ZbDoAg0LO6
0Io4VkAvNJNEnsSV9VdLBh/XYlc4K49JqKyWTL4/FJFAGbsmHY3b+QU90AS6FYRv
KeBAoiyebrjx0vmzb8E8h3xthVLN+AfMlR1ickY62zvnpkbncSMY/skur1D2KfbF
3sFprty2pEtjFcyB5+18l2IyyHGOlEUw1PZdOAV4/Myh1EZRgYBPs80lYTJALCVF
IdOakH33WJCImtNZB0AbDTABG+JtMjQGscOa0qzf1Y/7tlhgCrynBBdaIJTx95TD
21BUHcHOu5yTIS6Ulysxfkv611+BiOKHgdq7DVGP78VuzA7bCjlP1+vHqIt3cnIa
t2tEyuZ/XF4uc3/i4g0uP9r7AmtET7Z6SKECWjpVv+UEgLx5Cv+ql+LSKYQMvU9a
i3B1F9fatn3FSLVYrL4aRxu4TSw9POb0/lgDNmN3lGQOsjGCZPibkHjgPEVxKuiq
9Oi38/VTQ0ZKAmHwBTq1WTZIrPrCW0/YMQ6yIJZulwQ9Yx1cgzYzEfg04fPXlXMi
vkvNpKbYIICzqj0/DVztz9wgpW6mnd0A2VX2dqbMM0fJUCHA6pj8AvXY4R+9Q4rj
eWRK9ycInQARAQAB/gcDApjt7biRO0PEyrrAiUwDMsJL4/CVMu11qUWEPjKe2Grh
ZTW3N+m3neKPRULu+LUtndUcEdVWUCoDzAJ7MwihZtV5vKST/5Scd2inonOaJqoA
nS3wnEMN/Sc93HAZiZnFx3NKjQVNCwbuEs45mXkkcjLm2iadrTL8fL4acsu5IsvD
LbDwVOPeNnHKl6Hr20e39fK0FuJEyH49JM6U3B1/8385sJB8+E24+hvSF81aMddh
Ne4Bc3ZYiYaKxe1quPNKC0CQhAZiT7LsMfkInXr0hY1I+kISNXEJ1dPYOEWiv0Ze
jD5Pupn34okKNEeBCx+dK8BmUCi6Jgs7McUA7hN0D/YUS++5fuR55UQq2j8Ui0tS
P8GDr86upH3PgEL0STh9fYfJ7TesxurwonWjlmmT62Myl4Pr+RmpS6PXOnhtcADm
eGLpzhTveFj4JBLMpyYHgBTqcs12zfprATOpsI/89kmQoGCZpG6+AbfSHqNNPdy2
eqUCBhOZlIIda1z/cexmU3f/gBqyflFf8fkvmlO4AvI8aMH3OpgHdWnzh+AB51xj
kmdD/oWel9v7Dz4HoZUfwFaLZ0fE3P9voD8e+sCwqQwVqRY4L/BOYPD5noVOKgOj
ABNKu5uKrobj6rFUi6DTUCjFGcmoF1Sc06xFNaagUNggRbmlC/dz22RWdDUYv5ra
N6TxIDkGC0cK6ujyK0nes3DN0aHjgwWuMXDYkN3UckiebI4Cv/eF9jvUKOSiIcy1
RtxdazZS4dYg2LBMeJKVkPi5elsNyw2812nEY3du/nEkQYXfYgWOF27OR+g4Y9Yw
1BiqJ1TTjbQnd/khOCrrbzDH1mw00+1XVsT6wjObuYqqxPPS87UrqmMf6OdoYfPm
zEOnNLBnsJ5VQM3A3pcT40RfdBrZRO8LjGhzKTreyq3C+jz0RLa5HNE8GgOhGyck
ME4h+RhXlE8KGM+tTo6PA1NJSrEt+8kZzxjP4rIEn0aVthCkNXK12inuXtnHm0ao
iLUlQOsfPFEnzl0TUPd7+z7j/wB+XiKU/AyEUuB0mvdxdKtqXvajahOyhLjzHQhz
ZnNlgANGtiqcSoJmkJ8yAvhrtQX51fQLftxbArRW1RYk/5l+Gy3azR+gUC17M6JN
jrUYxn0zlAxDGFH7gACHUONwVekcuEffHzgu2lk7MyO1Y+lPnwabqjG0eWWHuU00
hskJlXyhj7DeR12bwjYkyyjG62GvOH02g3OMvUgNGH+K321Dz539csCh/xwtg7Wt
U3YAphU7htQ1dPDfk1IRs7DQo2L+ZTE57vmL5m0l6fTataEWBPUXkygfQFUJOM6Q
yY76UEZww1OSDujNeY171NSTzXCVkUeAdAMXgjaHXWLK2QUQUoXbYX/Kr7Vvt9Fu
Jh6eGjjp7dSjQ9+DW8CAB8vxd93gsQQGWYjmGu8khkEmx6OdZhmSbDbe915LQTb9
sPhk2s5/Szsvr5W2JJ2321JI6KXBJMZvPC5jEBWmRzOYkRd2vloft+CSMfXF+Zfd
nYtc6R3dvb9vcjo+a9wFtfcoDsO0MaPSM+9GB25MamdatmGX6iLOy9Re1UABwUi/
VhTWNkP5uzqx0sDwHEIa2rYOwxpIZDwwjM3oOASCW1DDBQ0BI9KNjfIeL3ubx2mS
2x8hFU9qSK4umoDNbzOqGPSlkdbiPcNjF2ZcSN1qQZiYdwLL5dw6APNyBVjxTN1J
gkCdJ/HwAY+r93Lbl5g8gz8d0vJEyfn//34sn9u+toSTw55GcG9Ks1kSKIeDNh0h
MiPm3HmJAh8EGAEIAAkFAl6tzaACGwwACgkQfYUety1zvaBV9hAAgliX36pXJ59g
3I9/4R68e/fGg0FMM6D+01yCeiKApOYRrJ0cYKn7ITDYmHhlGGpBAie90UsqX12h
hdLP7LoQx7sjTyzQt6JmpA8krIwi2ON7FKBkdYb8IYx4mE/5vKnYT4/SFnwTmnZY
+m+NzK2U/qmhq8JyO8gozdAKJUcgz49IVv2Ij0tQ4qaPbyPwQxIDyKnT758nJhB1
jTqo+oWtER8q3okzIlqcArqn5rDaNJx+DRYL4E/IddyHQAiUWUka8usIUqeW5reu
zoPUE2CCfOJSGArkqHQQqMx0WEzjQTwAPaHrQbera4SbiV/o4CLCV/u5p1Qnig+Q
iUsakmlD299t//125LIQEa5qzd9hRC7u1uJS7VdW8eGIEcZ0/XT/sr+z23z0kpZH
D3dXPX0BwM4IP9xu31CNg10x0rKwjbxy8VaskFEelpqpu+gpAnxqMd1evpeUHcOd
r5RgPgkNFfba9Nbxf7uEX+HOmsOM+kdtSmdGIvsBZjVnW31nnoDMp49jG4OynjrH
cRuoM9sxdr6UDqb22CZ3/e0YN4UaZM3YDWMVaP/QBVgvIFcdByqNWezpd9T4ZUII
MZlaV1uRnHg6B/zTzhIdMM80AXz6Uv6kw4S+Lt7HlbrnMT7uKLuvzH7cle0hcIUa
PejgXO0uIRolYQ3sz2tMGhx1MfBqH64=
=WbwB
-----END PGP PRIVATE KEY BLOCK-----`;
jest.spyOn(context, 'defaultContext').mockImplementation((): string => { jest.spyOn(context, 'defaultContext').mockImplementation((): string => {
return 'https://github.com/docker/build-push-action.git#test-jest'; return 'https://github.com/docker/build-push-action.git#test-jest';
}); });
@@ -39,7 +147,6 @@ describe('getArgs', () => {
'buildx', 'buildx',
'build', 'build',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile', '--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'--file', 'Dockerfile',
'.' '.'
] ]
], ],
@@ -54,7 +161,20 @@ describe('getArgs', () => {
'--build-arg', 'MY_ARG=val1,val2,val3', '--build-arg', 'MY_ARG=val1,val2,val3',
'--build-arg', 'ARG=val', '--build-arg', 'ARG=val',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile', '--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'--file', 'Dockerfile', 'https://github.com/docker/build-push-action.git#test-jest'
]
],
[
'0.4.2',
new Map<string, string>([
['tags', 'name/app:7.4, name/app:latest'],
]),
[
'buildx',
'build',
'--tag', 'name/app:7.4',
'--tag', 'name/app:latest',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'https://github.com/docker/build-push-action.git#test-jest' 'https://github.com/docker/build-push-action.git#test-jest'
] ]
], ],
@@ -71,7 +191,6 @@ describe('getArgs', () => {
'--label', 'org.opencontainers.image.title=buildkit', '--label', 'org.opencontainers.image.title=buildkit',
'--label', 'org.opencontainers.image.description=concurrent, cache-efficient, and Dockerfile-agnostic builder toolkit', '--label', 'org.opencontainers.image.description=concurrent, cache-efficient, and Dockerfile-agnostic builder toolkit',
'--output', 'type=local,dest=./release-out', '--output', 'type=local,dest=./release-out',
'--file', 'Dockerfile',
'.' '.'
] ]
], ],
@@ -85,7 +204,6 @@ describe('getArgs', () => {
'buildx', 'buildx',
'build', 'build',
'--platform', 'linux/amd64,linux/arm64', '--platform', 'linux/amd64,linux/arm64',
'--file', 'Dockerfile',
'.' '.'
] ]
], ],
@@ -98,7 +216,6 @@ describe('getArgs', () => {
'buildx', 'buildx',
'build', 'build',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile', '--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'--file', 'Dockerfile',
'.' '.'
] ]
], ],
@@ -113,7 +230,6 @@ describe('getArgs', () => {
'build', 'build',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile', '--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'--secret', 'id=GIT_AUTH_TOKEN,src=/tmp/.docker-build-push-jest/.tmpname-jest', '--secret', 'id=GIT_AUTH_TOKEN,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--file', 'Dockerfile',
'.' '.'
] ]
], ],
@@ -128,7 +244,6 @@ describe('getArgs', () => {
'build', 'build',
'--output', '.', '--output', '.',
'--secret', 'id=GIT_AUTH_TOKEN,src=/tmp/.docker-build-push-jest/.tmpname-jest', '--secret', 'id=GIT_AUTH_TOKEN,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--file', 'Dockerfile',
'https://github.com/docker/build-push-action.git#test-jest' 'https://github.com/docker/build-push-action.git#test-jest'
] ]
], ],
@@ -154,6 +269,97 @@ describe('getArgs', () => {
'--push', '--push',
'https://github.com/docker/build-push-action.git#heads/master' 'https://github.com/docker/build-push-action.git#heads/master'
] ]
],
[
'0.4.2',
new Map<string, string>([
['context', 'https://github.com/docker/build-push-action.git#heads/master'],
['tag', 'localhost:5000/name/app:latest'],
['platforms', 'linux/amd64,linux/arm64'],
['secrets', `GIT_AUTH_TOKEN=abcdefghi,jklmno=0123456789
"MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc"
FOO=bar
"EMPTYLINE=aaaa
bbbb
ccc"`],
['file', './test/Dockerfile'],
['builder', 'builder-git-context-2'],
['push', 'true']
]),
[
'buildx',
'build',
'--platform', 'linux/amd64,linux/arm64',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'--secret', 'id=GIT_AUTH_TOKEN,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--secret', 'id=MYSECRET,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--secret', 'id=FOO,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--secret', 'id=EMPTYLINE,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--file', './test/Dockerfile',
'--builder', 'builder-git-context-2',
'--push',
'https://github.com/docker/build-push-action.git#heads/master'
]
],
[
'0.4.2',
new Map<string, string>([
['context', 'https://github.com/docker/build-push-action.git#heads/master'],
['tag', 'localhost:5000/name/app:latest'],
['platforms', 'linux/amd64,linux/arm64'],
['secrets', `GIT_AUTH_TOKEN=abcdefghi,jklmno=0123456789
MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc
FOO=bar
EMPTYLINE=aaaa
bbbb
ccc`],
['file', './test/Dockerfile'],
['builder', 'builder-git-context-2'],
['push', 'true']
]),
[
'buildx',
'build',
'--platform', 'linux/amd64,linux/arm64',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'--secret', 'id=GIT_AUTH_TOKEN,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--secret', 'id=MYSECRET,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--secret', 'id=FOO,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--secret', 'id=EMPTYLINE,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--file', './test/Dockerfile',
'--builder', 'builder-git-context-2',
'--push',
'https://github.com/docker/build-push-action.git#heads/master'
]
],
[
'0.5.1',
new Map<string, string>([
['context', 'https://github.com/docker/build-push-action.git#heads/master'],
['tag', 'localhost:5000/name/app:latest'],
['secret-files', `MY_SECRET=${path.join(__dirname, 'fixtures', 'secret.txt').split(path.sep).join(path.posix.sep)}`],
['file', './test/Dockerfile'],
['builder', 'builder-git-context-2'],
['network', 'host'],
['push', 'true']
]),
[
'buildx',
'build',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'--secret', 'id=MY_SECRET,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--file', './test/Dockerfile',
'--builder', 'builder-git-context-2',
'--network', 'host',
'--push',
'https://github.com/docker/build-push-action.git#heads/master'
]
] ]
])( ])(
'given %p with %p as inputs, returns %p', 'given %p with %p as inputs, returns %p',
@@ -172,68 +378,167 @@ describe('getArgs', () => {
}); });
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('handles multiple lines and ignoring comma correctly', async () => { it('multiline and ignoring comma correctly', async () => {
setInput('cache-from', 'user/app:cache\ntype=local,src=path/to/dir'); setInput('cache-from', 'user/app:cache\ntype=local,src=path/to/dir');
const res = await context.getInputList('cache-from', true); const res = await context.getInputList('cache-from', true);
console.log(res); console.log(res);
expect(res).toEqual(['user/app:cache', 'type=local,src=path/to/dir']); expect(res).toEqual(['user/app:cache', 'type=local,src=path/to/dir']);
}); });
it('handles different new lines and ignoring comma correctly', async () => { it('different new lines and ignoring comma correctly', async () => {
setInput('cache-from', 'user/app:cache\r\ntype=local,src=path/to/dir'); setInput('cache-from', 'user/app:cache\r\ntype=local,src=path/to/dir');
const res = await context.getInputList('cache-from', true); const res = await context.getInputList('cache-from', true);
console.log(res); console.log(res);
expect(res).toEqual(['user/app:cache', 'type=local,src=path/to/dir']); 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('large multiline values', async () => {
setInput(
'secrets',
`"GPG_KEY=${pgp}"
FOO=bar`
);
const res = await context.getInputList('secrets', true);
console.log(res);
expect(res).toEqual([`GPG_KEY=${pgp}`, '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

@@ -0,0 +1 @@
bar

View File

@@ -7,52 +7,14 @@ branding:
color: 'blue' color: 'blue'
inputs: inputs:
builder:
description: "Builder instance"
required: false
context:
description: "Build's context is the set of files located in the specified PATH or URL"
required: false
file:
description: "Path to the Dockerfile"
required: false
default: './Dockerfile'
build-args:
description: "List of build-time variables"
required: false
labels:
description: "List of metadata for an image"
required: false
tags:
description: "List of tags"
required: false
pull:
description: "Always attempt to pull a newer version of the image"
required: false
default: 'false'
target:
description: "Sets the target stage to build"
required: false
allow: allow:
description: "List of extra privileged entitlement (eg. network.host,security.insecure)" description: "List of extra privileged entitlement (eg. network.host,security.insecure)"
required: false required: false
no-cache: build-args:
description: "Do not use cache when building the image" description: "List of build-time variables"
required: false required: false
default: 'false' builder:
platforms: description: "Builder instance"
description: "List of target platforms for build"
required: false
load:
description: "Load is a shorthand for --output=type=docker"
required: false
default: 'false'
push:
description: "Push is a shorthand for --output=type=registry"
required: false
default: 'false'
outputs:
description: "List of output destinations (format: type=local,dest=path)"
required: false required: false
cache-from: cache-from:
description: "List of external cache sources for buildx (eg. user/app:cache, type=local,src=path/to/dir)" description: "List of external cache sources for buildx (eg. user/app:cache, type=local,src=path/to/dir)"
@@ -60,16 +22,59 @@ inputs:
cache-to: cache-to:
description: "List of cache export destinations for buildx (eg. user/app:cache, type=local,dest=path/to/dir)" description: "List of cache export destinations for buildx (eg. user/app:cache, type=local,dest=path/to/dir)"
required: false required: false
context:
description: "Build's context is the set of files located in the specified PATH or URL"
required: false
file:
description: "Path to the Dockerfile"
required: false
labels:
description: "List of metadata for an image"
required: false
load:
description: "Load is a shorthand for --output=type=docker"
required: false
default: 'false'
network:
description: "Set the networking mode for the RUN instructions during build"
required: false
no-cache:
description: "Do not use cache when building the image"
required: false
default: 'false'
outputs:
description: "List of output destinations (format: type=local,dest=path)"
required: false
platforms:
description: "List of target platforms for build"
required: false
pull:
description: "Always attempt to pull a newer version of the image"
required: false
default: 'false'
push:
description: "Push is a shorthand for --output=type=registry"
required: false
default: 'false'
secrets: secrets:
description: "List of secrets to expose to the build (eg. key=value, GIT_AUTH_TOKEN=mytoken)" description: "List of secrets to expose to the build (eg. key=string, GIT_AUTH_TOKEN=mytoken)"
required: false
secret-files:
description: "List of secret files to expose to the build (eg. key=filename, MY_SECRET=./secret.txt)"
required: false
ssh:
description: "List of SSH agent socket or keys to expose to the build"
required: false
tags:
description: "List of tags"
required: false
target:
description: "Sets the target stage to build"
required: false required: false
github-token: github-token:
description: "GitHub Token used to authenticate against a repository for Git context" description: "GitHub Token used to authenticate against a repository for Git context"
default: ${{ github.token }} default: ${{ github.token }}
required: false required: false
ssh:
description: "List of SSH agent socket or keys to expose to the build"
required: false
outputs: outputs:
digest: digest:

1527
dist/index.js generated vendored
View File

@@ -49,6 +49,13 @@ module.exports =
/************************************************************************/ /************************************************************************/
/******/ ({ /******/ ({
/***/ 4:
/***/ (function(module) {
module.exports = require("child_process");
/***/ }),
/***/ 8: /***/ 8:
/***/ (function(module, __unusedexports, __webpack_require__) { /***/ (function(module, __unusedexports, __webpack_require__) {
@@ -1987,15 +1994,12 @@ const debug = __webpack_require__(427)
const { MAX_LENGTH, MAX_SAFE_INTEGER } = __webpack_require__(293) const { MAX_LENGTH, MAX_SAFE_INTEGER } = __webpack_require__(293)
const { re, t } = __webpack_require__(523) const { re, t } = __webpack_require__(523)
const parseOptions = __webpack_require__(785)
const { compareIdentifiers } = __webpack_require__(463) const { compareIdentifiers } = __webpack_require__(463)
class SemVer { class SemVer {
constructor (version, options) { constructor (version, options) {
if (!options || typeof options !== 'object') { options = parseOptions(options)
options = {
loose: !!options,
includePrerelease: false
}
}
if (version instanceof SemVer) { if (version instanceof SemVer) {
if (version.loose === !!options.loose && if (version.loose === !!options.loose &&
version.includePrerelease === !!options.includePrerelease) { version.includePrerelease === !!options.includePrerelease) {
@@ -2275,6 +2279,22 @@ class SemVer {
module.exports = SemVer module.exports = SemVer
/***/ }),
/***/ 91:
/***/ (function(module) {
"use strict";
module.exports = function (Yallist) {
Yallist.prototype[Symbol.iterator] = function* () {
for (let walker = this.head; walker; walker = walker.next) {
yield walker.value
}
}
}
/***/ }), /***/ }),
/***/ 98: /***/ 98:
@@ -2451,9 +2471,344 @@ try {
/***/ }), /***/ }),
/***/ 129: /***/ 129:
/***/ (function(module) { /***/ (function(module, __unusedexports, __webpack_require__) {
"use strict";
// A linked list to keep track of recently-used-ness
const Yallist = __webpack_require__(665)
const MAX = Symbol('max')
const LENGTH = Symbol('length')
const LENGTH_CALCULATOR = Symbol('lengthCalculator')
const ALLOW_STALE = Symbol('allowStale')
const MAX_AGE = Symbol('maxAge')
const DISPOSE = Symbol('dispose')
const NO_DISPOSE_ON_SET = Symbol('noDisposeOnSet')
const LRU_LIST = Symbol('lruList')
const CACHE = Symbol('cache')
const UPDATE_AGE_ON_GET = Symbol('updateAgeOnGet')
const naiveLength = () => 1
// lruList is a yallist where the head is the youngest
// item, and the tail is the oldest. the list contains the Hit
// objects as the entries.
// Each Hit object has a reference to its Yallist.Node. This
// never changes.
//
// cache is a Map (or PseudoMap) that matches the keys to
// the Yallist.Node object.
class LRUCache {
constructor (options) {
if (typeof options === 'number')
options = { max: options }
if (!options)
options = {}
if (options.max && (typeof options.max !== 'number' || options.max < 0))
throw new TypeError('max must be a non-negative number')
// Kind of weird to have a default max of Infinity, but oh well.
const max = this[MAX] = options.max || Infinity
const lc = options.length || naiveLength
this[LENGTH_CALCULATOR] = (typeof lc !== 'function') ? naiveLength : lc
this[ALLOW_STALE] = options.stale || false
if (options.maxAge && typeof options.maxAge !== 'number')
throw new TypeError('maxAge must be a number')
this[MAX_AGE] = options.maxAge || 0
this[DISPOSE] = options.dispose
this[NO_DISPOSE_ON_SET] = options.noDisposeOnSet || false
this[UPDATE_AGE_ON_GET] = options.updateAgeOnGet || false
this.reset()
}
// resize the cache when the max changes.
set max (mL) {
if (typeof mL !== 'number' || mL < 0)
throw new TypeError('max must be a non-negative number')
this[MAX] = mL || Infinity
trim(this)
}
get max () {
return this[MAX]
}
set allowStale (allowStale) {
this[ALLOW_STALE] = !!allowStale
}
get allowStale () {
return this[ALLOW_STALE]
}
set maxAge (mA) {
if (typeof mA !== 'number')
throw new TypeError('maxAge must be a non-negative number')
this[MAX_AGE] = mA
trim(this)
}
get maxAge () {
return this[MAX_AGE]
}
// resize the cache when the lengthCalculator changes.
set lengthCalculator (lC) {
if (typeof lC !== 'function')
lC = naiveLength
if (lC !== this[LENGTH_CALCULATOR]) {
this[LENGTH_CALCULATOR] = lC
this[LENGTH] = 0
this[LRU_LIST].forEach(hit => {
hit.length = this[LENGTH_CALCULATOR](hit.value, hit.key)
this[LENGTH] += hit.length
})
}
trim(this)
}
get lengthCalculator () { return this[LENGTH_CALCULATOR] }
get length () { return this[LENGTH] }
get itemCount () { return this[LRU_LIST].length }
rforEach (fn, thisp) {
thisp = thisp || this
for (let walker = this[LRU_LIST].tail; walker !== null;) {
const prev = walker.prev
forEachStep(this, fn, walker, thisp)
walker = prev
}
}
forEach (fn, thisp) {
thisp = thisp || this
for (let walker = this[LRU_LIST].head; walker !== null;) {
const next = walker.next
forEachStep(this, fn, walker, thisp)
walker = next
}
}
keys () {
return this[LRU_LIST].toArray().map(k => k.key)
}
values () {
return this[LRU_LIST].toArray().map(k => k.value)
}
reset () {
if (this[DISPOSE] &&
this[LRU_LIST] &&
this[LRU_LIST].length) {
this[LRU_LIST].forEach(hit => this[DISPOSE](hit.key, hit.value))
}
this[CACHE] = new Map() // hash of items by key
this[LRU_LIST] = new Yallist() // list of items in order of use recency
this[LENGTH] = 0 // length of items in the list
}
dump () {
return this[LRU_LIST].map(hit =>
isStale(this, hit) ? false : {
k: hit.key,
v: hit.value,
e: hit.now + (hit.maxAge || 0)
}).toArray().filter(h => h)
}
dumpLru () {
return this[LRU_LIST]
}
set (key, value, maxAge) {
maxAge = maxAge || this[MAX_AGE]
if (maxAge && typeof maxAge !== 'number')
throw new TypeError('maxAge must be a number')
const now = maxAge ? Date.now() : 0
const len = this[LENGTH_CALCULATOR](value, key)
if (this[CACHE].has(key)) {
if (len > this[MAX]) {
del(this, this[CACHE].get(key))
return false
}
const node = this[CACHE].get(key)
const item = node.value
// dispose of the old one before overwriting
// split out into 2 ifs for better coverage tracking
if (this[DISPOSE]) {
if (!this[NO_DISPOSE_ON_SET])
this[DISPOSE](key, item.value)
}
item.now = now
item.maxAge = maxAge
item.value = value
this[LENGTH] += len - item.length
item.length = len
this.get(key)
trim(this)
return true
}
const hit = new Entry(key, value, len, now, maxAge)
// oversized objects fall out of cache automatically.
if (hit.length > this[MAX]) {
if (this[DISPOSE])
this[DISPOSE](key, value)
return false
}
this[LENGTH] += hit.length
this[LRU_LIST].unshift(hit)
this[CACHE].set(key, this[LRU_LIST].head)
trim(this)
return true
}
has (key) {
if (!this[CACHE].has(key)) return false
const hit = this[CACHE].get(key).value
return !isStale(this, hit)
}
get (key) {
return get(this, key, true)
}
peek (key) {
return get(this, key, false)
}
pop () {
const node = this[LRU_LIST].tail
if (!node)
return null
del(this, node)
return node.value
}
del (key) {
del(this, this[CACHE].get(key))
}
load (arr) {
// reset the cache
this.reset()
const now = Date.now()
// A previous serialized cache has the most recent items first
for (let l = arr.length - 1; l >= 0; l--) {
const hit = arr[l]
const expiresAt = hit.e || 0
if (expiresAt === 0)
// the item was created without expiration in a non aged cache
this.set(hit.k, hit.v)
else {
const maxAge = expiresAt - now
// dont add already expired items
if (maxAge > 0) {
this.set(hit.k, hit.v, maxAge)
}
}
}
}
prune () {
this[CACHE].forEach((value, key) => get(this, key, false))
}
}
const get = (self, key, doUse) => {
const node = self[CACHE].get(key)
if (node) {
const hit = node.value
if (isStale(self, hit)) {
del(self, node)
if (!self[ALLOW_STALE])
return undefined
} else {
if (doUse) {
if (self[UPDATE_AGE_ON_GET])
node.value.now = Date.now()
self[LRU_LIST].unshiftNode(node)
}
}
return hit.value
}
}
const isStale = (self, hit) => {
if (!hit || (!hit.maxAge && !self[MAX_AGE]))
return false
const diff = Date.now() - hit.now
return hit.maxAge ? diff > hit.maxAge
: self[MAX_AGE] && (diff > self[MAX_AGE])
}
const trim = self => {
if (self[LENGTH] > self[MAX]) {
for (let walker = self[LRU_LIST].tail;
self[LENGTH] > self[MAX] && walker !== null;) {
// We know that we're about to delete this one, and also
// what the next least recently used key will be, so just
// go ahead and set it now.
const prev = walker.prev
del(self, walker)
walker = prev
}
}
}
const del = (self, node) => {
if (node) {
const hit = node.value
if (self[DISPOSE])
self[DISPOSE](hit.key, hit.value)
self[LENGTH] -= hit.length
self[CACHE].delete(hit.key)
self[LRU_LIST].removeNode(node)
}
}
class Entry {
constructor (key, value, length, now, maxAge) {
this.key = key
this.value = value
this.length = length
this.now = now
this.maxAge = maxAge || 0
}
}
const forEachStep = (self, fn, node, thisp) => {
let hit = node.value
if (isStale(self, hit)) {
del(self, node)
if (!self[ALLOW_STALE])
hit = undefined
}
if (hit)
fn.call(thisp, hit.value, hit.key, self)
}
module.exports = LRUCache
module.exports = require("child_process");
/***/ }), /***/ }),
@@ -2495,7 +2850,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const os = __importStar(__webpack_require__(87)); const os = __importStar(__webpack_require__(87));
const events = __importStar(__webpack_require__(614)); const events = __importStar(__webpack_require__(614));
const child = __importStar(__webpack_require__(129)); const child = __importStar(__webpack_require__(4));
const path = __importStar(__webpack_require__(622)); const path = __importStar(__webpack_require__(622));
const io = __importStar(__webpack_require__(436)); const io = __importStar(__webpack_require__(436));
const ioUtil = __importStar(__webpack_require__(962)); const ioUtil = __importStar(__webpack_require__(962));
@@ -3102,6 +3457,7 @@ const minVersion = (range, loose) => {
for (let i = 0; i < range.set.length; ++i) { for (let i = 0; i < range.set.length; ++i) {
const comparators = range.set[i] const comparators = range.set[i]
let setMin = null
comparators.forEach((comparator) => { comparators.forEach((comparator) => {
// Clone to avoid manipulating the comparator's semver object. // Clone to avoid manipulating the comparator's semver object.
const compver = new SemVer(comparator.semver.version) const compver = new SemVer(comparator.semver.version)
@@ -3116,8 +3472,8 @@ const minVersion = (range, loose) => {
/* fallthrough */ /* fallthrough */
case '': case '':
case '>=': case '>=':
if (!minver || gt(minver, compver)) { if (!setMin || gt(compver, setMin)) {
minver = compver setMin = compver
} }
break break
case '<': case '<':
@@ -3129,6 +3485,8 @@ const minVersion = (range, loose) => {
throw new Error(`Unexpected operation: ${comparator.operator}`) throw new Error(`Unexpected operation: ${comparator.operator}`)
} }
}) })
if (setMin && (!minver || gt(minver, setMin)))
minver = setMin
} }
if (minver && range.test(minver)) { if (minver && range.test(minver)) {
@@ -4223,10 +4581,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.parseVersion = exports.getVersion = exports.isAvailable = exports.hasGitAuthToken = exports.isLocalOrTarExporter = exports.getSecret = exports.getImageID = exports.getImageIDFile = void 0; exports.parseVersion = exports.getVersion = exports.isAvailable = exports.hasGitAuthToken = exports.isLocalOrTarExporter = exports.getSecret = exports.getSecretFile = exports.getSecretString = exports.getImageID = exports.getImageIDFile = void 0;
const sync_1 = __importDefault(__webpack_require__(750));
const fs_1 = __importDefault(__webpack_require__(747)); const fs_1 = __importDefault(__webpack_require__(747));
const path_1 = __importDefault(__webpack_require__(622)); const path_1 = __importDefault(__webpack_require__(622));
const sync_1 = __importDefault(__webpack_require__(750));
const semver = __importStar(__webpack_require__(383)); const semver = __importStar(__webpack_require__(383));
const context = __importStar(__webpack_require__(842)); const context = __importStar(__webpack_require__(842));
const exec = __importStar(__webpack_require__(757)); const exec = __importStar(__webpack_require__(757));
@@ -4246,15 +4604,36 @@ function getImageID() {
}); });
} }
exports.getImageID = getImageID; exports.getImageID = getImageID;
function getSecret(kvp) { function getSecretString(kvp) {
return __awaiter(this, void 0, void 0, function* () {
return getSecret(kvp, false);
});
}
exports.getSecretString = getSecretString;
function getSecretFile(kvp) {
return __awaiter(this, void 0, void 0, function* () {
return getSecret(kvp, true);
});
}
exports.getSecretFile = getSecretFile;
function getSecret(kvp, file) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const delimiterIndex = kvp.indexOf('='); const delimiterIndex = kvp.indexOf('=');
const key = kvp.substring(0, delimiterIndex); const key = kvp.substring(0, delimiterIndex);
const value = kvp.substring(delimiterIndex + 1); let value = kvp.substring(delimiterIndex + 1);
if (key.length == 0 || value.length == 0) {
throw new Error(`${kvp} is not a valid secret`);
}
if (file) {
if (!fs_1.default.existsSync(value)) {
throw new Error(`secret file ${value} not found`);
}
value = fs_1.default.readFileSync(value, { encoding: 'utf-8' });
}
const secretFile = context.tmpNameSync({ const secretFile = context.tmpNameSync({
tmpdir: context.tmpDir() tmpdir: context.tmpDir()
}); });
yield fs_1.default.writeFileSync(secretFile, value); fs_1.default.writeFileSync(secretFile, value);
return `id=${key},src=${secretFile}`; return `id=${key},src=${secretFile}`;
}); });
} }
@@ -4264,7 +4643,7 @@ function isLocalOrTarExporter(outputs) {
delimiter: ',', delimiter: ',',
trim: true, trim: true,
columns: false, columns: false,
relax_column_count: true relaxColumnCount: true
})) { })) {
// Local if no type is defined // Local if no type is defined
// https://github.com/docker/buildx/blob/d2bf42f8b4784d83fde17acb3ed84703ddc2156b/build/output.go#L29-L43 // https://github.com/docker/buildx/blob/d2bf42f8b4784d83fde17acb3ed84703ddc2156b/build/output.go#L29-L43
@@ -4655,7 +5034,7 @@ const outside = (version, range, hilo, options) => {
throw new TypeError('Must provide a hilo val of "<" or ">"') throw new TypeError('Must provide a hilo val of "<" or ">"')
} }
// If it satisifes the range it is not outside // If it satisfies the range it is not outside
if (satisfies(version, range, options)) { if (satisfies(version, range, options)) {
return false return false
} }
@@ -4772,7 +5151,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}); });
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const childProcess = __webpack_require__(129); const childProcess = __webpack_require__(4);
const path = __webpack_require__(622); const path = __webpack_require__(622);
const util_1 = __webpack_require__(669); const util_1 = __webpack_require__(669);
const ioUtil = __webpack_require__(962); const ioUtil = __webpack_require__(962);
@@ -8423,12 +8802,7 @@ class Comparator {
return ANY return ANY
} }
constructor (comp, options) { constructor (comp, options) {
if (!options || typeof options !== 'object') { options = parseOptions(options)
options = {
loose: !!options,
includePrerelease: false
}
}
if (comp instanceof Comparator) { if (comp instanceof Comparator) {
if (comp.loose === !!options.loose) { if (comp.loose === !!options.loose) {
@@ -8550,6 +8924,7 @@ class Comparator {
module.exports = Comparator module.exports = Comparator
const parseOptions = __webpack_require__(785)
const {re, t} = __webpack_require__(523) const {re, t} = __webpack_require__(523)
const cmp = __webpack_require__(98) const cmp = __webpack_require__(98)
const debug = __webpack_require__(427) const debug = __webpack_require__(427)
@@ -9118,6 +9493,440 @@ if (!exports.IsPost) {
} }
//# sourceMappingURL=state-helper.js.map //# sourceMappingURL=state-helper.js.map
/***/ }),
/***/ 665:
/***/ (function(module, __unusedexports, __webpack_require__) {
"use strict";
module.exports = Yallist
Yallist.Node = Node
Yallist.create = Yallist
function Yallist (list) {
var self = this
if (!(self instanceof Yallist)) {
self = new Yallist()
}
self.tail = null
self.head = null
self.length = 0
if (list && typeof list.forEach === 'function') {
list.forEach(function (item) {
self.push(item)
})
} else if (arguments.length > 0) {
for (var i = 0, l = arguments.length; i < l; i++) {
self.push(arguments[i])
}
}
return self
}
Yallist.prototype.removeNode = function (node) {
if (node.list !== this) {
throw new Error('removing node which does not belong to this list')
}
var next = node.next
var prev = node.prev
if (next) {
next.prev = prev
}
if (prev) {
prev.next = next
}
if (node === this.head) {
this.head = next
}
if (node === this.tail) {
this.tail = prev
}
node.list.length--
node.next = null
node.prev = null
node.list = null
return next
}
Yallist.prototype.unshiftNode = function (node) {
if (node === this.head) {
return
}
if (node.list) {
node.list.removeNode(node)
}
var head = this.head
node.list = this
node.next = head
if (head) {
head.prev = node
}
this.head = node
if (!this.tail) {
this.tail = node
}
this.length++
}
Yallist.prototype.pushNode = function (node) {
if (node === this.tail) {
return
}
if (node.list) {
node.list.removeNode(node)
}
var tail = this.tail
node.list = this
node.prev = tail
if (tail) {
tail.next = node
}
this.tail = node
if (!this.head) {
this.head = node
}
this.length++
}
Yallist.prototype.push = function () {
for (var i = 0, l = arguments.length; i < l; i++) {
push(this, arguments[i])
}
return this.length
}
Yallist.prototype.unshift = function () {
for (var i = 0, l = arguments.length; i < l; i++) {
unshift(this, arguments[i])
}
return this.length
}
Yallist.prototype.pop = function () {
if (!this.tail) {
return undefined
}
var res = this.tail.value
this.tail = this.tail.prev
if (this.tail) {
this.tail.next = null
} else {
this.head = null
}
this.length--
return res
}
Yallist.prototype.shift = function () {
if (!this.head) {
return undefined
}
var res = this.head.value
this.head = this.head.next
if (this.head) {
this.head.prev = null
} else {
this.tail = null
}
this.length--
return res
}
Yallist.prototype.forEach = function (fn, thisp) {
thisp = thisp || this
for (var walker = this.head, i = 0; walker !== null; i++) {
fn.call(thisp, walker.value, i, this)
walker = walker.next
}
}
Yallist.prototype.forEachReverse = function (fn, thisp) {
thisp = thisp || this
for (var walker = this.tail, i = this.length - 1; walker !== null; i--) {
fn.call(thisp, walker.value, i, this)
walker = walker.prev
}
}
Yallist.prototype.get = function (n) {
for (var i = 0, walker = this.head; walker !== null && i < n; i++) {
// abort out of the list early if we hit a cycle
walker = walker.next
}
if (i === n && walker !== null) {
return walker.value
}
}
Yallist.prototype.getReverse = function (n) {
for (var i = 0, walker = this.tail; walker !== null && i < n; i++) {
// abort out of the list early if we hit a cycle
walker = walker.prev
}
if (i === n && walker !== null) {
return walker.value
}
}
Yallist.prototype.map = function (fn, thisp) {
thisp = thisp || this
var res = new Yallist()
for (var walker = this.head; walker !== null;) {
res.push(fn.call(thisp, walker.value, this))
walker = walker.next
}
return res
}
Yallist.prototype.mapReverse = function (fn, thisp) {
thisp = thisp || this
var res = new Yallist()
for (var walker = this.tail; walker !== null;) {
res.push(fn.call(thisp, walker.value, this))
walker = walker.prev
}
return res
}
Yallist.prototype.reduce = function (fn, initial) {
var acc
var walker = this.head
if (arguments.length > 1) {
acc = initial
} else if (this.head) {
walker = this.head.next
acc = this.head.value
} else {
throw new TypeError('Reduce of empty list with no initial value')
}
for (var i = 0; walker !== null; i++) {
acc = fn(acc, walker.value, i)
walker = walker.next
}
return acc
}
Yallist.prototype.reduceReverse = function (fn, initial) {
var acc
var walker = this.tail
if (arguments.length > 1) {
acc = initial
} else if (this.tail) {
walker = this.tail.prev
acc = this.tail.value
} else {
throw new TypeError('Reduce of empty list with no initial value')
}
for (var i = this.length - 1; walker !== null; i--) {
acc = fn(acc, walker.value, i)
walker = walker.prev
}
return acc
}
Yallist.prototype.toArray = function () {
var arr = new Array(this.length)
for (var i = 0, walker = this.head; walker !== null; i++) {
arr[i] = walker.value
walker = walker.next
}
return arr
}
Yallist.prototype.toArrayReverse = function () {
var arr = new Array(this.length)
for (var i = 0, walker = this.tail; walker !== null; i++) {
arr[i] = walker.value
walker = walker.prev
}
return arr
}
Yallist.prototype.slice = function (from, to) {
to = to || this.length
if (to < 0) {
to += this.length
}
from = from || 0
if (from < 0) {
from += this.length
}
var ret = new Yallist()
if (to < from || to < 0) {
return ret
}
if (from < 0) {
from = 0
}
if (to > this.length) {
to = this.length
}
for (var i = 0, walker = this.head; walker !== null && i < from; i++) {
walker = walker.next
}
for (; walker !== null && i < to; i++, walker = walker.next) {
ret.push(walker.value)
}
return ret
}
Yallist.prototype.sliceReverse = function (from, to) {
to = to || this.length
if (to < 0) {
to += this.length
}
from = from || 0
if (from < 0) {
from += this.length
}
var ret = new Yallist()
if (to < from || to < 0) {
return ret
}
if (from < 0) {
from = 0
}
if (to > this.length) {
to = this.length
}
for (var i = this.length, walker = this.tail; walker !== null && i > to; i--) {
walker = walker.prev
}
for (; walker !== null && i > from; i--, walker = walker.prev) {
ret.push(walker.value)
}
return ret
}
Yallist.prototype.splice = function (start, deleteCount, ...nodes) {
if (start > this.length) {
start = this.length - 1
}
if (start < 0) {
start = this.length + start;
}
for (var i = 0, walker = this.head; walker !== null && i < start; i++) {
walker = walker.next
}
var ret = []
for (var i = 0; walker && i < deleteCount; i++) {
ret.push(walker.value)
walker = this.removeNode(walker)
}
if (walker === null) {
walker = this.tail
}
if (walker !== this.head && walker !== this.tail) {
walker = walker.prev
}
for (var i = 0; i < nodes.length; i++) {
walker = insert(this, walker, nodes[i])
}
return ret;
}
Yallist.prototype.reverse = function () {
var head = this.head
var tail = this.tail
for (var walker = head; walker !== null; walker = walker.prev) {
var p = walker.prev
walker.prev = walker.next
walker.next = p
}
this.head = tail
this.tail = head
return this
}
function insert (self, node, value) {
var inserted = node === self.head ?
new Node(value, null, node, self) :
new Node(value, node, node.next, self)
if (inserted.next === null) {
self.tail = inserted
}
if (inserted.prev === null) {
self.head = inserted
}
self.length++
return inserted
}
function push (self, item) {
self.tail = new Node(item, self.tail, null, self)
if (!self.head) {
self.head = self.tail
}
self.length++
}
function unshift (self, item) {
self.head = new Node(item, null, self.head, self)
if (!self.tail) {
self.tail = self.head
}
self.length++
}
function Node (value, prev, next, list) {
if (!(this instanceof Node)) {
return new Node(value, prev, next, list)
}
this.list = list
this.value = value
if (prev) {
prev.next = this
this.prev = prev
} else {
this.prev = null
}
if (next) {
next.prev = this
this.next = next
} else {
this.next = null
}
}
try {
// add if support for Symbol.iterator is present
__webpack_require__(91)(Yallist)
} catch (er) {}
/***/ }), /***/ }),
/***/ 668: /***/ 668:
@@ -10285,6 +11094,24 @@ exports.Octokit = Octokit;
//# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map
/***/ }),
/***/ 785:
/***/ (function(module) {
// parse out just the options we care about so we always get a consistent
// obj with keys in a consistent order.
const opts = ['includePrerelease', 'loose', 'rtl']
const parseOptions = options =>
!options ? {}
: typeof options !== 'object' ? { loose: true }
: opts.filter(k => options[k]).reduce((options, k) => {
options[k] = true
return options
}, {})
module.exports = parseOptions
/***/ }), /***/ }),
/***/ 804: /***/ 804:
@@ -10334,12 +11161,7 @@ function removeHook (state, name, method) {
// hoisted class for cyclic dependency // hoisted class for cyclic dependency
class Range { class Range {
constructor (range, options) { constructor (range, options) {
if (!options || typeof options !== 'object') { options = parseOptions(options)
options = {
loose: !!options,
includePrerelease: false
}
}
if (range instanceof Range) { if (range instanceof Range) {
if ( if (
@@ -10379,6 +11201,24 @@ class Range {
throw new TypeError(`Invalid SemVer Range: ${range}`) throw new TypeError(`Invalid SemVer Range: ${range}`)
} }
// if we have any that are not the null set, throw out null sets.
if (this.set.length > 1) {
// keep the first one, in case they're all null sets
const first = this.set[0]
this.set = this.set.filter(c => !isNullSet(c[0]))
if (this.set.length === 0)
this.set = [first]
else if (this.set.length > 1) {
// if we have any that are *, then the range is just *
for (const c of this.set) {
if (c.length === 1 && isAny(c[0])) {
this.set = [c]
break
}
}
}
}
this.format() this.format()
} }
@@ -10397,8 +11237,17 @@ class Range {
} }
parseRange (range) { parseRange (range) {
const loose = this.options.loose
range = range.trim() range = range.trim()
// memoize range parsing for performance.
// this is a very hot path, and fully deterministic.
const memoOpts = Object.keys(this.options).join(',')
const memoKey = `parseRange:${memoOpts}:${range}`
const cached = cache.get(memoKey)
if (cached)
return cached
const loose = this.options.loose
// `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4`
const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE] const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE]
range = range.replace(hr, hyphenReplace(this.options.includePrerelease)) range = range.replace(hr, hyphenReplace(this.options.includePrerelease))
@@ -10420,15 +11269,33 @@ class Range {
// ready to be split into comparators. // ready to be split into comparators.
const compRe = loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR] const compRe = loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR]
return range const rangeList = range
.split(' ') .split(' ')
.map(comp => parseComparator(comp, this.options)) .map(comp => parseComparator(comp, this.options))
.join(' ') .join(' ')
.split(/\s+/) .split(/\s+/)
// >=0.0.0 is equivalent to *
.map(comp => replaceGTE0(comp, this.options)) .map(comp => replaceGTE0(comp, this.options))
// in loose mode, throw out any that are not valid comparators // in loose mode, throw out any that are not valid comparators
.filter(this.options.loose ? comp => !!comp.match(compRe) : () => true) .filter(this.options.loose ? comp => !!comp.match(compRe) : () => true)
.map(comp => new Comparator(comp, this.options)) .map(comp => new Comparator(comp, this.options))
// if any comparators are the null set, then replace with JUST null set
// if more than one comparator, remove any * comparators
// also, don't include the same comparator more than once
const l = rangeList.length
const rangeMap = new Map()
for (const comp of rangeList) {
if (isNullSet(comp))
return [comp]
rangeMap.set(comp.value, comp)
}
if (rangeMap.size > 1 && rangeMap.has(''))
rangeMap.delete('')
const result = [...rangeMap.values()]
cache.set(memoKey, result)
return result
} }
intersects (range, options) { intersects (range, options) {
@@ -10477,6 +11344,10 @@ class Range {
} }
module.exports = Range module.exports = Range
const LRU = __webpack_require__(129)
const cache = new LRU({ max: 1000 })
const parseOptions = __webpack_require__(785)
const Comparator = __webpack_require__(532) const Comparator = __webpack_require__(532)
const debug = __webpack_require__(427) const debug = __webpack_require__(427)
const SemVer = __webpack_require__(88) const SemVer = __webpack_require__(88)
@@ -10488,6 +11359,9 @@ const {
caretTrimReplace caretTrimReplace
} = __webpack_require__(523) } = __webpack_require__(523)
const isNullSet = c => c.value === '<0.0.0-0'
const isAny = c => c.value === ''
// take a set of comparators and determine whether there // take a set of comparators and determine whether there
// exists a version which can satisfy it // exists a version which can satisfy it
const isSatisfiable = (comparators, options) => { const isSatisfiable = (comparators, options) => {
@@ -10812,21 +11686,53 @@ additional information.
const { Transform } = __webpack_require__(413) const { Transform } = __webpack_require__(413)
const ResizeableBuffer = __webpack_require__(942) const ResizeableBuffer = __webpack_require__(942)
// white space characters
// https://en.wikipedia.org/wiki/Whitespace_character
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes#Types
// \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff
const tab = 9 const tab = 9
const nl = 10 const nl = 10 // \n, 0x0A in hexadecimal, 10 in decimal
const np = 12 const np = 12
const cr = 13 const cr = 13 // \r, 0x0D in hexadécimal, 13 in decimal
const space = 32 const space = 32
const bom_utf8 = Buffer.from([239, 187, 191]) const boms = {
// Note, the following are equals:
// Buffer.from("\ufeff")
// Buffer.from([239, 187, 191])
// Buffer.from('EFBBBF', 'hex')
'utf8': Buffer.from([239, 187, 191]),
// Note, the following are equals:
// Buffer.from "\ufeff", 'utf16le
// Buffer.from([255, 254])
'utf16le': Buffer.from([255, 254])
}
class Parser extends Transform { class Parser extends Transform {
constructor(opts = {}){ constructor(opts = {}){
super({...{readableObjectMode: true}, ...opts}) super({...{readableObjectMode: true}, ...opts, encoding: null})
this.__originalOptions = opts
this.__normalizeOptions(opts)
}
__normalizeOptions(opts){
const options = {} const options = {}
// Merge with user options // Merge with user options
for(let opt in opts){ for(let opt in opts){
options[underscore(opt)] = opts[opt] options[underscore(opt)] = opts[opt]
} }
// Normalize option `encoding`
// Note: defined first because other options depends on it
// to convert chars/strings into buffers.
if(options.encoding === undefined || options.encoding === true){
options.encoding = 'utf8'
}else if(options.encoding === null || options.encoding === false){
options.encoding = null
}else if(typeof options.encoding !== 'string' && options.encoding !== null){
throw new CsvError('CSV_INVALID_OPTION_ENCODING', [
'Invalid option encoding:',
'encoding must be a string or null to return a buffer,',
`got ${JSON.stringify(options.encoding)}`
], options)
}
// Normalize option `bom` // Normalize option `bom`
if(options.bom === undefined || options.bom === null || options.bom === false){ if(options.bom === undefined || options.bom === null || options.bom === false){
options.bom = false options.bom = false
@@ -10834,7 +11740,7 @@ class Parser extends Transform {
throw new CsvError('CSV_INVALID_OPTION_BOM', [ throw new CsvError('CSV_INVALID_OPTION_BOM', [
'Invalid option bom:', 'bom must be true,', 'Invalid option bom:', 'bom must be true,',
`got ${JSON.stringify(options.bom)}` `got ${JSON.stringify(options.bom)}`
]) ], options)
} }
// Normalize option `cast` // Normalize option `cast`
let fnCastField = null let fnCastField = null
@@ -10847,7 +11753,7 @@ class Parser extends Transform {
throw new CsvError('CSV_INVALID_OPTION_CAST', [ throw new CsvError('CSV_INVALID_OPTION_CAST', [
'Invalid option cast:', 'cast must be true or a function,', 'Invalid option cast:', 'cast must be true or a function,',
`got ${JSON.stringify(options.cast)}` `got ${JSON.stringify(options.cast)}`
]) ], options)
} }
// Normalize option `cast_date` // Normalize option `cast_date`
if(options.cast_date === undefined || options.cast_date === null || options.cast_date === false || options.cast_date === ''){ if(options.cast_date === undefined || options.cast_date === null || options.cast_date === false || options.cast_date === ''){
@@ -10861,7 +11767,7 @@ class Parser extends Transform {
throw new CsvError('CSV_INVALID_OPTION_CAST_DATE', [ throw new CsvError('CSV_INVALID_OPTION_CAST_DATE', [
'Invalid option cast_date:', 'cast_date must be true or a function,', 'Invalid option cast_date:', 'cast_date must be true or a function,',
`got ${JSON.stringify(options.cast_date)}` `got ${JSON.stringify(options.cast_date)}`
]) ], options)
} }
// Normalize option `columns` // Normalize option `columns`
let fnFirstLineToHeaders = null let fnFirstLineToHeaders = null
@@ -10880,7 +11786,7 @@ class Parser extends Transform {
'Invalid option columns:', 'Invalid option columns:',
'expect an object, a function or true,', 'expect an object, a function or true,',
`got ${JSON.stringify(options.columns)}` `got ${JSON.stringify(options.columns)}`
]) ], options)
} }
// Normalize option `columns_duplicates_to_array` // Normalize option `columns_duplicates_to_array`
if(options.columns_duplicates_to_array === undefined || options.columns_duplicates_to_array === null || options.columns_duplicates_to_array === false){ if(options.columns_duplicates_to_array === undefined || options.columns_duplicates_to_array === null || options.columns_duplicates_to_array === false){
@@ -10890,21 +11796,21 @@ class Parser extends Transform {
'Invalid option columns_duplicates_to_array:', 'Invalid option columns_duplicates_to_array:',
'expect an boolean,', 'expect an boolean,',
`got ${JSON.stringify(options.columns_duplicates_to_array)}` `got ${JSON.stringify(options.columns_duplicates_to_array)}`
]) ], options)
} }
// Normalize option `comment` // Normalize option `comment`
if(options.comment === undefined || options.comment === null || options.comment === false || options.comment === ''){ if(options.comment === undefined || options.comment === null || options.comment === false || options.comment === ''){
options.comment = null options.comment = null
}else{ }else{
if(typeof options.comment === 'string'){ if(typeof options.comment === 'string'){
options.comment = Buffer.from(options.comment) options.comment = Buffer.from(options.comment, options.encoding)
} }
if(!Buffer.isBuffer(options.comment)){ if(!Buffer.isBuffer(options.comment)){
throw new CsvError('CSV_INVALID_OPTION_COMMENT', [ throw new CsvError('CSV_INVALID_OPTION_COMMENT', [
'Invalid option comment:', 'Invalid option comment:',
'comment must be a buffer or a string,', 'comment must be a buffer or a string,',
`got ${JSON.stringify(options.comment)}` `got ${JSON.stringify(options.comment)}`
]) ], options)
} }
} }
// Normalize option `delimiter` // Normalize option `delimiter`
@@ -10915,39 +11821,35 @@ class Parser extends Transform {
'Invalid option delimiter:', 'Invalid option delimiter:',
'delimiter must be a non empty string or buffer or array of string|buffer,', 'delimiter must be a non empty string or buffer or array of string|buffer,',
`got ${delimiter_json}` `got ${delimiter_json}`
]) ], options)
} }
options.delimiter = options.delimiter.map(function(delimiter){ options.delimiter = options.delimiter.map(function(delimiter){
if(delimiter === undefined || delimiter === null || delimiter === false){ if(delimiter === undefined || delimiter === null || delimiter === false){
return Buffer.from(',') return Buffer.from(',', options.encoding)
} }
if(typeof delimiter === 'string'){ if(typeof delimiter === 'string'){
delimiter = Buffer.from(delimiter) delimiter = Buffer.from(delimiter, options.encoding)
} }
if( !Buffer.isBuffer(delimiter) || delimiter.length === 0){ if( !Buffer.isBuffer(delimiter) || delimiter.length === 0){
throw new CsvError('CSV_INVALID_OPTION_DELIMITER', [ throw new CsvError('CSV_INVALID_OPTION_DELIMITER', [
'Invalid option delimiter:', 'Invalid option delimiter:',
'delimiter must be a non empty string or buffer or array of string|buffer,', 'delimiter must be a non empty string or buffer or array of string|buffer,',
`got ${delimiter_json}` `got ${delimiter_json}`
]) ], options)
} }
return delimiter return delimiter
}) })
// Normalize option `escape` // Normalize option `escape`
if(options.escape === undefined || options.escape === true){ if(options.escape === undefined || options.escape === true){
options.escape = Buffer.from('"') options.escape = Buffer.from('"', options.encoding)
}else if(typeof options.escape === 'string'){ }else if(typeof options.escape === 'string'){
options.escape = Buffer.from(options.escape) options.escape = Buffer.from(options.escape, options.encoding)
}else if (options.escape === null || options.escape === false){ }else if (options.escape === null || options.escape === false){
options.escape = null options.escape = null
} }
if(options.escape !== null){ if(options.escape !== null){
if(!Buffer.isBuffer(options.escape)){ if(!Buffer.isBuffer(options.escape)){
throw new Error(`Invalid Option: escape must be a buffer, a string or a boolean, got ${JSON.stringify(options.escape)}`) throw new Error(`Invalid Option: escape must be a buffer, a string or a boolean, got ${JSON.stringify(options.escape)}`)
}else if(options.escape.length !== 1){
throw new Error(`Invalid Option Length: escape must be one character, got ${options.escape.length}`)
}else{
options.escape = options.escape[0]
} }
} }
// Normalize option `from` // Normalize option `from`
@@ -10980,6 +11882,27 @@ class Parser extends Transform {
throw new Error(`Invalid Option: from_line must be an integer, got ${JSON.stringify(opts.from_line)}`) throw new Error(`Invalid Option: from_line must be an integer, got ${JSON.stringify(opts.from_line)}`)
} }
} }
// Normalize options `ignore_last_delimiters`
if(options.ignore_last_delimiters === undefined || options.ignore_last_delimiters === null){
options.ignore_last_delimiters = false
}else if(typeof options.ignore_last_delimiters === 'number'){
options.ignore_last_delimiters = Math.floor(options.ignore_last_delimiters)
if(options.ignore_last_delimiters === 0){
options.ignore_last_delimiters = false
}
}else if(typeof options.ignore_last_delimiters !== 'boolean'){
throw new CsvError('CSV_INVALID_OPTION_IGNORE_LAST_DELIMITERS', [
'Invalid option `ignore_last_delimiters`:',
'the value must be a boolean value or an integer,',
`got ${JSON.stringify(options.ignore_last_delimiters)}`
], options)
}
if(options.ignore_last_delimiters === true && options.columns === false){
throw new CsvError('CSV_IGNORE_LAST_DELIMITERS_REQUIRES_COLUMNS', [
'The option `ignore_last_delimiters`',
'requires the activation of the `columns` option'
], options)
}
// Normalize option `info` // Normalize option `info`
if(options.info === undefined || options.info === null || options.info === false){ if(options.info === undefined || options.info === null || options.info === false){
options.info = false options.info = false
@@ -11003,7 +11926,11 @@ class Parser extends Transform {
if(options.objname.length === 0){ if(options.objname.length === 0){
throw new Error(`Invalid Option: objname must be a non empty buffer`) throw new Error(`Invalid Option: objname must be a non empty buffer`)
} }
options.objname = options.objname.toString() if(options.encoding === null){
// Don't call `toString`, leave objname as a buffer
}else{
options.objname = options.objname.toString(options.encoding)
}
}else if(typeof options.objname === 'string'){ }else if(typeof options.objname === 'string'){
if(options.objname.length === 0){ if(options.objname.length === 0){
throw new Error(`Invalid Option: objname must be a non empty string`) throw new Error(`Invalid Option: objname must be a non empty string`)
@@ -11020,23 +11947,19 @@ class Parser extends Transform {
'Invalid option `on_record`:', 'Invalid option `on_record`:',
'expect a function,', 'expect a function,',
`got ${JSON.stringify(options.on_record)}` `got ${JSON.stringify(options.on_record)}`
]) ], options)
} }
// Normalize option `quote` // Normalize option `quote`
if(options.quote === null || options.quote === false || options.quote === ''){ if(options.quote === null || options.quote === false || options.quote === ''){
options.quote = null options.quote = null
}else{ }else{
if(options.quote === undefined || options.quote === true){ if(options.quote === undefined || options.quote === true){
options.quote = Buffer.from('"') options.quote = Buffer.from('"', options.encoding)
}else if(typeof options.quote === 'string'){ }else if(typeof options.quote === 'string'){
options.quote = Buffer.from(options.quote) options.quote = Buffer.from(options.quote, options.encoding)
} }
if(!Buffer.isBuffer(options.quote)){ if(!Buffer.isBuffer(options.quote)){
throw new Error(`Invalid Option: quote must be a buffer or a string, got ${JSON.stringify(options.quote)}`) throw new Error(`Invalid Option: quote must be a buffer or a string, got ${JSON.stringify(options.quote)}`)
}else if(options.quote.length !== 1){
throw new Error(`Invalid Option Length: quote must be one character, got ${options.quote.length}`)
}else{
options.quote = options.quote[0]
} }
} }
// Normalize option `raw` // Normalize option `raw`
@@ -11053,7 +11976,7 @@ class Parser extends Transform {
} }
options.record_delimiter = options.record_delimiter.map( function(rd){ options.record_delimiter = options.record_delimiter.map( function(rd){
if(typeof rd === 'string'){ if(typeof rd === 'string'){
rd = Buffer.from(rd) rd = Buffer.from(rd, options.encoding)
} }
return rd return rd
}) })
@@ -11182,13 +12105,24 @@ class Parser extends Transform {
bomSkipped: false, bomSkipped: false,
castField: fnCastField, castField: fnCastField,
commenting: false, commenting: false,
// Current error encountered by a record
error: undefined,
enabled: options.from_line === 1, enabled: options.from_line === 1,
escaping: false, escaping: false,
escapeIsQuote: options.escape === options.quote, // escapeIsQuote: options.escape === options.quote,
escapeIsQuote: Buffer.isBuffer(options.escape) && Buffer.isBuffer(options.quote) && Buffer.compare(options.escape, options.quote) === 0,
expectedRecordLength: options.columns === null ? 0 : options.columns.length, expectedRecordLength: options.columns === null ? 0 : options.columns.length,
field: new ResizeableBuffer(20), field: new ResizeableBuffer(20),
firstLineToHeaders: fnFirstLineToHeaders, firstLineToHeaders: fnFirstLineToHeaders,
info: Object.assign({}, this.info), info: Object.assign({}, this.info),
needMoreDataSize: Math.max(
// Skip if the remaining buffer smaller than comment
options.comment !== null ? options.comment.length : 0,
// Skip if the remaining buffer can be delimiter
...options.delimiter.map( (delimiter) => delimiter.length),
// Skip if the remaining buffer can be escape sequence
options.quote !== null ? options.quote.length : 0,
),
previousBuf: undefined, previousBuf: undefined,
quoting: false, quoting: false,
stop: false, stop: false,
@@ -11197,7 +12131,7 @@ class Parser extends Transform {
recordHasError: false, recordHasError: false,
record_length: 0, record_length: 0,
recordDelimiterMaxLength: options.record_delimiter.length === 0 ? 2 : Math.max(...options.record_delimiter.map( (v) => v.length)), recordDelimiterMaxLength: options.record_delimiter.length === 0 ? 2 : Math.max(...options.record_delimiter.map( (v) => v.length)),
trimChars: [Buffer.from(' ')[0], Buffer.from('\t')[0]], trimChars: [Buffer.from(' ', options.encoding)[0], Buffer.from('\t', options.encoding)[0]],
wasQuoting: false, wasQuoting: false,
wasRowDelimiter: false wasRowDelimiter: false
} }
@@ -11251,11 +12185,15 @@ class Parser extends Transform {
this.state.previousBuf = buf this.state.previousBuf = buf
return return
} }
// skip BOM detect because data length < 3
}else{ }else{
if(bom_utf8.compare(buf, 0, 3) === 0){ for(let encoding in boms){
// Skip BOM if(boms[encoding].compare(buf, 0, boms[encoding].length) === 0){
buf = buf.slice(3) // Skip BOM
buf = buf.slice(boms[encoding].length)
// Renormalize original options with the new encoding
this.__normalizeOptions({...this.__originalOptions, encoding: encoding})
break
}
} }
this.state.bomSkipped = true this.state.bomSkipped = true
} }
@@ -11282,7 +12220,7 @@ class Parser extends Transform {
} }
// Auto discovery of record_delimiter, unix, mac and windows supported // Auto discovery of record_delimiter, unix, mac and windows supported
if(this.state.quoting === false && record_delimiter.length === 0){ if(this.state.quoting === false && record_delimiter.length === 0){
const record_delimiterCount = this.__autoDiscoverRowDelimiter(buf, pos) const record_delimiterCount = this.__autoDiscoverRecordDelimiter(buf, pos)
if(record_delimiterCount){ if(record_delimiterCount){
record_delimiter = this.options.record_delimiter record_delimiter = this.options.record_delimiter
} }
@@ -11301,35 +12239,37 @@ class Parser extends Transform {
}else{ }else{
// Escape is only active inside quoted fields // Escape is only active inside quoted fields
// We are quoting, the char is an escape chr and there is a chr to escape // We are quoting, the char is an escape chr and there is a chr to escape
if(escape !== null && this.state.quoting === true && chr === escape && pos + 1 < bufLen){ // if(escape !== null && this.state.quoting === true && chr === escape && pos + 1 < bufLen){
if(escape !== null && this.state.quoting === true && this.__isEscape(buf, pos, chr) && pos + escape.length < bufLen){
if(escapeIsQuote){ if(escapeIsQuote){
if(buf[pos+1] === quote){ if(this.__isQuote(buf, pos+escape.length)){
this.state.escaping = true this.state.escaping = true
pos += escape.length - 1
continue continue
} }
}else{ }else{
this.state.escaping = true this.state.escaping = true
pos += escape.length - 1
continue continue
} }
} }
// Not currently escaping and chr is a quote // Not currently escaping and chr is a quote
// TODO: need to compare bytes instead of single char // TODO: need to compare bytes instead of single char
if(this.state.commenting === false && chr === quote){ if(this.state.commenting === false && this.__isQuote(buf, pos)){
if(this.state.quoting === true){ if(this.state.quoting === true){
const nextChr = buf[pos+1] const nextChr = buf[pos+quote.length]
const isNextChrTrimable = rtrim && this.__isCharTrimable(nextChr) const isNextChrTrimable = rtrim && this.__isCharTrimable(nextChr)
// const isNextChrComment = nextChr === comment const isNextChrComment = comment !== null && this.__compareBytes(comment, buf, pos+quote.length, nextChr)
const isNextChrComment = comment !== null && this.__compareBytes(comment, buf, pos+1, nextChr) const isNextChrDelimiter = this.__isDelimiter(buf, pos+quote.length, nextChr)
const isNextChrDelimiter = this.__isDelimiter(nextChr, buf, pos+1) const isNextChrRecordDelimiter = record_delimiter.length === 0 ? this.__autoDiscoverRecordDelimiter(buf, pos+quote.length) : this.__isRecordDelimiter(nextChr, buf, pos+quote.length)
const isNextChrRowDelimiter = record_delimiter.length === 0 ? this.__autoDiscoverRowDelimiter(buf, pos+1) : this.__isRecordDelimiter(nextChr, buf, pos+1)
// Escape a quote // Escape a quote
// Treat next char as a regular character // Treat next char as a regular character
// TODO: need to compare bytes instead of single char if(escape !== null && this.__isEscape(buf, pos, chr) && this.__isQuote(buf, pos + escape.length)){
if(escape !== null && chr === escape && nextChr === quote){ pos += escape.length - 1
pos++ }else if(!nextChr || isNextChrDelimiter || isNextChrRecordDelimiter || isNextChrComment || isNextChrTrimable){
}else if(!nextChr || isNextChrDelimiter || isNextChrRowDelimiter || isNextChrComment || isNextChrTrimable){
this.state.quoting = false this.state.quoting = false
this.state.wasQuoting = true this.state.wasQuoting = true
pos += quote.length - 1
continue continue
}else if(relax === false){ }else if(relax === false){
const err = this.__error( const err = this.__error(
@@ -11337,16 +12277,16 @@ class Parser extends Transform {
'Invalid Closing Quote:', 'Invalid Closing Quote:',
`got "${String.fromCharCode(nextChr)}"`, `got "${String.fromCharCode(nextChr)}"`,
`at line ${this.info.lines}`, `at line ${this.info.lines}`,
'instead of delimiter, row delimiter, trimable character', 'instead of delimiter, record delimiter, trimable character',
'(if activated) or comment', '(if activated) or comment',
], this.__context()) ], this.options, this.__context())
) )
if(err !== undefined) return err if(err !== undefined) return err
}else{ }else{
this.state.quoting = false this.state.quoting = false
this.state.wasQuoting = true this.state.wasQuoting = true
// continue
this.state.field.prepend(quote) this.state.field.prepend(quote)
pos += quote.length - 1
} }
}else{ }else{
if(this.state.field.length !== 0){ if(this.state.field.length !== 0){
@@ -11356,7 +12296,7 @@ class Parser extends Transform {
new CsvError('INVALID_OPENING_QUOTE', [ new CsvError('INVALID_OPENING_QUOTE', [
'Invalid Opening Quote:', 'Invalid Opening Quote:',
`a quote is found inside a field at line ${this.info.lines}`, `a quote is found inside a field at line ${this.info.lines}`,
], this.__context(), { ], this.options, this.__context(), {
field: this.state.field, field: this.state.field,
}) })
) )
@@ -11364,6 +12304,7 @@ class Parser extends Transform {
} }
}else{ }else{
this.state.quoting = true this.state.quoting = true
pos += quote.length - 1
continue continue
} }
} }
@@ -11377,25 +12318,24 @@ class Parser extends Transform {
this.info.comment_lines++ this.info.comment_lines++
// Skip full comment line // Skip full comment line
}else{ }else{
// Activate records emition if above from_line
if(this.state.enabled === false && this.info.lines + (this.state.wasRowDelimiter === true ? 1: 0) >= from_line){
this.state.enabled = true
this.__resetField()
this.__resetRecord()
pos += recordDelimiterLength - 1
continue
}
// Skip if line is empty and skip_empty_lines activated // Skip if line is empty and skip_empty_lines activated
if(skip_empty_lines === true && this.state.wasQuoting === false && this.state.record.length === 0 && this.state.field.length === 0){ if(skip_empty_lines === true && this.state.wasQuoting === false && this.state.record.length === 0 && this.state.field.length === 0){
this.info.empty_lines++ this.info.empty_lines++
pos += recordDelimiterLength - 1 pos += recordDelimiterLength - 1
continue continue
} }
// Activate records emition if above from_line const errField = this.__onField()
if(this.state.enabled === false && this.info.lines + (this.state.wasRowDelimiter === true ? 1: 0 ) >= from_line){ if(errField !== undefined) return errField
this.state.enabled = true const errRecord = this.__onRecord()
this.__resetField() if(errRecord !== undefined) return errRecord
this.__resetRow()
pos += recordDelimiterLength - 1
continue
}else{
const errField = this.__onField()
if(errField !== undefined) return errField
const errRecord = this.__onRow()
if(errRecord !== undefined) return errRecord
}
if(to !== -1 && this.info.records >= to){ if(to !== -1 && this.info.records >= to){
this.state.stop = true this.state.stop = true
this.push(null) this.push(null)
@@ -11414,7 +12354,7 @@ class Parser extends Transform {
this.state.commenting = true this.state.commenting = true
continue continue
} }
let delimiterLength = this.__isDelimiter(chr, buf, pos) let delimiterLength = this.__isDelimiter(buf, pos, chr)
if(delimiterLength !== 0){ if(delimiterLength !== 0){
const errField = this.__onField() const errField = this.__onField()
if(errField !== undefined) return errField if(errField !== undefined) return errField
@@ -11431,7 +12371,7 @@ class Parser extends Transform {
'record exceed the maximum number of tolerated bytes', 'record exceed the maximum number of tolerated bytes',
`of ${max_record_size}`, `of ${max_record_size}`,
`at line ${this.info.lines}`, `at line ${this.info.lines}`,
], this.__context()) ], this.options, this.__context())
) )
if(err !== undefined) return err if(err !== undefined) return err
} }
@@ -11448,7 +12388,7 @@ class Parser extends Transform {
'Invalid Closing Quote:', 'Invalid Closing Quote:',
'found non trimable byte after quote', 'found non trimable byte after quote',
`at line ${this.info.lines}`, `at line ${this.info.lines}`,
], this.__context()) ], this.options, this.__context())
) )
if(err !== undefined) return err if(err !== undefined) return err
} }
@@ -11460,7 +12400,7 @@ class Parser extends Transform {
new CsvError('CSV_QUOTE_NOT_CLOSED', [ new CsvError('CSV_QUOTE_NOT_CLOSED', [
'Quote Not Closed:', 'Quote Not Closed:',
`the parsing is finished with an opening quote at line ${this.info.lines}`, `the parsing is finished with an opening quote at line ${this.info.lines}`,
], this.__context()) ], this.options, this.__context())
) )
if(err !== undefined) return err if(err !== undefined) return err
}else{ }else{
@@ -11468,7 +12408,7 @@ class Parser extends Transform {
if(this.state.wasQuoting === true || this.state.record.length !== 0 || this.state.field.length !== 0){ if(this.state.wasQuoting === true || this.state.record.length !== 0 || this.state.field.length !== 0){
const errField = this.__onField() const errField = this.__onField()
if(errField !== undefined) return errField if(errField !== undefined) return errField
const errRecord = this.__onRow() const errRecord = this.__onRecord()
if(errRecord !== undefined) return errRecord if(errRecord !== undefined) return errRecord
}else if(this.state.wasRowDelimiter === true){ }else if(this.state.wasRowDelimiter === true){
this.info.empty_lines++ this.info.empty_lines++
@@ -11484,21 +12424,17 @@ class Parser extends Transform {
this.state.wasRowDelimiter = false this.state.wasRowDelimiter = false
} }
} }
// Helper to test if a character is a space or a line delimiter __onRecord(){
__isCharTrimable(chr){ const {columns, columns_duplicates_to_array, encoding, info, from, relax_column_count, relax_column_count_less, relax_column_count_more, raw, skip_lines_with_empty_values} = this.options
return chr === space || chr === tab || chr === cr || chr === nl || chr === np
}
__onRow(){
const {columns, columns_duplicates_to_array, info, from, relax_column_count, relax_column_count_less, relax_column_count_more, raw, skip_lines_with_empty_values} = this.options
const {enabled, record} = this.state const {enabled, record} = this.state
if(enabled === false){ if(enabled === false){
return this.__resetRow() return this.__resetRecord()
} }
// Convert the first line into column names // Convert the first line into column names
const recordLength = record.length const recordLength = record.length
if(columns === true){ if(columns === true){
if(isRecordEmpty(record)){ if(isRecordEmpty(record)){
this.__resetRow() this.__resetRecord()
return return
} }
return this.__firstLineToColumns(record) return this.__firstLineToColumns(record)
@@ -11507,45 +12443,45 @@ class Parser extends Transform {
this.state.expectedRecordLength = recordLength this.state.expectedRecordLength = recordLength
} }
if(recordLength !== this.state.expectedRecordLength){ if(recordLength !== this.state.expectedRecordLength){
const err = columns === false ?
// Todo: rename CSV_INCONSISTENT_RECORD_LENGTH to
// CSV_RECORD_INCONSISTENT_FIELDS_LENGTH
new CsvError('CSV_INCONSISTENT_RECORD_LENGTH', [
'Invalid Record Length:',
`expect ${this.state.expectedRecordLength},`,
`got ${recordLength} on line ${this.info.lines}`,
], this.options, this.__context(), {
record: record,
})
:
// Todo: rename CSV_RECORD_DONT_MATCH_COLUMNS_LENGTH to
// CSV_RECORD_INCONSISTENT_COLUMNS
new CsvError('CSV_RECORD_DONT_MATCH_COLUMNS_LENGTH', [
'Invalid Record Length:',
`columns length is ${columns.length},`, // rename columns
`got ${recordLength} on line ${this.info.lines}`,
], this.options, this.__context(), {
record: record,
})
if(relax_column_count === true || if(relax_column_count === true ||
(relax_column_count_less === true && recordLength < this.state.expectedRecordLength) || (relax_column_count_less === true && recordLength < this.state.expectedRecordLength) ||
(relax_column_count_more === true && recordLength > this.state.expectedRecordLength) ){ (relax_column_count_more === true && recordLength > this.state.expectedRecordLength) ){
this.info.invalid_field_length++ this.info.invalid_field_length++
this.state.error = err
// Error is undefined with skip_lines_with_error
}else{ }else{
if(columns === false){ const finalErr = this.__error(err)
const err = this.__error( if(finalErr) return finalErr
new CsvError('CSV_INCONSISTENT_RECORD_LENGTH', [
'Invalid Record Length:',
`expect ${this.state.expectedRecordLength},`,
`got ${recordLength} on line ${this.info.lines}`,
], this.__context(), {
record: record,
})
)
if(err !== undefined) return err
}else{
const err = this.__error(
// CSV_INVALID_RECORD_LENGTH_DONT_MATCH_COLUMNS
new CsvError('CSV_RECORD_DONT_MATCH_COLUMNS_LENGTH', [
'Invalid Record Length:',
`columns length is ${columns.length},`, // rename columns
`got ${recordLength} on line ${this.info.lines}`,
], this.__context(), {
record: record,
})
)
if(err !== undefined) return err
}
} }
} }
if(skip_lines_with_empty_values === true){ if(skip_lines_with_empty_values === true){
if(isRecordEmpty(record)){ if(isRecordEmpty(record)){
this.__resetRow() this.__resetRecord()
return return
} }
} }
if(this.state.recordHasError === true){ if(this.state.recordHasError === true){
this.__resetRow() this.__resetRecord()
this.state.recordHasError = false this.state.recordHasError = false
return return
} }
@@ -11556,7 +12492,6 @@ class Parser extends Transform {
// Transform record array to an object // Transform record array to an object
for(let i = 0, l = record.length; i < l; i++){ for(let i = 0, l = record.length; i < l; i++){
if(columns[i] === undefined || columns[i].disabled) continue if(columns[i] === undefined || columns[i].disabled) continue
// obj[columns[i].name] = record[i]
// Turn duplicate columns into an array // Turn duplicate columns into an array
if (columns_duplicates_to_array === true && obj[columns[i].name]) { if (columns_duplicates_to_array === true && obj[columns[i].name]) {
if (Array.isArray(obj[columns[i].name])) { if (Array.isArray(obj[columns[i].name])) {
@@ -11573,7 +12508,7 @@ class Parser extends Transform {
if(raw === true || info === true){ if(raw === true || info === true){
const err = this.__push(Object.assign( const err = this.__push(Object.assign(
{record: obj}, {record: obj},
(raw === true ? {raw: this.state.rawBuffer.toString()}: {}), (raw === true ? {raw: this.state.rawBuffer.toString(encoding)}: {}),
(info === true ? {info: this.state.info}: {}) (info === true ? {info: this.state.info}: {})
)) ))
if(err){ if(err){
@@ -11589,7 +12524,7 @@ class Parser extends Transform {
if(raw === true || info === true){ if(raw === true || info === true){
const err = this.__push(Object.assign( const err = this.__push(Object.assign(
{record: [obj[objname], obj]}, {record: [obj[objname], obj]},
raw === true ? {raw: this.state.rawBuffer.toString()}: {}, raw === true ? {raw: this.state.rawBuffer.toString(encoding)}: {},
info === true ? {info: this.state.info}: {} info === true ? {info: this.state.info}: {}
)) ))
if(err){ if(err){
@@ -11606,7 +12541,7 @@ class Parser extends Transform {
if(raw === true || info === true){ if(raw === true || info === true){
const err = this.__push(Object.assign( const err = this.__push(Object.assign(
{record: record}, {record: record},
raw === true ? {raw: this.state.rawBuffer.toString()}: {}, raw === true ? {raw: this.state.rawBuffer.toString(encoding)}: {},
info === true ? {info: this.state.info}: {} info === true ? {info: this.state.info}: {}
)) ))
if(err){ if(err){
@@ -11620,7 +12555,7 @@ class Parser extends Transform {
} }
} }
} }
this.__resetRow() this.__resetRecord()
} }
__firstLineToColumns(record){ __firstLineToColumns(record){
const {firstLineToHeaders} = this.state const {firstLineToHeaders} = this.state
@@ -11632,7 +12567,7 @@ class Parser extends Transform {
'Invalid Column Mapping:', 'Invalid Column Mapping:',
'expect an array from column function,', 'expect an array from column function,',
`got ${JSON.stringify(headers)}` `got ${JSON.stringify(headers)}`
], this.__context(), { ], this.options, this.__context(), {
headers: headers, headers: headers,
}) })
) )
@@ -11640,27 +12575,28 @@ class Parser extends Transform {
const normalizedHeaders = normalizeColumnsArray(headers) const normalizedHeaders = normalizeColumnsArray(headers)
this.state.expectedRecordLength = normalizedHeaders.length this.state.expectedRecordLength = normalizedHeaders.length
this.options.columns = normalizedHeaders this.options.columns = normalizedHeaders
this.__resetRow() this.__resetRecord()
return return
}catch(err){ }catch(err){
return err return err
} }
} }
__resetRow(){ __resetRecord(){
if(this.options.raw === true){ if(this.options.raw === true){
this.state.rawBuffer.reset() this.state.rawBuffer.reset()
} }
this.state.error = undefined
this.state.record = [] this.state.record = []
this.state.record_length = 0 this.state.record_length = 0
} }
__onField(){ __onField(){
const {cast, rtrim, max_record_size} = this.options const {cast, encoding, rtrim, max_record_size} = this.options
const {enabled, wasQuoting} = this.state const {enabled, wasQuoting} = this.state
// Short circuit for the from_line options // Short circuit for the from_line options
if(enabled === false){ /* this.options.columns !== true && */ if(enabled === false){ /* this.options.columns !== true && */
return this.__resetField() return this.__resetField()
} }
let field = this.state.field.toString() let field = this.state.field.toString(encoding)
if(rtrim === true && wasQuoting === false){ if(rtrim === true && wasQuoting === false){
field = field.trimRight() field = field.trimRight()
} }
@@ -11718,6 +12654,10 @@ class Parser extends Transform {
} }
return [undefined, field] return [undefined, field]
} }
// Helper to test if a character is a space or a line delimiter
__isCharTrimable(chr){
return chr === space || chr === tab || chr === cr || chr === nl || chr === np
}
// Keep it in case we implement the `cast_int` option // Keep it in case we implement the `cast_int` option
// __isInt(value){ // __isInt(value){
// // return Number.isInteger(parseInt(value)) // // return Number.isInteger(parseInt(value))
@@ -11727,39 +12667,36 @@ class Parser extends Transform {
__isFloat(value){ __isFloat(value){
return (value - parseFloat( value ) + 1) >= 0 // Borrowed from jquery return (value - parseFloat( value ) + 1) >= 0 // Borrowed from jquery
} }
__compareBytes(sourceBuf, targetBuf, pos, firtByte){ __compareBytes(sourceBuf, targetBuf, targetPos, firstByte){
if(sourceBuf[0] !== firtByte) return 0 if(sourceBuf[0] !== firstByte) return 0
const sourceLength = sourceBuf.length const sourceLength = sourceBuf.length
for(let i = 1; i < sourceLength; i++){ for(let i = 1; i < sourceLength; i++){
if(sourceBuf[i] !== targetBuf[pos+i]) return 0 if(sourceBuf[i] !== targetBuf[targetPos+i]) return 0
} }
return sourceLength return sourceLength
} }
__needMoreData(i, bufLen, end){ __needMoreData(i, bufLen, end){
if(end){ if(end) return false
return false const {quote} = this.options
} const {quoting, needMoreDataSize, recordDelimiterMaxLength} = this.state
const {comment, delimiter} = this.options
const {quoting, recordDelimiterMaxLength} = this.state
const numOfCharLeft = bufLen - i - 1 const numOfCharLeft = bufLen - i - 1
const requiredLength = Math.max( const requiredLength = Math.max(
// Skip if the remaining buffer smaller than comment needMoreDataSize,
comment ? comment.length : 0, // Skip if the remaining buffer smaller than record delimiter
// Skip if the remaining buffer smaller than row delimiter
recordDelimiterMaxLength, recordDelimiterMaxLength,
// Skip if the remaining buffer can be row delimiter following the closing quote // Skip if the remaining buffer can be record delimiter following the closing quote
// 1 is for quote.length // 1 is for quote.length
quoting ? (1 + recordDelimiterMaxLength) : 0, quoting ? (quote.length + recordDelimiterMaxLength) : 0,
// Skip if the remaining buffer can be delimiter
delimiter.length,
// Skip if the remaining buffer can be escape sequence
// 1 is for escape.length
1
) )
return numOfCharLeft < requiredLength return numOfCharLeft < requiredLength
} }
__isDelimiter(chr, buf, pos){ __isDelimiter(buf, pos, chr){
const {delimiter} = this.options const {delimiter, ignore_last_delimiters} = this.options
if(ignore_last_delimiters === true && this.state.record.length === this.options.columns.length - 1){
return 0
}else if(ignore_last_delimiters !== false && typeof ignore_last_delimiters === 'number' && this.state.record.length === ignore_last_delimiters - 1){
return 0
}
loop1: for(let i = 0; i < delimiter.length; i++){ loop1: for(let i = 0; i < delimiter.length; i++){
const del = delimiter[i] const del = delimiter[i]
if(del[0] === chr){ if(del[0] === chr){
@@ -11789,20 +12726,46 @@ class Parser extends Transform {
} }
return 0 return 0
} }
__autoDiscoverRowDelimiter(buf, pos){ __isEscape(buf, pos, chr){
const {escape} = this.options
if(escape === null) return false
const l = escape.length
if(escape[0] === chr){
for(let i = 0; i < l; i++){
if(escape[i] !== buf[pos+i]){
return false
}
}
return true
}
return false
}
__isQuote(buf, pos){
const {quote} = this.options
if(quote === null) return false
const l = quote.length
for(let i = 0; i < l; i++){
if(quote[i] !== buf[pos+i]){
return false
}
}
return true
}
__autoDiscoverRecordDelimiter(buf, pos){
const {encoding} = this.options
const chr = buf[pos] const chr = buf[pos]
if(chr === cr){ if(chr === cr){
if(buf[pos+1] === nl){ if(buf[pos+1] === nl){
this.options.record_delimiter.push(Buffer.from('\r\n')) this.options.record_delimiter.push(Buffer.from('\r\n', encoding))
this.state.recordDelimiterMaxLength = 2 this.state.recordDelimiterMaxLength = 2
return 2 return 2
}else{ }else{
this.options.record_delimiter.push(Buffer.from('\r')) this.options.record_delimiter.push(Buffer.from('\r', encoding))
this.state.recordDelimiterMaxLength = 1 this.state.recordDelimiterMaxLength = 1
return 1 return 1
} }
}else if(chr === nl){ }else if(chr === nl){
this.options.record_delimiter.push(Buffer.from('\n')) this.options.record_delimiter.push(Buffer.from('\n', encoding))
this.state.recordDelimiterMaxLength = 1 this.state.recordDelimiterMaxLength = 1
return 1 return 1
} }
@@ -11830,6 +12793,7 @@ class Parser extends Transform {
) : ) :
this.state.record.length, this.state.record.length,
empty_lines: this.info.empty_lines, empty_lines: this.info.empty_lines,
error: this.state.error,
header: columns === true, header: columns === true,
index: this.state.record.length, index: this.state.record.length,
invalid_field_length: this.info.invalid_field_length, invalid_field_length: this.info.invalid_field_length,
@@ -11855,7 +12819,7 @@ const parse = function(){
throw new CsvError('CSV_INVALID_ARGUMENT', [ throw new CsvError('CSV_INVALID_ARGUMENT', [
'Invalid argument:', 'Invalid argument:',
`got ${JSON.stringify(argument)} at index ${i}` `got ${JSON.stringify(argument)} at index ${i}`
]) ], this.options)
} }
} }
const parser = new Parser(options) const parser = new Parser(options)
@@ -11894,7 +12858,7 @@ const parse = function(){
} }
class CsvError extends Error { class CsvError extends Error {
constructor(code, message, ...contexts) { constructor(code, message, options, ...contexts) {
if(Array.isArray(message)) message = message.join(' ') if(Array.isArray(message)) message = message.join(' ')
super(message) super(message)
if(Error.captureStackTrace !== undefined){ if(Error.captureStackTrace !== undefined){
@@ -11904,7 +12868,7 @@ class CsvError extends Error {
for(const context of contexts){ for(const context of contexts){
for(const key in context){ for(const key in context){
const value = context[key] const value = context[key]
this[key] = Buffer.isBuffer(value) ? value.toString() : value == null ? value : JSON.parse(JSON.stringify(value)) this[key] = Buffer.isBuffer(value) ? value.toString(options.encoding) : value == null ? value : JSON.parse(JSON.stringify(value))
} }
} }
} }
@@ -12032,8 +12996,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next()); step((generator = generator.apply(thisArg, _arguments || [])).next());
}); });
}; };
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.asyncForEach = exports.getInputList = exports.getArgs = exports.getInputs = exports.tmpNameSync = exports.tmpDir = exports.defaultContext = void 0; exports.asyncForEach = exports.getInputList = exports.getArgs = exports.getInputs = exports.tmpNameSync = exports.tmpDir = exports.defaultContext = void 0;
const sync_1 = __importDefault(__webpack_require__(750));
const fs = __importStar(__webpack_require__(747)); const fs = __importStar(__webpack_require__(747));
const os = __importStar(__webpack_require__(87)); const os = __importStar(__webpack_require__(87));
const path = __importStar(__webpack_require__(622)); const path = __importStar(__webpack_require__(622));
@@ -12046,7 +13014,7 @@ let _defaultContext, _tmpDir;
function defaultContext() { function defaultContext() {
var _a, _b; var _a, _b;
if (!_defaultContext) { if (!_defaultContext) {
_defaultContext = `https://github.com/${github.context.repo.owner}/${github.context.repo.repo}.git#${(_b = (_a = github.context) === null || _a === void 0 ? void 0 : _a.ref) === null || _b === void 0 ? void 0 : _b.replace(/^refs\//, '')}`; _defaultContext = `${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${github.context.repo.owner}/${github.context.repo.repo}.git#${(_b = (_a = github.context) === null || _a === void 0 ? void 0 : _a.ref) === null || _b === void 0 ? void 0 : _b.replace(/^refs\//, '')}`;
} }
return _defaultContext; return _defaultContext;
} }
@@ -12065,25 +13033,27 @@ exports.tmpNameSync = tmpNameSync;
function getInputs(defaultContext) { function getInputs(defaultContext) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
return { return {
context: core.getInput('context') || defaultContext,
file: core.getInput('file') || 'Dockerfile',
buildArgs: yield getInputList('build-args', true),
labels: yield getInputList('labels', true),
tags: yield getInputList('tags'),
pull: /true/i.test(core.getInput('pull')),
target: core.getInput('target'),
allow: yield getInputList('allow'), allow: yield getInputList('allow'),
noCache: /true/i.test(core.getInput('no-cache')), buildArgs: yield getInputList('build-args', true),
builder: core.getInput('builder'), builder: core.getInput('builder'),
platforms: yield getInputList('platforms'),
load: /true/i.test(core.getInput('load')),
push: /true/i.test(core.getInput('push')),
outputs: yield getInputList('outputs', true),
cacheFrom: yield getInputList('cache-from', true), cacheFrom: yield getInputList('cache-from', true),
cacheTo: yield getInputList('cache-to', true), cacheTo: yield getInputList('cache-to', true),
context: core.getInput('context') || defaultContext,
file: core.getInput('file'),
labels: yield getInputList('labels', true),
load: /true/i.test(core.getInput('load')),
network: core.getInput('network'),
noCache: /true/i.test(core.getInput('no-cache')),
outputs: yield getInputList('outputs', true),
platforms: yield getInputList('platforms'),
pull: /true/i.test(core.getInput('pull')),
push: /true/i.test(core.getInput('push')),
secrets: yield getInputList('secrets', true), secrets: yield getInputList('secrets', true),
githubToken: core.getInput('github-token'), secretFiles: yield getInputList('secret-files', true),
ssh: yield getInputList('ssh') ssh: yield getInputList('ssh'),
tags: yield getInputList('tags'),
target: core.getInput('target'),
githubToken: core.getInput('github-token')
}; };
}); });
} }
@@ -12133,10 +13103,23 @@ function getBuildArgs(inputs, defaultContext, buildxVersion) {
args.push('--cache-to', cacheTo); args.push('--cache-to', cacheTo);
})); }));
yield exports.asyncForEach(inputs.secrets, (secret) => __awaiter(this, void 0, void 0, function* () { yield exports.asyncForEach(inputs.secrets, (secret) => __awaiter(this, void 0, void 0, function* () {
args.push('--secret', yield buildx.getSecret(secret)); try {
args.push('--secret', yield buildx.getSecretString(secret));
}
catch (err) {
core.warning(err.message);
}
}));
yield exports.asyncForEach(inputs.secretFiles, (secretFile) => __awaiter(this, void 0, void 0, function* () {
try {
args.push('--secret', yield buildx.getSecretFile(secretFile));
}
catch (err) {
core.warning(err.message);
}
})); }));
if (inputs.githubToken && !buildx.hasGitAuthToken(inputs.secrets) && inputs.context == defaultContext) { if (inputs.githubToken && !buildx.hasGitAuthToken(inputs.secrets) && inputs.context == defaultContext) {
args.push('--secret', yield buildx.getSecret(`GIT_AUTH_TOKEN=${inputs.githubToken}`)); args.push('--secret', yield buildx.getSecretString(`GIT_AUTH_TOKEN=${inputs.githubToken}`));
} }
yield exports.asyncForEach(inputs.ssh, (ssh) => __awaiter(this, void 0, void 0, function* () { yield exports.asyncForEach(inputs.ssh, (ssh) => __awaiter(this, void 0, void 0, function* () {
args.push('--ssh', ssh); args.push('--ssh', ssh);
@@ -12162,6 +13145,9 @@ function getCommonArgs(inputs) {
if (inputs.load) { if (inputs.load) {
args.push('--load'); args.push('--load');
} }
if (inputs.network) {
args.push('--network', inputs.network);
}
if (inputs.push) { if (inputs.push) {
args.push('--push'); args.push('--push');
} }
@@ -12170,14 +13156,27 @@ function getCommonArgs(inputs) {
} }
function getInputList(name, ignoreComma) { function getInputList(name, ignoreComma) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
let res = [];
const items = core.getInput(name); const items = core.getInput(name);
if (items == '') { if (items == '') {
return []; return res;
} }
return items for (let output of (yield sync_1.default(items, {
.split(/\r?\n/) columns: false,
.filter(x => x) relaxColumnCount: true,
.reduce((acc, line) => acc.concat(!ignoreComma ? line.split(',').filter(x => x) : line).map(pat => pat.trim()), []); skipLinesWithEmptyValues: true
}))) {
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());
}); });
} }
exports.getInputList = getInputList; exports.getInputList = getInputList;
@@ -12207,37 +13206,51 @@ module.exports = clean
/***/ (function(module, __unusedexports, __webpack_require__) { /***/ (function(module, __unusedexports, __webpack_require__) {
const Range = __webpack_require__(828) const Range = __webpack_require__(828)
const { ANY } = __webpack_require__(532) const Comparator = __webpack_require__(532)
const { ANY } = Comparator
const satisfies = __webpack_require__(55) const satisfies = __webpack_require__(55)
const compare = __webpack_require__(309) const compare = __webpack_require__(309)
// Complex range `r1 || r2 || ...` is a subset of `R1 || R2 || ...` iff: // Complex range `r1 || r2 || ...` is a subset of `R1 || R2 || ...` iff:
// - Every simple range `r1, r2, ...` is a subset of some `R1, R2, ...` // - Every simple range `r1, r2, ...` is a null set, OR
// - Every simple range `r1, r2, ...` which is not a null set is a subset of
// some `R1, R2, ...`
// //
// Simple range `c1 c2 ...` is a subset of simple range `C1 C2 ...` iff: // Simple range `c1 c2 ...` is a subset of simple range `C1 C2 ...` iff:
// - If c is only the ANY comparator // - If c is only the ANY comparator
// - If C is only the ANY comparator, return true // - If C is only the ANY comparator, return true
// - Else return false // - Else if in prerelease mode, return false
// - else replace c with `[>=0.0.0]`
// - If C is only the ANY comparator
// - if in prerelease mode, return true
// - else replace C with `[>=0.0.0]`
// - Let EQ be the set of = comparators in c // - Let EQ be the set of = comparators in c
// - If EQ is more than one, return true (null set) // - If EQ is more than one, return true (null set)
// - Let GT be the highest > or >= comparator in c // - Let GT be the highest > or >= comparator in c
// - Let LT be the lowest < or <= comparator in c // - Let LT be the lowest < or <= comparator in c
// - If GT and LT, and GT.semver > LT.semver, return true (null set) // - If GT and LT, and GT.semver > LT.semver, return true (null set)
// - If any C is a = range, and GT or LT are set, return false
// - If EQ // - If EQ
// - If GT, and EQ does not satisfy GT, return true (null set) // - If GT, and EQ does not satisfy GT, return true (null set)
// - If LT, and EQ does not satisfy LT, return true (null set) // - If LT, and EQ does not satisfy LT, return true (null set)
// - If EQ satisfies every C, return true // - If EQ satisfies every C, return true
// - Else return false // - Else return false
// - If GT // - If GT
// - If GT is lower than any > or >= comp in C, return false // - If GT.semver is lower than any > or >= comp in C, return false
// - If GT is >=, and GT.semver does not satisfy every C, return false // - If GT is >=, and GT.semver does not satisfy every C, return false
// - If GT.semver has a prerelease, and not in prerelease mode
// - If no C has a prerelease and the GT.semver tuple, return false
// - If LT // - If LT
// - If LT.semver is greater than that of any > comp in C, return false // - If LT.semver is greater than any < or <= comp in C, return false
// - If LT is <=, and LT.semver does not satisfy every C, return false // - If LT is <=, and LT.semver does not satisfy every C, return false
// - If any C is a = range, and GT or LT are set, return false // - If GT.semver has a prerelease, and not in prerelease mode
// - If no C has a prerelease and the LT.semver tuple, return false
// - Else return true // - Else return true
const subset = (sub, dom, options) => { const subset = (sub, dom, options = {}) => {
if (sub === dom)
return true
sub = new Range(sub, options) sub = new Range(sub, options)
dom = new Range(dom, options) dom = new Range(dom, options)
let sawNonNull = false let sawNonNull = false
@@ -12260,8 +13273,24 @@ const subset = (sub, dom, options) => {
} }
const simpleSubset = (sub, dom, options) => { const simpleSubset = (sub, dom, options) => {
if (sub.length === 1 && sub[0].semver === ANY) if (sub === dom)
return dom.length === 1 && dom[0].semver === ANY return true
if (sub.length === 1 && sub[0].semver === ANY) {
if (dom.length === 1 && dom[0].semver === ANY)
return true
else if (options.includePrerelease)
sub = [ new Comparator('>=0.0.0-0') ]
else
sub = [ new Comparator('>=0.0.0') ]
}
if (dom.length === 1 && dom[0].semver === ANY) {
if (options.includePrerelease)
return true
else
dom = [ new Comparator('>=0.0.0') ]
}
const eqSet = new Set() const eqSet = new Set()
let gt, lt let gt, lt
@@ -12298,26 +13327,57 @@ const simpleSubset = (sub, dom, options) => {
if (!satisfies(eq, String(c), options)) if (!satisfies(eq, String(c), options))
return false return false
} }
return true return true
} }
let higher, lower let higher, lower
let hasDomLT, hasDomGT let hasDomLT, hasDomGT
// if the subset has a prerelease, we need a comparator in the superset
// with the same tuple and a prerelease, or it's not a subset
let needDomLTPre = lt &&
!options.includePrerelease &&
lt.semver.prerelease.length ? lt.semver : false
let needDomGTPre = gt &&
!options.includePrerelease &&
gt.semver.prerelease.length ? gt.semver : false
// exception: <1.2.3-0 is the same as <1.2.3
if (needDomLTPre && needDomLTPre.prerelease.length === 1 &&
lt.operator === '<' && needDomLTPre.prerelease[0] === 0) {
needDomLTPre = false
}
for (const c of dom) { for (const c of dom) {
hasDomGT = hasDomGT || c.operator === '>' || c.operator === '>=' hasDomGT = hasDomGT || c.operator === '>' || c.operator === '>='
hasDomLT = hasDomLT || c.operator === '<' || c.operator === '<=' hasDomLT = hasDomLT || c.operator === '<' || c.operator === '<='
if (gt) { if (gt) {
if (needDomGTPre) {
if (c.semver.prerelease && c.semver.prerelease.length &&
c.semver.major === needDomGTPre.major &&
c.semver.minor === needDomGTPre.minor &&
c.semver.patch === needDomGTPre.patch) {
needDomGTPre = false
}
}
if (c.operator === '>' || c.operator === '>=') { if (c.operator === '>' || c.operator === '>=') {
higher = higherGT(gt, c, options) higher = higherGT(gt, c, options)
if (higher === c) if (higher === c && higher !== gt)
return false return false
} else if (gt.operator === '>=' && !satisfies(gt.semver, String(c), options)) } else if (gt.operator === '>=' && !satisfies(gt.semver, String(c), options))
return false return false
} }
if (lt) { if (lt) {
if (needDomLTPre) {
if (c.semver.prerelease && c.semver.prerelease.length &&
c.semver.major === needDomLTPre.major &&
c.semver.minor === needDomLTPre.minor &&
c.semver.patch === needDomLTPre.patch) {
needDomLTPre = false
}
}
if (c.operator === '<' || c.operator === '<=') { if (c.operator === '<' || c.operator === '<=') {
lower = lowerLT(lt, c, options) lower = lowerLT(lt, c, options)
if (lower === c) if (lower === c && lower !== lt)
return false return false
} else if (lt.operator === '<=' && !satisfies(lt.semver, String(c), options)) } else if (lt.operator === '<=' && !satisfies(lt.semver, String(c), options))
return false return false
@@ -12335,6 +13395,12 @@ const simpleSubset = (sub, dom, options) => {
if (lt && hasDomGT && !gt && gtltComp !== 0) if (lt && hasDomGT && !gt && gtltComp !== 0)
return false return false
// we needed a prerelease range in a specific tuple, but didn't get one
// then this isn't a subset. eg >=1.2.3-pre is not a subset of >=1.0.0,
// because it includes prereleases in the 1.2.3 tuple
if (needDomGTPre || needDomLTPre)
return false
return true return true
} }
@@ -12536,13 +13602,9 @@ const {MAX_LENGTH} = __webpack_require__(293)
const { re, t } = __webpack_require__(523) const { re, t } = __webpack_require__(523)
const SemVer = __webpack_require__(88) const SemVer = __webpack_require__(88)
const parseOptions = __webpack_require__(785)
const parse = (version, options) => { const parse = (version, options) => {
if (!options || typeof options !== 'object') { options = parseOptions(options)
options = {
loose: !!options,
includePrerelease: false
}
}
if (version instanceof SemVer) { if (version instanceof SemVer) {
return version return version
@@ -13192,13 +14254,28 @@ class ResizeableBuffer{
this.buf = Buffer.alloc(size) this.buf = Buffer.alloc(size)
} }
prepend(val){ prepend(val){
const length = this.length++ if(Buffer.isBuffer(val)){
if(length === this.size){ const length = this.length + val.length
this.resize() if(length >= this.size){
this.resize()
if(length >= this.size){
throw Error('INVALID_BUFFER_STATE')
}
}
const buf = this.buf
this.buf = Buffer.alloc(this.size)
val.copy(this.buf, 0)
buf.copy(this.buf, val.length)
this.length += val.length
}else{
const length = this.length++
if(length === this.size){
this.resize()
}
const buf = this.clone()
this.buf[0] = val
buf.copy(this.buf,1, 0, length)
} }
const buf = this.clone()
this.buf[0] = val
buf.copy(this.buf,1, 0, length)
} }
append(val){ append(val){
const length = this.length++ const length = this.length++
@@ -13217,11 +14294,15 @@ class ResizeableBuffer{
this.buf.copy(buf,0, 0, length) this.buf.copy(buf,0, 0, length)
this.buf = buf this.buf = buf
} }
toString(){ toString(encoding){
return this.buf.slice(0, this.length).toString() if(encoding){
return this.buf.slice(0, this.length).toString(encoding)
}else{
return Uint8Array.prototype.slice.call(this.buf.slice(0, this.length))
}
} }
toJSON(){ toJSON(){
return this.toString() return this.toString('utf8')
} }
reset(){ reset(){
this.length = 0 this.length = 0

View File

@@ -1,42 +1,67 @@
variable "NODE_VERSION" {
default = "12"
}
target "node-version" {
args = {
NODE_VERSION = NODE_VERSION
}
}
group "default" { group "default" {
targets = ["build"] targets = ["build"]
} }
group "pre-checkin" { group "pre-checkin" {
targets = ["update-yarn", "format", "build"] targets = ["vendor-update", "format", "build"]
} }
group "validate" { group "validate" {
targets = ["validate-format", "validate-build", "validate-yarn"] targets = ["format-validate", "build-validate", "vendor-validate"]
}
target "update-yarn" {
target = "update-yarn"
output = ["."]
} }
target "build" { target "build" {
target = "dist" inherits = ["node-version"]
dockerfile = "./hack/build.Dockerfile"
target = "build-update"
output = ["."] output = ["."]
} }
target "test" { target "build-validate" {
target = "test" inherits = ["node-version"]
dockerfile = "./hack/build.Dockerfile"
target = "build-validate"
} }
target "format" { target "format" {
target = "format" inherits = ["node-version"]
dockerfile = "./hack/build.Dockerfile"
target = "format-update"
output = ["."] output = ["."]
} }
target "validate-format" { target "format-validate" {
target = "validate-format" inherits = ["node-version"]
dockerfile = "./hack/build.Dockerfile"
target = "format-validate"
} }
target "validate-build" { target "vendor-update" {
target = "validate-build" inherits = ["node-version"]
dockerfile = "./hack/vendor.Dockerfile"
target = "update"
output = ["."]
} }
target "validate-yarn" { target "vendor-validate" {
target = "validate-yarn" inherits = ["node-version"]
dockerfile = "./hack/vendor.Dockerfile"
target = "validate"
}
target "test" {
inherits = ["node-version"]
dockerfile = "./hack/test.Dockerfile"
target = "test-coverage"
output = ["./coverage"]
} }

107
docs/advanced/cache.md Normal file
View File

@@ -0,0 +1,107 @@
# Cache
* [Registry cache](#registry-cache)
* [GitHub cache](#github-cache)
> More info about buildx cache: https://github.com/docker/buildx/blob/master/docs/reference/buildx_build.md#cache-from
## Registry cache
You can import/export cache from a cache manifest or (special) image configuration on the registry.
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: user/app:latest
cache-from: type=registry,ref=user/app:latest
cache-to: type=inline
```
## GitHub cache
> :warning: At the moment caches are copied over the existing cache so it [keeps growing](https://github.com/docker/build-push-action/issues/252).
> The `Move cache` step is used as a temporary fix (see https://github.com/moby/buildkit/issues/1896).
> :rocket: There is a new cache backend using GitHub cache being developed that will lighten your workflow.
> More info: https://github.com/docker/buildx/pull/535
You can leverage [GitHub cache](https://docs.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows)
using [actions/cache](https://github.com/actions/cache) with this action:
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: user/app:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
-
# Temp fix
# https://github.com/docker/build-push-action/issues/252
# https://github.com/moby/buildkit/issues/1896
name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
```

View File

@@ -0,0 +1,48 @@
# Update DockerHub repo description
You can update the [DockerHub repository description](https://docs.docker.com/docker-hub/repos/) using
a third party action called [DockerHub Description](https://github.com/peter-evans/dockerhub-description)
with this action:
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: user/app:latest
-
name: Update repo description
uses: peter-evans/dockerhub-description@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
repository: user/app
```

View File

@@ -0,0 +1,35 @@
# Export image to Docker
You may want your build result to be available in the Docker client through `docker images` to be able to use it
in another step of your workflow:
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Build
uses: docker/build-push-action@v2
with:
context: .
load: true
tags: myimage:latest
-
name: Inspect
run: |
docker image inspect myimage:latest
```

View File

@@ -0,0 +1,44 @@
# Isolated builders
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
uses: docker/setup-buildx-action@v1
id: builder1
-
uses: docker/setup-buildx-action@v1
id: builder2
-
name: Builder 1 name
run: echo ${{ steps.builder1.outputs.name }}
-
name: Builder 2 name
run: echo ${{ steps.builder2.outputs.name }}
-
name: Build against builder1
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder1.outputs.name }}
context: .
target: mytarget1
-
name: Build against builder2
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder2.outputs.name }}
context: .
target: mytarget2
```

View File

@@ -0,0 +1,44 @@
# Local registry
For testing purposes you may need to create a [local registry](https://hub.docker.com/_/registry) to push images into:
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
services:
registry:
image: registry:2
ports:
- 5000:5000
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
driver-opts: network=host
-
name: Build and push to local registry
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: localhost:5000/name/app:latest
-
name: Inspect
run: |
docker buildx imagetools inspect localhost:5000/name/app:latest
```

View File

@@ -0,0 +1,44 @@
# Multi-platform image
You can build multi-platform images using the [`platforms` input](../../README.md#inputs) as described below.
> :bulb: List of available platforms will be displayed and available through our [setup-buildx](https://github.com/docker/setup-buildx-action#about) action.
> :bulb: If you want support for more platforms, you can use QEMU with our [setup-qemu](https://github.com/docker/setup-qemu-action) action.
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: user/app:latest
```

View File

@@ -0,0 +1,57 @@
# Push to multi-registries
* [Docker Hub and GHCR](#docker-hub-and-ghcr)
## Docker Hub and GHCR
The following workflow will connect you to [DockerHub](https://github.com/docker/login-action#dockerhub)
and [GitHub Container Registry](https://github.com/docker/login-action#github-container-registry) and push the
image to these registries.
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
user/app:latest
user/app:1.0.0
ghcr.io/user/app:latest
ghcr.io/user/app:1.0.0
```

84
docs/advanced/secrets.md Normal file
View File

@@ -0,0 +1,84 @@
# Secrets
In the following example we will expose and use the [GITHUB_TOKEN secret](https://docs.github.com/en/actions/reference/authentication-in-a-workflow#about-the-github_token-secret)
as provided by GitHub in your workflow.
First let's create our `Dockerfile` to use our secret:
```Dockerfile
#syntax=docker/dockerfile:1.2
FROM alpine
RUN --mount=type=secret,id=github_token \
cat /run/secrets/github_token
```
As you can see we have named our secret `github_token`. Here is the workflow you can use to expose this secret using
the [`secrets` input](../../README.md#inputs):
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Build
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm64
tags: user/app:latest
secrets: |
"github_token=${{ secrets.GITHUB_TOKEN }}"
```
> :bulb: You can also expose a secret file to the build with [`secret-files`](../../README.md#inputs) input:
> ```yaml
> secret-files: |
> "MY_SECRET=./secret.txt"
> ```
If you're using [GitHub secrets](https://docs.github.com/en/actions/reference/encrypted-secrets) and need to handle
multi-line value, you will need to place the key-value pair between quotes:
```yaml
secrets: |
"MYSECRET=${{ secrets.GPG_KEY }}"
GIT_AUTH_TOKEN=abcdefghi,jklmno=0123456789
"MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc"
FOO=bar
"EMPTYLINE=aaaa
bbbb
ccc"
"JSON_SECRET={""key1"":""value1"",""key2"":""value2""}"
```
| Key | Value |
|--------------------|--------------------------------------------------|
| `MYSECRET` | `***********************` |
| `GIT_AUTH_TOKEN` | `abcdefghi,jklmno=0123456789` |
| `MYSECRET` | `aaaaaaaa\nbbbbbbb\nccccccccc` |
| `FOO` | `bar` |
| `EMPTYLINE` | `aaaa\n\nbbbb\nccc` |
| `JSON_SECRET` | `{"key1":"value1","key2":"value2"}` |
> :bulb: All quote signs need to be doubled for escaping.

View File

@@ -0,0 +1,77 @@
# Handle tags and labels
If you come from [`v1`](https://github.com/docker/build-push-action/tree/releases/v1#readme) and want an
"automatic" tag management and [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/master/annotations.md)
for labels, you can do it in a dedicated step. The following workflow will use the [Docker meta action](https://github.com/crazy-max/ghaction-docker-meta)
to handle tags and labels based on GitHub actions events and Git metadata.
```yaml
name: ci
on:
schedule:
- cron: '0 10 * * *' # everyday at 10am
push:
branches:
- '**'
tags:
- 'v*.*.*'
pull_request:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
id: meta
uses: crazy-max/ghaction-docker-meta@v2
with:
# list of Docker images to use as base name for tags
images: |
name/app
ghcr.io/username/app
# generate Docker tags based on the following events/attributes
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
```

42
hack/build.Dockerfile Normal file
View File

@@ -0,0 +1,42 @@
# syntax=docker/dockerfile:1.2
ARG NODE_VERSION
FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache cpio findutils git
WORKDIR /src
FROM base AS deps
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn install
FROM deps AS build
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn run build && mkdir /out && cp -Rf dist /out/
FROM scratch AS build-update
COPY --from=build /out /
FROM build AS build-validate
RUN --mount=type=bind,target=.,rw \
git add -A && cp -rf /out/* .; \
if [ -n "$(git status --porcelain -- dist)" ]; then \
echo >&2 'ERROR: Build result differs. Please build first with "docker buildx bake build"'; \
git status --porcelain -- dist; \
exit 1; \
fi
FROM deps AS format
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn run format \
&& mkdir /out && find . -name '*.ts' -not -path './node_modules/*' | cpio -pdm /out
FROM scratch AS format-update
COPY --from=format /out /
FROM deps AS format-validate
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn run format-check \

23
hack/test.Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
# syntax=docker/dockerfile:1.2
ARG NODE_VERSION
FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache git
WORKDIR /src
FROM base AS deps
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn install
FROM deps AS test
ENV RUNNER_TEMP=/tmp/github_runner
ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
--mount=type=bind,from=crazymax/docker,source=/usr/libexec/docker/cli-plugins/docker-buildx,target=/usr/libexec/docker/cli-plugins/docker-buildx \
--mount=type=bind,from=crazymax/docker,source=/usr/local/bin/docker,target=/usr/bin/docker \
yarn run test --coverageDirectory=/tmp/coverage
FROM scratch AS test-coverage
COPY --from=test /tmp/coverage /

23
hack/vendor.Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
# syntax=docker/dockerfile:1.2
ARG NODE_VERSION
FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache git
WORKDIR /src
FROM base AS vendored
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn install && mkdir /out && cp yarn.lock /out
FROM scratch AS update
COPY --from=vendored /out /
FROM vendored AS validate
RUN --mount=type=bind,target=.,rw \
git add -A && cp -rf /out/* .; \
if [ -n "$(git status --porcelain -- yarn.lock)" ]; then \
echo >&2 'ERROR: Vendor result differs. Please vendor your package with "docker buildx bake vendor-update"'; \
git status --porcelain -- yarn.lock; \
exit 1; \
fi

View File

@@ -31,8 +31,8 @@
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
"@actions/exec": "^1.0.4", "@actions/exec": "^1.0.4",
"@actions/github": "^4.0.0", "@actions/github": "^4.0.0",
"csv-parse": "^4.12.0", "csv-parse": "^4.15.3",
"semver": "^7.3.2", "semver": "^7.3.5",
"tmp": "^0.2.1" "tmp": "^0.2.1"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,7 +1,8 @@
import csvparse from 'csv-parse/lib/sync';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import csvparse from 'csv-parse/lib/sync';
import * as semver from 'semver'; import * as semver from 'semver';
import * as context from './context'; import * as context from './context';
import * as exec from './exec'; import * as exec from './exec';
@@ -17,14 +18,34 @@ export async function getImageID(): Promise<string | undefined> {
return fs.readFileSync(iidFile, {encoding: 'utf-8'}); return fs.readFileSync(iidFile, {encoding: 'utf-8'});
} }
export async function getSecret(kvp: string): Promise<string> { export async function getSecretString(kvp: string): Promise<string> {
return getSecret(kvp, false);
}
export async function getSecretFile(kvp: string): Promise<string> {
return getSecret(kvp, true);
}
export async function getSecret(kvp: string, file: boolean): Promise<string> {
const delimiterIndex = kvp.indexOf('='); const delimiterIndex = kvp.indexOf('=');
const key = kvp.substring(0, delimiterIndex); const key = kvp.substring(0, delimiterIndex);
const value = kvp.substring(delimiterIndex + 1); let value = kvp.substring(delimiterIndex + 1);
if (key.length == 0 || value.length == 0) {
throw new Error(`${kvp} is not a valid secret`);
}
if (file) {
if (!fs.existsSync(value)) {
throw new Error(`secret file ${value} not found`);
}
value = fs.readFileSync(value, {encoding: 'utf-8'});
}
const secretFile = context.tmpNameSync({ const secretFile = context.tmpNameSync({
tmpdir: context.tmpDir() tmpdir: context.tmpDir()
}); });
await fs.writeFileSync(secretFile, value); fs.writeFileSync(secretFile, value);
return `id=${key},src=${secretFile}`; return `id=${key},src=${secretFile}`;
} }
@@ -33,7 +54,7 @@ export function isLocalOrTarExporter(outputs: string[]): Boolean {
delimiter: ',', delimiter: ',',
trim: true, trim: true,
columns: false, columns: false,
relax_column_count: true relaxColumnCount: true
})) { })) {
// Local if no type is defined // Local if no type is defined
// https://github.com/docker/buildx/blob/d2bf42f8b4784d83fde17acb3ed84703ddc2156b/build/output.go#L29-L43 // https://github.com/docker/buildx/blob/d2bf42f8b4784d83fde17acb3ed84703ddc2156b/build/output.go#L29-L43

View File

@@ -1,3 +1,4 @@
import csvparse from 'csv-parse/lib/sync';
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
@@ -12,30 +13,32 @@ import * as buildx from './buildx';
let _defaultContext, _tmpDir: string; let _defaultContext, _tmpDir: string;
export interface Inputs { export interface Inputs {
context: string;
file: string;
buildArgs: string[];
labels: string[];
tags: string[];
pull: boolean;
target: string;
allow: string[]; allow: string[];
noCache: boolean; buildArgs: string[];
builder: string; builder: string;
platforms: string[];
load: boolean;
push: boolean;
outputs: string[];
cacheFrom: string[]; cacheFrom: string[];
cacheTo: string[]; cacheTo: string[];
context: string;
file: string;
labels: string[];
load: boolean;
network: string;
noCache: boolean;
outputs: string[];
platforms: string[];
pull: boolean;
push: boolean;
secrets: string[]; secrets: string[];
githubToken: string; secretFiles: string[];
ssh: string[]; ssh: string[];
tags: string[];
target: string;
githubToken: string;
} }
export function defaultContext(): string { export function defaultContext(): string {
if (!_defaultContext) { if (!_defaultContext) {
_defaultContext = `https://github.com/${github.context.repo.owner}/${ _defaultContext = `${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${github.context.repo.owner}/${
github.context.repo.repo github.context.repo.repo
}.git#${github.context?.ref?.replace(/^refs\//, '')}`; }.git#${github.context?.ref?.replace(/^refs\//, '')}`;
} }
@@ -55,25 +58,27 @@ export function tmpNameSync(options?: tmp.TmpNameOptions): string {
export async function getInputs(defaultContext: string): Promise<Inputs> { export async function getInputs(defaultContext: string): Promise<Inputs> {
return { return {
context: core.getInput('context') || defaultContext,
file: core.getInput('file') || 'Dockerfile',
buildArgs: await getInputList('build-args', true),
labels: await getInputList('labels', true),
tags: await getInputList('tags'),
pull: /true/i.test(core.getInput('pull')),
target: core.getInput('target'),
allow: await getInputList('allow'), allow: await getInputList('allow'),
noCache: /true/i.test(core.getInput('no-cache')), buildArgs: await getInputList('build-args', true),
builder: core.getInput('builder'), builder: core.getInput('builder'),
platforms: await getInputList('platforms'),
load: /true/i.test(core.getInput('load')),
push: /true/i.test(core.getInput('push')),
outputs: await getInputList('outputs', true),
cacheFrom: await getInputList('cache-from', true), cacheFrom: await getInputList('cache-from', true),
cacheTo: await getInputList('cache-to', true), cacheTo: await getInputList('cache-to', true),
context: core.getInput('context') || defaultContext,
file: core.getInput('file'),
labels: await getInputList('labels', true),
load: /true/i.test(core.getInput('load')),
network: core.getInput('network'),
noCache: /true/i.test(core.getInput('no-cache')),
outputs: await getInputList('outputs', true),
platforms: await getInputList('platforms'),
pull: /true/i.test(core.getInput('pull')),
push: /true/i.test(core.getInput('push')),
secrets: await getInputList('secrets', true), secrets: await getInputList('secrets', true),
githubToken: core.getInput('github-token'), secretFiles: await getInputList('secret-files', true),
ssh: await getInputList('ssh') ssh: await getInputList('ssh'),
tags: await getInputList('tags'),
target: core.getInput('target'),
githubToken: core.getInput('github-token')
}; };
} }
@@ -121,10 +126,21 @@ async function getBuildArgs(inputs: Inputs, defaultContext: string, buildxVersio
args.push('--cache-to', cacheTo); args.push('--cache-to', cacheTo);
}); });
await asyncForEach(inputs.secrets, async secret => { await asyncForEach(inputs.secrets, async secret => {
args.push('--secret', await buildx.getSecret(secret)); try {
args.push('--secret', await buildx.getSecretString(secret));
} catch (err) {
core.warning(err.message);
}
});
await asyncForEach(inputs.secretFiles, async secretFile => {
try {
args.push('--secret', await buildx.getSecretFile(secretFile));
} catch (err) {
core.warning(err.message);
}
}); });
if (inputs.githubToken && !buildx.hasGitAuthToken(inputs.secrets) && inputs.context == defaultContext) { if (inputs.githubToken && !buildx.hasGitAuthToken(inputs.secrets) && inputs.context == defaultContext) {
args.push('--secret', await buildx.getSecret(`GIT_AUTH_TOKEN=${inputs.githubToken}`)); args.push('--secret', await buildx.getSecretString(`GIT_AUTH_TOKEN=${inputs.githubToken}`));
} }
await asyncForEach(inputs.ssh, async ssh => { await asyncForEach(inputs.ssh, async ssh => {
args.push('--ssh', ssh); args.push('--ssh', ssh);
@@ -149,6 +165,9 @@ async function getCommonArgs(inputs: Inputs): Promise<Array<string>> {
if (inputs.load) { if (inputs.load) {
args.push('--load'); args.push('--load');
} }
if (inputs.network) {
args.push('--network', inputs.network);
}
if (inputs.push) { if (inputs.push) {
args.push('--push'); args.push('--push');
} }
@@ -156,17 +175,29 @@ async function getCommonArgs(inputs: Inputs): Promise<Array<string>> {
} }
export async function getInputList(name: string, ignoreComma?: boolean): Promise<string[]> { export async function getInputList(name: string, ignoreComma?: boolean): Promise<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 (await csvparse(items, {
.filter(x => x) columns: false,
.reduce<string[]>( relaxColumnCount: true,
(acc, line) => acc.concat(!ignoreComma ? line.split(',').filter(x => x) : line).map(pat => pat.trim()), 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

@@ -1236,10 +1236,10 @@ cssstyle@^2.2.0:
dependencies: dependencies:
cssom "~0.3.6" cssom "~0.3.6"
csv-parse@*, csv-parse@^4.12.0: csv-parse@*, csv-parse@^4.15.3:
version "4.12.0" version "4.15.3"
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.12.0.tgz#fd42d6291bbaadd51d3009f6cadbb3e53b4ce026" resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.15.3.tgz#8a62759617a920c328cb31c351b05053b8f92b10"
integrity sha512-wPQl3H79vWLPI8cgKFcQXl0NBgYYEqVnT1i6/So7OjMpsI540oD7p93r3w6fDSyPvwkTepG05F69/7AViX2lXg== integrity sha512-jlTqDvLdHnYMSr08ynNfk4IAUSJgJjTKy2U5CQBSu4cN9vQOJonLVZP4Qo4gKKrIgIQ5dr07UwOJdi+lRqT12w==
dashdash@^1.12.0: dashdash@^1.12.0:
version "1.14.1" version "1.14.1"
@@ -2614,6 +2614,13 @@ lru-cache@^4.1.5:
pseudomap "^1.0.2" pseudomap "^1.0.2"
yallist "^2.1.2" yallist "^2.1.2"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
make-dir@^3.0.0: make-dir@^3.0.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
@@ -3236,10 +3243,12 @@ saxes@^5.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
semver@7.x, semver@^7.3.2: semver@7.x, semver@^7.3.2, semver@^7.3.5:
version "7.3.2" version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies:
lru-cache "^6.0.0"
semver@^6.0.0, semver@^6.3.0: semver@^6.0.0, semver@^6.3.0:
version "6.3.0" version "6.3.0"
@@ -3886,15 +3895,20 @@ xmlchars@^2.2.0:
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
y18n@^4.0.0: y18n@^4.0.0:
version "4.0.0" version "4.0.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==
yallist@^2.1.2: yallist@^2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargs-parser@18.x, yargs-parser@^18.1.2: yargs-parser@18.x, yargs-parser@^18.1.2:
version "18.1.3" version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"