Repository: actions/toolkit
Pull request #1594 (2023-12-06 14:07:02)

@actions/artifact doc updates
This starts the initial documentation for `@action/artifact` v2.

I'll be following up in another PR with autogenerated markdown docs for the functions, interfaces, etc.

Also, this change alters the export behavior of the default client.

Instead of:

```js
import * as artifact from '@actions/artifact'
const artifactClient = artifact.create()

artifact.getArtifact( /* ... */ )
```

Now there is a default export with the client already initialized:

```js
import artifact from '@actions/artifact'

artifact.getArtifact( /* ... */ )
```


Patch:
diff --git a/packages/artifact/CONTRIBUTIONS.md b/packages/artifact/CONTRIBUTIONS.md
index c614728..23ede98 100644
--- a/packages/artifact/CONTRIBUTIONS.md
+++ b/packages/artifact/CONTRIBUTIONS.md
@@ -1,30 +1,43 @@
 # Contributions
 
-This package is used internally by the v2+ versions of [upload-artifact](https://github.com/actions/upload-artifact) and [download-artifact](https://github.com/actions/download-artifact). This package can also be used by other actions to interact with artifacts. Any changes or updates to this package will propagate updates to these actions so it is important that major changes or updates get properly tested. 
+This package is used internally by the v4 versions of [upload-artifact](https://github.com/actions/upload-artifact) and [download-artifact](https://github.com/actions/download-artifact). This package can also be used by other actions to interact with artifacts. Any changes or updates to this package will propagate updates to these actions so it is important that major changes or updates get properly tested.
 
 Any issues or feature requests that are related to the artifact actions should be filled in the appropriate repo.
 
 A limited range of unit tests run as part of each PR when making changes to the artifact packages. For small contributions and fixes, they should be sufficient.
 
-If making large changes, there are a few scenarios that should be tested.
+If making large changes, there are a few scenarios that should be tested:
 
-- Uploading very large artifacts (large artifacts get compressed using gzip so compression/decompression must be tested)
-- Uploading artifacts with lots of small files (each file is uploaded with its own HTTP call, timeouts and non-success HTTP responses can be expected so they must be properly handled)
+- Uploading very large artifacts
+- Uploading artifacts with lots of small files
 - Uploading artifacts using a self-hosted runner (uploads and downloads behave differently due to extra latency)
 - Downloading a single artifact (large and small, if lots of small files are part of an artifact, timeouts and non-success HTTP responses can be expected)
 - Downloading all artifacts at once
 
 Large architectural changes can impact upload/download performance so it is important to separately run extra tests. We request that any large contributions/changes have extra detailed testing so we can verify performance and possible regressions.
 
-It is not possible to run end-to-end tests for artifacts as part of a PR in this repo because certain env variables such as `ACTIONS_RUNTIME_URL` are only available from the context of an action as opposed to a shell script. These env variables are needed in order to make the necessary API calls.
+Tests will run for every push/pull_request [via Actions](https://github.com/actions/toolkit/blob/main/.github/workflows/artifact-tests.yml).
 
 # Testing
 
-Any easy way to test changes is to fork the artifact actions and to use `npm link` to test your changes.
+## Package tests
 
-1. Fork the [upload-artifact](https://github.com/actions/upload-artifact) and [download-artifact](https://github.com/actions/download-artifact) repos
-2. Clone the forks locally
-3. With your local changes to the toolkit repo, type `npm link` after ensuring there are no errors when running `tsc`
-4. In the locally cloned fork, type `npm link @actions/artifact`
-4. Create a new release for your local fork using `tsc` and `npm run release` (this will create a new `dist/index.js` file using `@vercel/ncc`)
-5. Commit and push your local changes, you will then be able to test your changes with your forked action
+To run unit tests for the `@actions/artifact` package:
+
+1. Clone `actions/toolkit` locally
+2. Install dependencies: `npm bootstrap`
+3. Change working directory to `packages/artifact`
+4. Run jest tests: `npm run test`
+
+## Within upload-artifact or download-artifact actions
+
+Any easy way to test changes for the official upload/download actions is to fork them, compile changes and run them.
+
+1. For your local `actions/toolkit` changes:
+   1. Change directory to `packages/artifact`
+   2. Compile the changes: `npm run tsc`
+   3. Symlink your package change: `npm link`
+2. Fork and clone either [upload-artifact](https://github.com/actions/upload-artifact) and [download-artifact](https://github.com/actions/download-artifact)
+   1. In the locally cloned fork, link to your local toolkit changes: `npm link @actions/artifact`
+   2. Then, compile your changes with: `npm run release`. The local `dist/index.js` should be updated with your changes.
+   3. Commit and push to your fork, you can then test with a `uses:` in your workflow pointed at your fork.
diff --git a/packages/artifact/README.md b/packages/artifact/README.md
index fee9a6a..d03dfc2 100644
--- a/packages/artifact/README.md
+++ b/packages/artifact/README.md
@@ -1,13 +1,150 @@
 # `@actions/artifact`
 
-## Usage
+Interact programmatically with [Actions Artifacts](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts).
 
-You can use this package to interact with the Actions artifacts.
+This is the core library that powers the [`@actions/upload-artifact`](https://github.com/actions/upload-artifact) and [`@actions/download-artifact`](https://github.com/actions/download-artifact) actions.
 
-This most recently published version of this package (`1.1.1`) can be found [here](https://github.com/actions/toolkit/tree/@actions/artifact@1.1.1/packages/artifact)
 
-## 🚧 Under construction 🚧
+- [`@actions/artifact`](#actionsartifact)
+  - [v2 - What's New](#v2---whats-new)
+    - [Improvements](#improvements)
+    - [Breaking changes](#breaking-changes)
+  - [Quick Start](#quick-start)
+  - [Examples](#examples)
+    - [Upload and Download](#upload-and-download)
+    - [Downloading from other workflow runs or repos](#downloading-from-other-workflow-runs-or-repos)
+    - [Speeding up large uploads](#speeding-up-large-uploads)
+  - [Additional Resources](#additional-resources)
 
-This package is currently undergoing a major overhaul in preparation for `v4` versions of `upload-artifact` and `download-artifact` (these Actions will use a new `2.0.0` version of `@actions/artifact` that will soon be released). The upcoming version of `@actions/artifact` will take advantage of a major re-architecture with entirely new APIs.
+## v2 - What's New
 
-The upcoming `2.0.0` package and `v4` artifact Actions aim to solve some of the major pain-points that have made artifact usage difficult up until now.
+> [!IMPORTANT]
+> @actions/artifact v2+, upload-artifact@v4+ download-artifact@v4+ are not currently supported on GHES yet. The previous version of this package can be found at [this tag](https://github.com/actions/toolkit/tree/@actions/artifact@1.1.2/packages/artifact) and [on npm](https://www.npmjs.com/package/@actions/artifact/v/1.1.2).
+
+The release of `@actions/artifact@v2` (including `upload-artifact@v4` and `download-artifact@v4`) are major changes to the backend architecture of Artifacts. They have numerous performance and behavioral improvements.
+
+### Improvements
+
+1. All upload and download operations are much quicker, up to 80% faster download times and 96% faster upload times in worst case scenarios.
+2. Once uploaded, an Artifact ID is returned and Artifacts are immediately available in the UI and [REST API](https://docs.github.com/en/rest/actions/artifacts). Previously, you would have to wait for the run to be completed before an ID was available or any APIs could be utilized.
+3. Artifacts can now be downloaded and deleted from the UI _before_ the entire workflow run finishes.
+4. The contents of an Artifact are uploaded together into an _immutable_ archive. They cannot be altered by subsequent jobs. Both of these factors help reduce the possibility of accidentally corrupting Artifact files. (Digest/integrity hash coming soon in the API!)
+5. This library (and `actions/download-artifact`) now support downloading Artifacts from _other_ repositories and runs if a `GITHUB_TOKEN` with sufficient `actions:read` permissions are provided.
+
+### Breaking changes
+
+1. Firewall rules required for self-hosted runners.
+
+    If you are using self-hosted runners behind a firewall, you must have flows open to [Actions endpoints](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#communication-between-self-hosted-runners-and-github). If you cannot use wildcard rules for your firewall, see the GitHub [meta endpoint](https://api.github.com/meta) for specific endpoints.
+
+    e.g.
+
+    ```bash
+    curl https://api.github.com/meta | jq .domains.actions
+    ```
+
+2. Uploading to the same named Artifact multiple times.
+
+    Due to how Artifacts are created in this new version, it is no longer possible to upload to the same named Artifact multiple times. You must either split the uploads into multiple Artifacts with different names, or only upload once.
+
+3. Limit of Artifacts for an individual job.
+
+    Each job in a workflow run now has a limit of 10 artifacts.
+
+## Quick Start
+
+Install the package:
+
+```bash
+npm i @actions/artifact
+```
+
+Import the module:
+
+```js
+// ES6 module
+import artifact from '@actions/artifact'
+
+// CommonJS
+const {default: artifact} = require('@actions/artifact')
+```
+
+ℹ️ For a comprehensive list of classes, interfaces, functions and more, see the [generated documentation](./docs/generated/README.md).
+
+## Examples
+
+### Upload and Download
+
+The most basic scenario is uploading one or more files to an Artifact, then downloading that Artifact. Downloads are based on the Artifact ID, which can be obtained in the response of `uploadArtifact`, `getArtifact`, `listArtifacts` or via the [REST API](https://docs.github.com/en/rest/actions/artifacts).
+
+```js
+const {id, size} = await artifact.uploadArtifact(
+  // name of the artifact
+  'my-artifact',
+  // files to include (supports absolute and relative paths)
+  ['/absolute/path/file1.txt', './relative/file2.txt'],
+  {
+    // optional: how long to retain the artifact
+    // if unspecified, defaults to repository/org retention settings (the limit of this value)
+    retentionDays: 10
+  }
+)
+
+console.log(`Created artifact with id: ${id} (bytes: ${size}`)
+
+const {downloadPath} = await artifact.downloadArtifact(id, {
+  // optional: download destination path. otherwise defaults to $GITHUB_WORKSPACE
+  path: '/tmp/dst/path',
+})
+
+console.log(`Downloaded artifact ${id} to: ${downloadPath}`)
+```
+
+### Downloading from other workflow runs or repos
+
+It may be useful to download Artifacts from other workflow runs, or even other repositories. By default, the permissions are scoped so they can only download Artifacts within the current workflow run. To elevate permissions for this scenario, you must specify `options.findBy` to `downloadArtifact`.
+
+```ts
+const findBy = {
+  // must have actions:read permission on target repository
+  token: process.env['GITHUB_TOKEN'],
+  workflowRunId: 123,
+  repositoryOwner: 'actions',
+  repositoryName: 'toolkit'
+}
+
+await artifact.downloadArtifact(1337, {
+  findBy
+})
+
+// can also be used in other methods
+
+await artifact.getArtifact('my-artifact', {
+  findBy
+})
+
+await artifact.listArtifacts({
+  findBy
+})
+```
+
+### Speeding up large uploads
+
+If you have large files that need to be uploaded (or file types that don't compress well), you may benefit from changing the compression level of the Artifact archive. NOTE: This is a tradeoff between artifact upload time and stored data size.
+
+```ts
+await artifact.uploadArtifact('my-massive-artifact', ['big_file.bin'], {
+  // The level of compression for Zlib to be applied to the artifact archive.
+  // - 0: No compression
+  // - 1: Best speed
+  // - 6: Default compression (same as GNU Gzip)
+  // - 9: Best compression
+  compressionLevel: 0
+})
+```
+
+## Additional Resources
+
+- [Releases](./RELEASES.md)
+- [Contribution Guide](./CONTRIBUTIONS.md)
+- [Frequently Asked Questions](./docs/faq.md)
diff --git a/packages/artifact/RELEASES.md b/packages/artifact/RELEASES.md
index 580d840..ce3ed43 100644
--- a/packages/artifact/RELEASES.md
+++ b/packages/artifact/RELEASES.md
@@ -94,3 +94,10 @@
 ### 1.1.1
 
 - Fixed a bug in Node16 where if an HTTP download finished too quickly (<1ms, e.g. when it's mocked) we attempt to delete a temp file that has not been created yet [#1278](https://github.com/actions/toolkit/pull/1278/commits/b9de68a590daf37c6747e38d3cb4f1dd2cfb791c)
+
+### 2.0.0
+
+Major release. Supports new Artifact backend for improved speed, reliability and behavior.
+Numerous API changes, [some breaking](./README.md#breaking-changes).
+
+Blog post with more info: TBD
diff --git a/packages/artifact/docs/docs.md b/packages/artifact/docs/docs.md
deleted file mode 100644
index e0fa311..0000000
--- a/packages/artifact/docs/docs.md
+++ /dev/null
@@ -1,1 +0,0 @@
-Docs will be added here once development of version `2.0.0` has finished
\ No newline at end of file
diff --git a/packages/artifact/docs/faq.md b/packages/artifact/docs/faq.md
new file mode 100644
index 0000000..c88d5c1
--- /dev/null
+++ b/packages/artifact/docs/faq.md
@@ -0,0 +1,35 @@
+# Frequently Asked Questions
+
+- [Frequently Asked Questions](#frequently-asked-questions)
+  - [Supported Characters](#supported-characters)
+  - [Compression? ZIP? How is my artifact stored?](#compression-zip-how-is-my-artifact-stored)
+
+## Supported Characters
+
+When uploading an artifact, the inputted `name` parameter along with the files specified in `files` cannot contain any of the following characters. If they are present in `name` or `files`,  the Artifact will be rejected by the server and the upload will fail. These characters are not allowed due to limitations and restrictions with certain file systems such as NTFS. To maintain platform-agnostic behavior, characters that are not supported by an individual filesystem/platform will not be supported on all filesystems/platforms.
+
+- "
+- :
+- <
+- \>
+- |
+- \*
+- ?
+
+In addition to the aforementioned characters, the inputted `name` also cannot include the following
+- \
+- /
+
+## Compression? ZIP? How is my artifact stored?
+
+When creating an Artifact, the files are dynamically compressed and streamed into a ZIP archive. Since they are stored in a ZIP, they can be compressed by Zlib in varying levels.
+
+The value can range from 0 to 9:
+
+- 0: No compression
+- 1: Best speed
+- 6: Default compression (same as GNU Gzip)
+- 9: Best compression
+
+Higher levels will result in better compression, but will take longer to complete.
+For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads.
diff --git a/packages/artifact/src/artifact.ts b/packages/artifact/src/artifact.ts
index 3ba3844..f5d9144 100644
--- a/packages/artifact/src/artifact.ts
+++ b/packages/artifact/src/artifact.ts
@@ -1,12 +1,8 @@
-import {ArtifactClient, Client} from './internal/client'
+import {ArtifactClient, DefaultArtifactClient} from './internal/client'
 
-/**
- * Exported functionality that we want to expose for any users of @actions/artifact
- */
 export * from './internal/shared/interfaces'
 export * from './internal/shared/errors'
-export {ArtifactClient}
+export * from './internal/client'
 
-export function create(): ArtifactClient {
-  return Client.create()
-}
+const client: ArtifactClient = new DefaultArtifactClient()
+export default client
diff --git a/packages/artifact/src/internal/client.ts b/packages/artifact/src/internal/client.ts
index 4dd40b8..c4971d9 100644
--- a/packages/artifact/src/internal/client.ts
+++ b/packages/artifact/src/internal/client.ts
@@ -19,9 +19,12 @@ import {getArtifactPublic, getArtifactInternal} from './find/get-artifact'
 import {listArtifactsPublic, listArtifactsInternal} from './find/list-artifacts'
 import {GHESNotSupportedError} from './shared/errors'
 
+/**
+ * Generic interface for the artifact client.
+ */
 export interface ArtifactClient {
   /**
-   * Uploads an artifact
+   * Uploads an artifact.
    *
    * @param name The name of the artifact, required
    * @param files A list of absolute or relative paths that denote what files should be uploaded
@@ -40,7 +43,7 @@ export interface ArtifactClient {
    * Lists all artifacts that are part of the current workflow run.
    * This function will return at most 1000 artifacts per workflow run.
    *
-   * If options.findBy is specified, this will call the public List-Artifacts API which can list from other runs.
+   * If `options.findBy` is specified, this will call the public List-Artifacts API which can list from other runs.
    * https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts
    *
    * @param options Extra options that allow for the customization of the list behavior
@@ -55,10 +58,10 @@ export interface ArtifactClient {
    * If there are multiple artifacts with the same name in the same workflow run, this will return the latest.
    * If the artifact is not found, it will throw.
    *
-   * If options.findBy is specified, this will use the public List Artifacts API with a name filter which can get artifacts from other runs.
+   * If `options.findBy` is specified, this will use the public List Artifacts API with a name filter which can get artifacts from other runs.
    * https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts
-   * @actions/artifact > 2.0.0 does not allow for creating multiple artifacts with the same name in the same workflow run.
-   * It is possible to have multiple artifacts with the same name in the same workflow run by using old versions of upload-artifact (v1,v2 and v3), @actions/artifact < v2.0.0 or it is a rerun.
+   * `@actions/artifact` v2+ does not allow for creating multiple artifacts with the same name in the same workflow run.
+   * It is possible to have multiple artifacts with the same name in the same workflow run by using old versions of upload-artifact (v1,v2 and v3), @actions/artifact < v2 or it is a rerun.
    * If there are multiple artifacts with the same name in the same workflow run this function will return the first artifact that matches the name.
    *
    * @param artifactName The name of the artifact to find
@@ -72,7 +75,7 @@ export interface ArtifactClient {
   /**
    * Downloads an artifact and unzips the content.
    *
-   * If options.findBy is specified, this will use the public Download Artifact API https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#download-an-artifact
+   * If `options.findBy` is specified, this will use the public Download Artifact API https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#download-an-artifact
    *
    * @param artifactId The name of the artifact to download
    * @param options Extra options that allow for the customization of the download behavior
@@ -84,17 +87,10 @@ export interface ArtifactClient {
   ): Promise<DownloadArtifactResponse>
 }
 
-export class Client implements ArtifactClient {
-  /**
-   * Constructs a Client
-   */
-  static create(): Client {
-    return new Client()
-  }
-
-  /**
-   * Upload Artifact
-   */
+/**
+ * The default artifact client that is used by the artifact action(s).
+ */
+export class DefaultArtifactClient implements ArtifactClient {
   async uploadArtifact(
     name: string,
     files: string[],
@@ -120,9 +116,6 @@ If the error persists, please check whether Actions is operating normally at [ht
     }
   }
 
-  /**
-   * Download Artifact
-   */
   async downloadArtifact(
     artifactId: number,
     options?: DownloadArtifactOptions & FindOptions
@@ -161,9 +154,6 @@ If the error persists, please check whether Actions and API requests are operati
     }
   }
 
-  /**
-   * List Artifacts
-   */
   async listArtifacts(
     options?: ListArtifactsOptions & FindOptions
   ): Promise<ListArtifactsResponse> {
@@ -200,9 +190,6 @@ If the error persists, please check whether Actions and API requests are operati
     }
   }
 
-  /**
-   * Get Artifact
-   */
   async getArtifact(
     artifactName: string,
     options?: FindOptions
diff --git a/packages/artifact/src/internal/shared/errors.ts b/packages/artifact/src/internal/shared/errors.ts
index a46a01f..841c44a 100644
--- a/packages/artifact/src/internal/shared/errors.ts
+++ b/packages/artifact/src/internal/shared/errors.ts
@@ -29,7 +29,7 @@ export class ArtifactNotFoundError extends Error {
 
 export class GHESNotSupportedError extends Error {
   constructor(
-    message = '@actions/artifact v2.0.0+ and download-artifact@v4+ are not currently supported on GHES.'
+    message = '@actions/artifact v2.0.0+, upload-artifact@v4+ and download-artifact@v4+ are not currently supported on GHES.'
   ) {
     super(message)
     this.name = 'GHESNotSupportedError'
diff --git a/packages/artifact/src/internal/shared/interfaces.ts b/packages/artifact/src/internal/shared/interfaces.ts
index dfd3e1d..e40fb66 100644
--- a/packages/artifact/src/internal/shared/interfaces.ts
+++ b/packages/artifact/src/internal/shared/interfaces.ts
@@ -127,7 +127,7 @@ export interface Artifact {
 }
 
 // FindOptions are for fetching Artifact(s) out of the scope of the current run.
-// Must specify a PAT with actions:read scope for cross run/repo lookup otherwise these will be ignored.
+// Must specify a token with actions:read scope for cross run/repo lookup otherwise these will be ignored.
 export interface FindOptions {
   findBy?: {
     // Token with actions:read permissions


Test patch:
diff --git a/.github/workflows/artifact-tests.yml b/.github/workflows/artifact-tests.yml
index 7bbb4aa..4c7c6ab 100644
--- a/.github/workflows/artifact-tests.yml
+++ b/.github/workflows/artifact-tests.yml
@@ -58,21 +58,21 @@ jobs:
       uses: actions/github-script@v7
       with:
         script: |
-          const artifact = require('./packages/artifact/lib/artifact')
+          const {default: artifact} = require('./packages/artifact/lib/artifact')
 
           const artifactName = 'my-artifact-${{ matrix.runs-on }}'
           console.log('artifactName: ' + artifactName)
 
           const fileContents = ['artifact-path/first.txt','artifact-path/second.txt']
 
-          const uploadResult = await artifact.create().uploadArtifact(artifactName, fileContents, './')
+          const uploadResult = await artifact.uploadArtifact(artifactName, fileContents, './')
           console.log(uploadResult)
 
           const size = uploadResult.size
           const id = uploadResult.id
 
           console.log(`Successfully uploaded artifact ${id}`)
-           
+
   verify:
     runs-on: ubuntu-latest
     needs: [build]
@@ -100,14 +100,14 @@ jobs:
       uses: actions/github-script@v7
       with:
         script: |
-          const artifact = require('./packages/artifact/lib/artifact')
+          const {default: artifact} = require('./packages/artifact/lib/artifact')
 
           const workflowRunId = process.env.GITHUB_RUN_ID
           const repository = process.env.GITHUB_REPOSITORY
           const repositoryOwner = repository.split('/')[0]
           const repositoryName = repository.split('/')[1]
 
-          const listResult = await artifact.create().listArtifacts(workflowRunId, repositoryOwner, repositoryName, '${{ secrets.GITHUB_TOKEN }}')
+          const listResult = await artifact.listArtifacts(workflowRunId, repositoryOwner, repositoryName, '${{ secrets.GITHUB_TOKEN }}')
           console.log(listResult)
 
           const artifacts = listResult.artifacts